diff --git a/LICENSE b/LICENSE index 29f81d812f3e768fa89638d1f72920dbfd1413a8..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/common/__init__.py b/app/common/__init__.py index 4c5d8937a923e5e96c9afeaee13305cbd476458c..baf8c672767e72dce4c18cb60699c7183d8965b2 100644 --- a/app/common/__init__.py +++ b/app/common/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from .model import * -import math,random,socket +import math,random,socket,chardet if get_sysinfo()['uname'][0]=='Linux': config.app['appmode']='produc' # def error(err,data): @@ -35,48 +35,50 @@ def serlogin(username,sign,timestamp,random,types="session"): inifo['role']=sqlite("role",model_app_path).where('id',inifo['role']).find() sqlite('admin',model_app_path).where('id',inifo['id']).update({'logintime':times()}) + + #根据权限给当前登录用户初始化菜单 + systemrolelist=[ #系统菜单权限 + {'title':'首页','icon':config.domain['kcwebimg']+'/icon/home.png','url':'/intapp/index/index/home',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'管理员','icon':config.domain['kcwebimg']+'/icon/admin.png','url':'/intapp/index/admin',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'终端管理','icon':config.domain['kcwebimg']+'/icon/terminal.png','url':'/intapp/index/socket/terminallist',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'导航管理','icon':config.domain['kcwebimg']+'/icon/menu.png','url':'/intapp/index/menu',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'模块管理','icon':config.domain['kcwebimg']+'/icon/modular.png','url':'/intapp/index/modular',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'插件管理','icon':config.domain['kcwebimg']+'/icon/plug.png','url':'/intapp/index/plug',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'计划任务','icon':config.domain['kcwebimg']+'/icon/plan.png','url':'/intapp/index/plan',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'任务队列','icon':config.domain['kcwebimg']+'/icon/task.png','url':'/intapp/index/task',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, + {'title':'系统配置','icon':config.domain['kcwebimg']+'/icon/setup.png','url':'/intapp/index/setup',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000} + ] + plugmenu=sqlite('plug',model_app_path).select() + for k in plugmenu: #插件菜单权限 + if k['name']!='index': + systemrolelist.append({'title':k['title'],'icon':k['icon'],'url':'/'+k['modular']+'/'+k['name'],"types":"left","pid":0,"admin_id":inifo['id'],"sort":1000}) + if inifo['role']: + rolelist=[] + if inifo['role']['id']==1: #开拓者权限 + rolelist=systemrolelist + else: + for k in systemrolelist: + for kk in json_decode(inifo['role']['roleroute']): + # strs=kk.split("/") + # print(k['url'],kk) + # if k['url'] in kk: + # rolelist.append(k) + if k['url'] in kk: + # rolelist.append(k) + tttt=True + for ttt in rolelist: + if ttt['title'] == k['title']: + tttt=False + break + if tttt: + rolelist.append(k) + if len(rolelist): + urlstr="0" + for k in systemrolelist: + urlstr+=",'"+k['url']+"'" + sqlite("menu",model_app_path).where("admin_id="+str(inifo['id'])+" and url in ("+urlstr+")").delete() + sqlite("menu",model_app_path).insert(rolelist) if types=='session': #如果使用sess登录,要分配系统菜单权限 - #根据权限给当前登录用户初始化菜单 - systemrolelist=[ #系统菜单权限 - {'title':'首页','icon':config.domain['kcwebimg']+'/icon/home.png','url':'/intapp/index/index/home',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'管理员','icon':config.domain['kcwebimg']+'/icon/admin.png','url':'/intapp/index/admin',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'导航管理','icon':config.domain['kcwebimg']+'/icon/menu.png','url':'/intapp/index/menu',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'模块管理','icon':config.domain['kcwebimg']+'/icon/modular.png','url':'/intapp/index/modular',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'插件管理','icon':config.domain['kcwebimg']+'/icon/plug.png','url':'/intapp/index/plug',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'计划任务','icon':config.domain['kcwebimg']+'/icon/plan.png','url':'/intapp/index/plan',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'任务队列','icon':config.domain['kcwebimg']+'/icon/task.png','url':'/intapp/index/task',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000}, - {'title':'系统配置','icon':config.domain['kcwebimg']+'/icon/setup.png','url':'/intapp/index/setup',"types":"left","pid":0,"admin_id":inifo['id'],"sort":10000} - ] - plugmenu=sqlite('plug',model_app_path).select() - for k in plugmenu: #插件菜单权限 - if k['name']!='index': - systemrolelist.append({'title':k['title'],'icon':k['icon'],'url':'/'+k['modular']+'/'+k['name'],"types":"left","pid":0,"admin_id":inifo['id'],"sort":1000}) - if inifo['role']: - rolelist=[] - if inifo['role']['id']==1: #开拓者权限 - rolelist=systemrolelist - else: - for k in systemrolelist: - for kk in json_decode(inifo['role']['roleroute']): - # strs=kk.split("/") - # print(k['url'],kk) - # if k['url'] in kk: - # rolelist.append(k) - if k['url'] in kk: - # rolelist.append(k) - tttt=True - for ttt in rolelist: - if ttt['title'] == k['title']: - tttt=False - break - if tttt: - rolelist.append(k) - if len(rolelist): - urlstr="0" - for k in systemrolelist: - urlstr+=",'"+k['url']+"'" - sqlite("menu",model_app_path).where("admin_id="+str(inifo['id'])+" and url in ("+urlstr+")").delete() - sqlite("menu",model_app_path).insert(rolelist) set_session("userinfo",inifo) else: account_token=md5(str(username)+str(inifo['password'])) @@ -94,7 +96,10 @@ def check_role(): status=True break if not status: - return response.tpl("/intapp/html/error.html",title="无权访问",content="抱歉...,您当前没有此页面访问权限,请联系管理员",imgsrc=config.domain['kcwebimg']+"/icon/suo.png",status="401") + if 'GET' == request.HEADER.Method(): + return response.tpl("/common/html/error.html",title="无权访问",content="抱歉...,您当前没有此页面访问权限,请联系管理员",imgsrc=config.domain['kcwebimg']+"/icon/suo.png",status="401") + else: + return errorjson(msg="您没有以下接口访问权限,可联系管理员申请。\r\n"+ts,status="401") def check_login(): "检查是否登录" account_token=request.args.get('account_token') @@ -112,6 +117,7 @@ def check_login(): return check_role() def before_request(): """请求拦截, + 进行登录验证,权限验证 """ if not config.app['cli']: @@ -201,17 +207,25 @@ def get_session(name): def del_session(name): "删除session" return session.rm("app"+str(name)) -def file_get_content(k): - "获取文件内容" - if os.path.isfile(k): - f=open(k,'r',encoding="utf-8") - con=f.read() - f.close() +def file_get_content(filename,encoding=False): + """获取文件内容 + + filename 完整文件名 + + encoding 是否返回文件编码 默认否 + """ + fileData='' + with open(filename, 'rb') as f: + cur_encoding = chardet.detect(f.read())['encoding'] + #用获取的编码读取该文件而不是python3默认的utf-8读取。 + with open(filename,encoding=cur_encoding) as file: + fileData = file.read() + if encoding: + return fileData,cur_encoding else: - con='' - return con -def file_set_content(k,data): - f=open(k,'w',encoding="utf-8") + return fileData +def file_set_content(k,data,encoding="utf-8"): + f=open(k,'w',encoding=encoding) f.write(data) f.close() return True @@ -249,8 +263,8 @@ class system_start: line = f.readline() if not line: break - if 'cmd' in line: - line='cmd' + if cmd in line: + line='' con=con+line f.close() file_set_content("/usr/bin/startkcweb",con) diff --git a/app/common/file/config.conf b/app/common/file/config.conf new file mode 100644 index 0000000000000000000000000000000000000000..a6aee50895cca581173ea2e52690b592a6273b76 --- /dev/null +++ b/app/common/file/config.conf @@ -0,0 +1 @@ +{"aliyun": {"address": "http://oss-cn-beijing.aliyuncs.com", "bucket": "fanshubackups", "access_key": "LTAISSz1gBwL1oOo", "access_key_secret": "MoGi9FusUZx6Vjp8FgLrRkxU22kFON", "backpath": "kcweb"}} \ No newline at end of file diff --git a/app/config/app.py b/app/config/app.py index f87401f7353853d53419d178a318863ad9f73b7c..12b586740c1724f502211511ef1ddcf374db0ff9 100644 --- a/app/config/app.py +++ b/app/config/app.py @@ -42,16 +42,14 @@ mongo['db']='test' mongo['retryWrites']=False #是否支持重新写入 #路由配置 -route['default']=True #是否开启默认路由 默认路由开启后面不影响以下配置的路由,模块名/版本名/控制器文件名/方法名 作为路由地址 如:http://www.kcw.com/api/v1/index/index/ -route['modular']='' -route['plug']='' -route['files']='index' #默认路由文件(控制器) -route['funct']='index' #默认路由函数 (操作方法) +route['default']=True #是否开启默认路由 默认路由开启后面不影响以下配置的路由,模块名/版本名/控制器文件名/方法名 作为路由地址 如:http://www.kcw.com/modular/plug/index/index/ +route['modular']='' #指定访问配置固定模块 (如果配置了该值,将无法通过改变url访问不同模块) +route['plug']='' #指定访问固定插件 (如果配置了该值,将无法通过改变url访问不同插件) +route['defmodular']='intapp' #默认模块 当url不包括模块名时 +route['defplug']='index' #默认插件 当url不包括插件名时 +route['files']='index' #默认路由文件(控制器) 当url不包括控制器名时 +route['funct']='index' #默认路由函数 (操作方法) 当url不包括操作方法名时 route['methods']=['POST','GET'] #默认请求方式 -route['children']=[ - # {'title':'首页','path':'/','component':'base/index/index/index','methods':['POST','GET']}, - # {'title':'首页','path':'/intapp','component':'intapp/base/page/index','methods':['POST','GET']}, -] #sqlite配置 sqlite['db']='kcwlicuxweb' #sqlite数据库文件 diff --git a/app/config/database.py b/app/config/database.py index 86a3144dee85782ffcdcc73e105f8d4b99263648..6fb9289f63637d9a46cd57ff1917e2685540d1a2 100644 --- a/app/config/database.py +++ b/app/config/database.py @@ -8,7 +8,7 @@ database['user']=['root'] #用户名 [用户名1,用户名2,用户名3...] database['password']=['root'] #密码 [密码1,密码2,密码3...] database['db']=['test'] #数据库名 [数据库名1,数据库名2,数据库名3...] database['charset']='utf8' #数据库编码默认采用utf8 -database['pattern']=True # True数据库长连接模式 False数据库短连接模式 注:建议web应用使用短连接,cli应用使用长连接 +database['pattern']=False # True数据库长连接模式 False数据库短连接模式 注:建议web应用有效,cli应用方式下,如果长时间运行建议使用mysql().close()关闭 database['cli']=False # 是否以cli方式运行 database['dbObjcount']=1 # 连接池数量(单个数据库地址链接数量),数据库链接实例数量 mysql长链接模式下有效 database['deploy']=0 # 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) mysql数据库有效 diff --git a/app/intapp/controller/index/__init__.py b/app/intapp/controller/index/__init__.py index a4f463e4888c6d4bc95a4cd663d69f9952b3a6d0..0e70a9d15a40ad8eb55f5c4c21e32af9f17a0078 100644 --- a/app/intapp/controller/index/__init__.py +++ b/app/intapp/controller/index/__init__.py @@ -6,4 +6,5 @@ from . import admin from . import plan from . import task from . import setup -from . import menu \ No newline at end of file +from . import menu +from . import socket \ No newline at end of file diff --git a/app/intapp/controller/index/admin.py b/app/intapp/controller/index/admin.py index 2fe1719213a173c8bbfaaa990fe45778bc21368b..77c27d8c1ae793ede8231de40320aef43fe0943a 100644 --- a/app/intapp/controller/index/admin.py +++ b/app/intapp/controller/index/admin.py @@ -15,10 +15,10 @@ def getpluglist(modular="intapp"): # print(data) i=0 for k in data: - k['value']=k['modular']+"/"+k['name'] - k['role']=json_decode(k['role']) + # k['value']=k['modular']+"/"+k['name'] + data[i]['role']=json_decode(k['role']) if k['name'] not in lists: - print(k['name'],lists) + # print(k['name'],lists) del data[i] i+=1 return successjson(data) @@ -80,6 +80,7 @@ def update(id=0): except:pass else: sqlite("admin",model_app_path).where("id",id).update(data) + shutil.rmtree(config.session['path']) return successjson() def setpwd(): "设置管理员登录密码" @@ -90,6 +91,15 @@ def setpwd(): return errorjson(msg="设置失败") else: return successjson() +def setmypwd(): + """设置自己的登录密码""" + data=request.get_json() + try: + sqlite("admin",model_app_path).where("id",G.userinfo['id']).update({'password':data['password']}) + except: + return errorjson(msg="设置失败") + else: + return successjson() def getrolelist(id=0): "获取角色" if id: @@ -139,6 +149,7 @@ def updaterole(id=0): except:pass else: sqlite("role",model_app_path).where("id",id).update(data) + shutil.rmtree(config.session['path']) return successjson() def deleterole(): "批量删除" diff --git a/app/intapp/controller/index/common/model.py b/app/intapp/controller/index/common/model.py index d1faecb3e3c6c379943eb9bb8250dc357597ab05..0a9b6372198a94bffeb9c27371ab9ff249448ff8 100644 --- a/app/intapp/controller/index/common/model.py +++ b/app/intapp/controller/index/common/model.py @@ -33,6 +33,48 @@ try: except: model_intapp_interval=model_intapp_interval() model_intapp_interval.create_table() +class model_intapp_terminal(modelsqliteintapp): + "终端列表" + table="terminal" + fields={ + "id":model.dbtype.int(LEN=11,PRI=True,A_L=True), #设置id为自增主键 + "icon":model.dbtype.varchar(LEN=128,DEFAULT=''), + "title":model.dbtype.varchar(LEN=128,DEFAULT=''), + "host":model.dbtype.varchar(LEN=32,DEFAULT=''), + "port":model.dbtype.varchar(LEN=32,DEFAULT=''), + "username":model.dbtype.varchar(LEN=32,DEFAULT=''), + "password":model.dbtype.varchar(LEN=32,DEFAULT=''), + "blacklist":model.dbtype.text(), #命令黑名单 + "whitelist":model.dbtype.text(), #命令白名单 + "types":model.dbtype.varchar(LEN=32,DEFAULT='共享连接'), #连接方式 + "addtime":model.dbtype.int(LEN=11,DEFAULT=0), #添加时间 + "updtime":model.dbtype.int(LEN=11,DEFAULT=0) #添加时间 + } +try: + sqlite('terminal',model_intapp_index_path).find() +except: + model_intapp_terminal=model_intapp_terminal() + model_intapp_terminal.create_table() +class model_intapp_shelllog(modelsqliteintapp): + "终端操作日志" + table="shelllog" + fields={ + "id":model.dbtype.int(LEN=11,PRI=True,A_L=True), #设置id为自增主键 + "uid":model.dbtype.int(LEN=11), + "icon":model.dbtype.varchar(LEN=256,DEFAULT=''), #头像地址 + "name":model.dbtype.varchar(LEN=64,DEFAULT=''), + "host":model.dbtype.varchar(LEN=32,DEFAULT=''), + "port":model.dbtype.varchar(LEN=32,DEFAULT=''), + "username":model.dbtype.varchar(LEN=32,DEFAULT=''), + "password":model.dbtype.varchar(LEN=32,DEFAULT=''), + "cmd":model.dbtype.varchar(LEN=2000,DEFAULT=''), #命令 + "addtime":model.dbtype.int(LEN=11,DEFAULT=0), #添加时间 + } +try: + sqlite('shelllog',model_intapp_index_path).find() +except: + model_intapp_shelllog=model_intapp_shelllog() + model_intapp_shelllog.create_table() def sqlite(table=None,configss=model_intapp_index_path): """sqlite数据库操作实例 diff --git a/app/intapp/controller/index/common/plantask.py b/app/intapp/controller/index/common/plantask.py index 65abec8df951af844231ac4b187204ec846d59f5..a48c07d036d4ea3d550ffdc24b22ae926e6ca5d9 100644 --- a/app/intapp/controller/index/common/plantask.py +++ b/app/intapp/controller/index/common/plantask.py @@ -80,17 +80,13 @@ class PLANTASK(): elif data['types']=='openurl': func=PLANTASK.openurls args=(data['value'],iden) - elif data['types']=='backupmysql': #备份mysql - # data['value'] #备份目录 - func=PLANTASK.shells - if data['oss']=='1': - args=("python3 server.py /intapp/soft/mysql/backups/whole/bankup_all_databases/1 --cli",iden) - else: - args=("python3 server.py /intapp/soft/mysql/backups/whole/bankup_all_databases --cli",iden) elif data['types']=='restart-php-fpm': #重启php func=PLANTASK.shells phpname=data['value'].replace(".", "") args=("pkill -9 "+phpname+"-fpm && "+phpname+"-fpm"+" -c /usr/local/php/"+data['value']+"/bin/php.ini -R",iden) + else: + func=PLANTASK.shells + args=(data['value'],iden) targger="cron" year=None month=None @@ -148,8 +144,10 @@ class PLANTASK(): PLANTASK.BlockingSchedulers = BlockingScheduler() if 'Linux' in get_sysinfo()['platform']: mu=multiprocessing.Process(target=PLANTASK.plantaskdsfsdfsafdsafsd,args=(PLANTASK.BlockingSchedulers,func,targger,args,year,month,week,day_of_week,day,hour,minute,second,iden)) - mu.daemon=True mu.start() + # f=open(config.cache['path']+"/plantaskstart.log","a") + # f.write("启动计划日志,"+data['value']+","+str(times())+"\n") + # f.close() elif 'Windows' in get_sysinfo()['platform']: t=threading.Thread(target=PLANTASK.plantaskdsfsdfsafdsafsd,args=(PLANTASK.BlockingSchedulers,func,targger,args,year,month,week,day_of_week,day,hour,minute,second,iden)) t.daemon=True @@ -157,14 +155,4 @@ class PLANTASK(): except: return False else: - return True -#这里是初始化计划任务 -if not config.app['cli']: #判断运行方式 - try: - serverserverintervals=sqlite("interval",model_intapp_index_path).select() - for serverserverinterval in serverserverintervals: - if (times()-int(serverserverinterval['updtime'])) > 5: - serverserverinterval['updtime']=times() - sqlite("interval",model_intapp_index_path).where("id",serverserverinterval['id']).update(serverserverinterval) - PLANTASK.plantask(serverserverinterval) #添加计划任务 - except:pass \ No newline at end of file + return True \ No newline at end of file diff --git a/app/intapp/controller/index/index.py b/app/intapp/controller/index/index.py index ed5af1a2a660b794b2b00556a95f4f9a6e7bb8f0..9305d5d5f6c96b19652a0461bd5db2568467b520 100644 --- a/app/intapp/controller/index/index.py +++ b/app/intapp/controller/index/index.py @@ -1,7 +1,7 @@ from .common import * import psutil def index(): - return response.tpl() + return response.tpl(userinfo=G.userinfo) def home(): return response.tpl() # def pub(html): @@ -49,10 +49,10 @@ def homes(): def disk():#磁盘分区和使用情况 partition=[] disk_usage=psutil.disk_usage('/') - # print(disk_usage) - partition.append({ - 'name':'/','type':'','count':disk_usage[0],'used':disk_usage[1],'free':disk_usage[2],'userate':disk_usage[3] - }) + if "Linux" in get_sysinfo()['platform']: + partition.append({ + 'name':'/','type':'','count':disk_usage[0],'used':disk_usage[1],'free':disk_usage[2],'userate':disk_usage[3] + }) partitions=psutil.disk_partitions() for v in partitions: disk_usage=psutil.disk_usage(v[0]) @@ -110,9 +110,18 @@ def process(): def shell(): - if G.userinfo['id']==1: + shellstr=request.args.get("shellstr") + if shellstr: + os.system(shellstr) + else: data=request.get_json() os.system(data['shell']) - return successjson() + return successjson() +def reboot(types='app'): + "重启" + if types=='app': + shellstr="bash server.sh" else: - return errorjson(msg="该功能仅对初始用户有效") \ No newline at end of file + shellstr="reboot" + os.system(shellstr) + return successjson() \ No newline at end of file diff --git a/app/intapp/controller/index/install.txt b/app/intapp/controller/index/install.txt index a87aaef3aaa07ece0e6550ec2a0cbfaadae8f7c6..4e40c2e5b34a2fb0a9c993e200fb45bbb2263ea6 100644 --- a/app/intapp/controller/index/install.txt +++ b/app/intapp/controller/index/install.txt @@ -1 +1,2 @@ -psutil==5.7.0 \ No newline at end of file +psutil==5.7.0 +pexpect==4.8.0 \ No newline at end of file diff --git a/app/intapp/controller/index/modular.py b/app/intapp/controller/index/modular.py index f7b8445a5f759f106eb1a72a8b4fd421335e83ba..561a24e6eda50076e0e40b83c00bbaac7ff7a79a 100644 --- a/app/intapp/controller/index/modular.py +++ b/app/intapp/controller/index/modular.py @@ -47,7 +47,10 @@ def modular_list(kw='',pagenow=1): k['status']=0 #0未安装 1已安装 2安装中 3卸载中 4不可以安装 if os.path.exists("app/"+str(k['name'])): k['status']=1 - kcwebuserinfo=file_get_content(kcwebuserinfopath+str(G.userinfo['id'])) + if os.path.isfile(kcwebuserinfopath+str(G.userinfo['id'])): + kcwebuserinfo=file_get_content(kcwebuserinfopath+str(G.userinfo['id'])) + else: + kcwebuserinfo='' if kcwebuserinfo: res['kcwebuserinfo']=json_decode(kcwebuserinfo) else: diff --git a/app/intapp/controller/index/plan.py b/app/intapp/controller/index/plan.py index b76653cf7aaf72e22dd4fd7ca5be29982fa404f4..3744e20eac9cbaafdbf6838c873d57266ca55ffa 100644 --- a/app/intapp/controller/index/plan.py +++ b/app/intapp/controller/index/plan.py @@ -42,14 +42,14 @@ def add(): if data['types'] != 'backupmysql' and (not data['name'] or not data['value']): return errorjson(code=1,msg="参数不全") # PLANTASK.plantask(data) - Queues.insert(target=PLANTASK.plantask,args=(data,),title="添加任务任务:"+data['name']) + # Queues.insert(target=PLANTASK.plantask,args=(data,),title="添加任务任务:"+data['name']) sqlite("interval").insert(data) - return successjson() + return successjson(msg="添加成功,重启后生效") def delpl(): "删除计划" id=request.get_json() sqlite("interval").where('id','in',id).delete() - return successjson(msg="任务已删除,重启控制板后生效") + return successjson(msg="任务已删除,重启后生效") def log(iden): "任务日志" return successjson(PLANTASK.log(iden)) diff --git a/app/intapp/controller/index/plug.py b/app/intapp/controller/index/plug.py index eb3fd7133ee72b1678460aa369bf5146dc9f24a6..0ffdfc9834a2293ddfbe1570cb6d627d53475723 100644 --- a/app/intapp/controller/index/plug.py +++ b/app/intapp/controller/index/plug.py @@ -41,7 +41,10 @@ def plug_list(modular="intapp",pagenow=1,group=False): if k['edition'] and float(k['edition'][0]) > float(kk['edition']): k['status']=5 kk['status']=5 - kcwebuserinfo=file_get_content(kcwebuserinfopath+str(G.userinfo['id'])) + if os.path.isfile(kcwebuserinfopath+str(G.userinfo['id'])): + kcwebuserinfo=file_get_content(kcwebuserinfopath+str(G.userinfo['id'])) + else: + kcwebuserinfo={} if kcwebuserinfo: res['kcwebuserinfo']=json_decode(kcwebuserinfo) else: @@ -73,14 +76,15 @@ def uploadplug(): else: return errorjson(msg="请先配置kcweb账号") def installplug(): - "安装插件" + "安装或更新插件" arr=request.get_json() #备份插件文稿数据 dirname="app/"+arr['modular']+"/controller/"+arr['name']+"/common/file" - if not os.path.exists("backup/"+dirname): - os.makedirs("backup/"+dirname) - zip.packzip(dirname,"backup/"+dirname+"/backup.zip") - file_set_content("backup/"+dirname+"/time",str(times())) + if os.path.exists(dirname): + if not os.path.exists("backup/"+dirname): + os.makedirs("backup/"+dirname) + zip.packzip(dirname,"backup/"+dirname+"/backup.zip") + file_set_content("backup/"+dirname+"/time",str(times())) server=create("app",arr['modular']) diff --git a/app/intapp/controller/index/pub.py b/app/intapp/controller/index/pub.py index c377a05ff7d67bcef5aae5ce99e572cccacfa71b..0ea49c27a6c0eadca21e582e5c5e26e9f4f2a1c2 100644 --- a/app/intapp/controller/index/pub.py +++ b/app/intapp/controller/index/pub.py @@ -1,16 +1,24 @@ from .common import * -import subprocess +import subprocess,pexpect + def before_request(): pass def checkserver(): return successjson() def outlogin(): - # account_token=request.args.get("account_token") - # if account_token: - # del_cache(account_token) - # else: - del_session('userinfo') + account_token=request.args.get("account_token") + if account_token: + del_cache(account_token) + else: + del_session('userinfo') return successjson() +def get_account_token(username,sign,timestamp,random,types="get_account_token"): + "获取用户token" + status,code,msg,account_token=serlogin(username,sign,timestamp,random,types) + if status: + return successjson(data={"account_token":account_token},msg=msg) + else: + return errorjson(code=code,msg=msg) def login(username,sign,timestamp,random,types="session"): "登录" status,code,msg,account_token=serlogin(username,sign,timestamp,random,types) @@ -18,9 +26,17 @@ def login(username,sign,timestamp,random,types="session"): return successjson(data=account_token,msg=msg) else: return errorjson(code=code,msg=msg) -def images(pre="cir",ContentType=""): - "pre 可选 squ、cir" - return "","200 ok",{"Content-Type":ContentType,"Access-Control-Allow-Origin":"*"} +def clistartplan(): + #这里是初始化计划任务 (cli方式运行) + try: + serverserverintervals=sqlite("interval",model_intapp_index_path).select() + for serverserverinterval in serverserverintervals: + if (times()-int(serverserverinterval['updtime'])) > 5: + serverserverinterval['updtime']=times() + sqlite("interval",model_intapp_index_path).where("id",serverserverinterval['id']).update(serverserverinterval) + PLANTASK.plantask(serverserverinterval) #添加计划任务 + except: + pass def gitpull(): "执行git" path=request.args.get('path') @@ -28,11 +44,21 @@ def gitpull(): title=request.args.get('title') if not title: title="执行命令 git pull 命令" - shell='cd '+path+' && sudo /usr/bin/git reset --hard' - if branch: - shell+=' origin/'+branch - shell+=' && sudo /usr/bin/git clean -f && sudo /usr/bin/git pull' - Queues.insert(target=PUBLICOther.gitpull,args=(path,shell),title=title,describes="执行命令:"+shell) + if 'Linux' in get_sysinfo()['platform']: + shell='cd '+path+' && git reset --hard' + if branch: + shell+=' origin/'+branch + shell+=' && git clean -f && git pull' + Queues.insert(target=PUBLICOther.gitpull,args=(path,shell),title=title,describes="执行命令:"+shell) + elif 'Window' in get_sysinfo()['platform']: + shell="git reset --hard" + if branch: + shell+=' origin/'+branch + Queues.insert(target=PUBLICOther.gitpull,args=(path,shell),title=title,describes="执行命令:"+shell) + shell='git clean -f' + Queues.insert(target=PUBLICOther.gitpull,args=(path,shell),title=title,describes="执行命令:"+shell) + shell='git pull' + Queues.insert(target=PUBLICOther.gitpull,args=(path,shell),title=title,describes="执行命令:"+shell) return successjson("命令已添加到任务队列中") class PUBLICOther(): def gitpull(path,shell): @@ -40,4 +66,94 @@ class PUBLICOther(): strs=pi.stdout.read().decode() # f=open(path+"/gitpull.log","w",encoding='utf-8') # f.write("\n时间:%s\n%s\n%s\n" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),shell,strs)) - # f.close() \ No newline at end of file + # f.close() + + +# def __createssl(data): +# "创建Let's Encrypt免费证书" +# # data=request.get_json() +# if len(data['domain'])<1: +# return errorjson(msg="至少添加一个域名") +# path=os.path.split(os.path.realpath(__file__))[0] +# # cmd='/intapp/letsencrypt/letsencrypt-auto certonly --standalone --email fk1402936534@qq.com -d test1.kwebapp.cn' +# # cmd='/intapp/letsencrypt/letsencrypt-auto certonly --email fk1402936534@qq.com -d *.kwebapp.cn --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory' +# cmd=path+'/letsencrypt/letsencrypt-auto certonly --standalone --email '+data['email'] +# # cmd='/intapp/letsencrypt/letsencrypt-auto certonly --email '++data['email'] +# domain='' +# for k in data['domain']: +# if k['domain']: +# cmd+=" -d "+k['domain'] +# domain+=k['domain']+"\n" +# else: +# return errorjson(msg="请输入域名") +# # file_set_content("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])+"/config.conf",json_encode(conf)) +# if os.path.exists("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])): +# shutil.rmtree("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])) +# if os.path.exists("/etc/letsencrypt/renewal/"+str(data['domain'][0]['domain'])): +# shutil.rmtree("/etc/letsencrypt/renewal/"+str(data['domain'][0]['domain'])) +# # cmd+=' --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory'' +# child = pexpect.spawn(cmd,timeout=180) + +# time.sleep(1) +# index = child.expect(["gree","right IP address","No valid IP addresses","ongratulations","appropriate number","Problem binding to port 80","certificate and chain have been saved","ocsp.int-x3.letsencrypt.org", pexpect.EOF, pexpect.TIMEOUT]) +# print("index1",index) +# if index == 0 : +# child.sendline("A") +# index = child.expect(["es", pexpect.EOF, pexpect.TIMEOUT]) +# print("index2",index) +# if (index == 0): +# child.sendline("Y") +# index = child.expect(["(?i)ongratulations", pexpect.EOF, pexpect.TIMEOUT]) +# print("index3",index) +# if index == 0: +# print("成功") +# conf={ +# "data":data, +# "createtime":times(), +# "createdate":str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) +# } +# file_set_content("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])+"/config.conf",json_encode(conf)) +# child.close(force=True) +# raise Exception("Let's Encrypt申请成功,请到ssl证书夹查看") +# else: +# print("error1") +# child.close(force=True) +# raise Exception("Let's Encrypt申请失败") +# else: +# raise Exception("申请失败,原因未知") +# child.close(force=True) +# elif index == 1 or index == 2: +# child.close(force=True) +# raise Exception("域名无法访问,你申请的域名可能没有解析到当前服务器或那您的域名没有备案") +# elif index == 3: +# conf={ +# "data":data, +# "createtime":times(), +# "createdate":str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) +# } +# file_set_content("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])+"/config.conf",json_encode(conf)) +# child.close(force=True) +# raise Exception("Let's Encrypt免费证书申请成功,请到ssl证书夹查看") +# elif index == 4: +# child.close(force=True) +# raise Exception("该域名已签发,并在有效期内") +# elif index == 5: +# child.close(force=True) +# raise Exception("80端口被占用,请停止占用80端口的程序后重新申请") +# elif index == 6: +# child.close(force=True) +# conf={ +# "data":data, +# "createtime":times(), +# "createdate":str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) +# } +# file_set_content("/etc/letsencrypt/live/"+str(data['domain'][0]['domain'])+"/config.conf",json_encode(conf)) + +# raise Exception("Let's ssl申请成功,请到ssl证书夹查看") +# elif index == 7: +# child.close(force=True) +# raise Exception("Let's ssl官网无法访问,请稍后或明日再试") +# else: +# print("error") +# child.close(force=True) +# raise Exception("Let's ssl申请失败") \ No newline at end of file diff --git a/app/intapp/controller/index/role.txt b/app/intapp/controller/index/role.txt index e269f6ba9c93b02468e3ea53e6749da555f8a168..dbcb02453b4a1d3b8719f06538be901747f3de0c 100644 --- a/app/intapp/controller/index/role.txt +++ b/app/intapp/controller/index/role.txt @@ -6,10 +6,75 @@ cpu和内存以及网络,/intapp/index/index/cpume 进程列表,/intapp/index/index/process shell执行能力,/intapp/index/index/shell -管理员,/intapp/index/admin -计划任务,/intapp/index/plan -任务队列,/intapp/index/task -系统配置,/intapp/index/setup -模块管理,/intapp/index/modular -插件管理,/intapp/index/plug -导航管理,/intapp/index/menu \ No newline at end of file +重启,/intapp/index/index/reboot + +管理员页面,/intapp/index/admin/index +路由列表,/intapp/index/admin/getpluglist +管理员列表,/intapp/index/admin/getlist +添加管理员,/intapp/index/admin/insert +删除管理员,/intapp/index/admin/delete +编辑管理员,/intapp/index/admin/update +设置管理员密码,/intapp/index/admin/setpwd +设置自己的密码,/intapp/index/admin/setmypwd +权限页面,/intapp/index/admin/role +角色列表,/intapp/index/admin/getrolelist +添加角色,/intapp/index/admin/insertrole +更新角色,/intapp/index/admin/updaterole +删除角色,/intapp/index/admin/deleterole + +导航页面,/intapp/index/menu/index +导航列表,/intapp/index/menu/menulist +编辑导航,/intapp/index/menu/menuupdate +删除导航,/intapp/index/menu/menudelete +添加导航,/intapp/index/menu/menuinsert + +模块页面,/intapp/index/modular/index +绑定kcweb,/intapp/index/modular/banduser +注册kcweb,/intapp/index/modular/kcwebreg +发送验证码,/intapp/index/modular/kcwebsebduser +模块列表,/intapp/index/modular/modular_list +打包模块上传,/intapp/index/modular/uploadmodular +安装模块,/intapp/index/modular/installmodular +卸载模块,/intapp/index/modular/uninstallmodular + + +插件页面,/intapp/index/plug/index +插件列表,/intapp/index/plug/getpluglist +云插件列表,/intapp/index/plug/plug_list +打包插件上传,/intapp/index/plug/uploadplug +安装更新插件,/intapp/index/plug/installplug +卸载插件,/intapp/index/plug/uninstallplug + +计划页面,/intapp/index/plan/index +列出计划,/intapp/index/plan/get +添加计划,/intapp/index/plan/add +删除计划,/intapp/index/plan/delpl +计划日志,/intapp/index/plan/log +保存计划配置,/intapp/index/plan/setconfig + +任务页面,/intapp/index/task/index +任务列表,/intapp/index/task/task +任务状态,/intapp/index/task/taskstatus + +设置页面,/intapp/index/setup/index +添加启动项,/intapp/index/setup/addstart +删除启动项,/intapp/index/setup/delstart +列出启动项,/intapp/index/setup/startlist +备份全部,/intapp/index/setup/backup +恢复全部文稿,/intapp/index/setup/recovery +下载备份,/intapp/index/setup/download +上传备份,/intapp/index/setup/postsup +保存配置信息,/intapp/index/setup/setconfig +阿里云列表,/intapp/index/setup/aliyunosslist +从阿里云备份点恢复,/intapp/index/setup/aliyunossdownload + +命令行页面,/intapp/index/socket/shell +终端列表页面,/intapp/index/socket/terminallist +命令日志页面,/intapp/index/socket/shelllog +删除令日志,/intapp/index/socket/deleteshelllog +获命令日志列表,/intapp/index/socket/getshelllog +获取终端列表,/intapp/index/socket/getterminallist +添加终端,/intapp/index/socket/addtermina +删除终端,/intapp/index/socket/delete +修改终端配置,/intapp/index/socket/update +查看ssh密码,/intapp/index/socket/getterminalpwd diff --git a/app/intapp/controller/index/setup.py b/app/intapp/controller/index/setup.py index 261071256a92687821cad5d9255b30160953a511..fd057570c22956b91abf023151ae12efd43a208e 100644 --- a/app/intapp/controller/index/setup.py +++ b/app/intapp/controller/index/setup.py @@ -1,4 +1,5 @@ from .common import * +import base64,oss2 def index(): return response.tpl() def addstart(): @@ -32,69 +33,116 @@ def startlist(): lists=yz[0] count=yz[1] data=return_list(lists,count,pagenow,pagesize) + if os.path.isfile("app/common/file/config.conf"): + data['config']=json_decode(file_get_content("app/common/file/config.conf")) + if not data['config']['aliyun']['backpath']: + data['config']['aliyun']['backpath']="kcweb" + else: + data['config']={} + return successjson(data) +def setconfig(): + "保存配置信息" + config=request.get_json() + file_set_content("app/common/file/config.conf",json_encode(config)) + return successjson() +def aliyunosslist(): + if not os.path.isfile("app/common/file/config.conf"): + return errorjson(msg="请先配置阿里云oss配置信息") + prefix=request.args.get("prefix") + if not prefix: + prefix="backups/" + data=[] + try: + fileconfig=json_decode(file_get_content("app/common/file/config.conf")) + auth = oss2.Auth(fileconfig['aliyun']['access_key'],fileconfig['aliyun']['access_key_secret']) + bucket = oss2.Bucket(auth,fileconfig['aliyun']['address'],fileconfig['aliyun']['bucket']) + # 列举fun文件夹下的文件与子文件夹名称,不列举子文件夹下的文件。 + + for obj in oss2.ObjectIterator(bucket, prefix = prefix, delimiter = '/'): + # 通过is_prefix方法判断obj是否为文件夹。 + if obj.is_prefix(): # 文件夹 + data.append({"name":obj.key.split("/")[-2],"path":obj.key,"type":"folder"}) + else: # 文件 + data.append({"name":obj.key.split("/")[-1],"path":obj.key,"type":"file"}) + except:pass return successjson(data) -def backup(): - "备份" - dirname=request.args.get("dirname") - qzx=request.args.get("qzx") - if not qzx and os.path.isfile("backup/"+dirname+"/backup.zip"): - timemy=file_get_content("backup/"+dirname+"/time") - strs="" - if timemy: - timemy=int(timemy) - ajc=times()-timemy - if(ajc>86400): - strs="上次备份时间"+str(int(ajc/86400))+"天前," - elif(ajc>3600): - strs="上次备份时间"+str(int(ajc/3600))+"小时前," - elif(ajc>60): - strs="上次备份时间"+str(int(ajc/60))+"分钟前," +def aliyunossdownload(types=""): + "从阿里云备份点恢复" + if not os.path.isfile("app/common/file/config.conf"): + return errorjson(msg="请先配置阿里云oss配置信息") + fileconfig=json_decode(file_get_content("app/common/file/config.conf")) + auth = oss2.Auth(fileconfig['aliyun']['access_key'],fileconfig['aliyun']['access_key_secret']) + bucket = oss2.Bucket(auth,fileconfig['aliyun']['address'],fileconfig['aliyun']['bucket']) + filepath=request.args.get("filepath") + if types=='mysql': #恢复mysql + pass + else: #恢复文稿 + bucket.get_object_to_file(filepath, "backup.zip") + zip.unzip_file("backup.zip","backup/app") + os.remove("backup.zip") + if os.path.exists("backup/app"): + filelist=get_file("backup/app") + for k in filelist: + if k['type']=='folder' and '__pycache__' not in k['path']: + if 'common/file' == k['path'][-11:]: + path=re.sub("backup/","",k['path']) + if os.path.exists(path): + shutil.rmtree(path) + shutil.copytree(k['path'],path) + return successjson() +def backup(types=''): + "备份全部" + filelist=get_file("app") + if os.path.exists("backup"): + shutil.rmtree("backup") + for k in filelist: + if k['type']=='folder' and '__pycache__' not in k['path']: + if 'common/file' == k['path'][-11:]: + shutil.copytree(k['path'],"backup/"+k['path']) + if types=='aliyun':#备份文件上传到阿里云oss + print("本地备份成功") + if not os.path.isfile("app/common/file/config.conf"): + print("您没有保存阿里云oss修改配置信息而无法上传") + else: + fileconfig=json_decode(file_get_content("app/common/file/config.conf")) + backpath=fileconfig['aliyun']['backpath'] + if backpath: + if backpath[:1]=='/': + backpath=backpath[1:] + if backpath[-1]=='/': + backpath=backpath[:-1] else: - strs="上次备份时间"+str(int(ajc))+"秒前," - return successjson(code=1,msg=strs+"现在备份会覆盖之前的,是否强制性备份") - if os.path.exists(dirname): - if not os.path.exists("backup/"+dirname): - os.makedirs("backup/"+dirname) - zip.packzip(dirname,"backup/"+dirname+"/backup.zip") - file_set_content("backup/"+dirname+"/time",str(times())) - return successjson() - else: - return errorjson(msg="您要备份的目录不存在") + backpath="kcweb" + auth = oss2.Auth(fileconfig['aliyun']['access_key'],fileconfig['aliyun']['access_key_secret']) + bucket = oss2.Bucket(auth,fileconfig['aliyun']['address'],fileconfig['aliyun']['bucket']) + zip.packzip("backup/app","backup/app.zip") + oss2.resumable_upload(bucket,"backups/"+backpath+"/app/"+time.strftime("%Y%m%d-%H:%M:%S",time.localtime(times()))+".zip","backup/app.zip") + filelist=[] + for obj in oss2.ObjectIterator(bucket, prefix="backups/"+backpath+"/app/"): + filelist.append(obj.key) + i=0 + while True: + if len(filelist)-i <= 5: #在阿里云保留5个备份文件 + break + bucket.delete_object(filelist[i]) + i+=1 + os.remove("backup/app.zip") + print("上传到阿里云oss成功") + if not config.app['cli']: + return successjson(msg="所有文稿备份成功") def recovery(): - "恢复" - dirname=request.args.get("dirname") - qzx=request.args.get("qzx") - if dirname: - if os.path.isfile("backup/"+dirname+"/backup.zip"): - if not qzx: - timemy=file_get_content("backup/"+dirname+"/time") - strs="" - if timemy: - timemy=int(timemy) - ajc=times()-timemy - if(ajc>86400): - strs="备份时间"+str(int(ajc/86400))+"天前," - elif(ajc>3600): - strs="备份时间"+str(int(ajc/3600))+"小时前," - elif(ajc>60): - strs="备份时间"+str(int(ajc/60))+"分钟前," - else: - strs="备份时间"+str(int(ajc))+"秒前," - return successjson(code=1,msg=strs+"是否恢复备份文件") - if not os.path.exists(dirname): - os.makedirs(dirname) - zip.unzip_file("backup/"+dirname+"/backup.zip",dirname) - return successjson() - else: - return errorjson(msg="备份文件不存在") - elif os.path.exists("backup"): - filelist=get_file("backup") + "恢复全部文稿" + if os.path.exists("backup/app"): + filelist=get_file("backup/app") for k in filelist: - if k['type']=='file' and 'backup.zip' in k['path']: - dirname=re.sub("backup/","",k['path']) - dirname=re.sub("/backup.zip","",dirname) - zip.unzip_file("backup/"+dirname+"/backup.zip",dirname) - return successjson("恢复所有") + if k['type']=='folder' and '__pycache__' not in k['path']: + if 'common/file' == k['path'][-11:]: + path=re.sub("backup/","",k['path']) + if os.path.exists(path): + shutil.rmtree(path) + shutil.copytree(k['path'],path) + # print(k['path'],path) + return successjson(msg="所有文稿恢复成功") else: return errorjson(msg="备份目录不存在") def download(name=""): @@ -110,16 +158,9 @@ def download(name=""): return "没有备份文件,请备份文件后再下载" def postsup(): "上传备份文件" - data=request.get_json() - qzx=request.args.get("qzx") - if not qzx and os.path.exists("backup"): - return successjson(code=1,msg="您服务器已经有一份备份文件,是否重新上传覆盖") - backup=data['backup'] - backup=backup.split(",") - btext=base64.b64decode(backup[1]) - f=open("backup.zip","wb") - f.write(btext) - f.close() - zip.unzip_file("backup.zip","backup") - os.remove("backup.zip") - return successjson() + if request.binary.save('file',"backup."+request.binary.filesuffix('file')): + zip.unzip_file("backup.zip","backup") + os.remove("backup.zip") + return successjson() + else: + return errorjson(msg="上传失败") diff --git a/app/intapp/controller/index/socket.py b/app/intapp/controller/index/socket.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce438dae8a647ddbedaba45271794840090d757 --- /dev/null +++ b/app/intapp/controller/index/socket.py @@ -0,0 +1,376 @@ +from .common import * +import paramiko +from urllib import parse +class sockets(kcwebsocket): + trans = {} + channel = {} + terminal={} + cmdstr="" + def __successjson(self,data=[],code=0,msg="成功"): + res={ + "code":code, + "msg":msg, + "time":int(times()), + "data":data + } + return json_encode(res) + def __errorjson(self,data=[],code=1,msg="失败",status='400 error'): + return self.__successjson(data=data,code=code,msg=msg) + def __setterminal(self,clientid,id,userinfo): + "设置终端信息" + try: + self.terminal[clientid] + except: + self.terminal[clientid]=sqlite("terminal").where("id",id).find() + self.terminal[clientid]['id']=id + self.terminal[clientid]['userinfo']=userinfo + if self.terminal[clientid]['blacklist']: #命令黑名单 + self.terminal[clientid]['blacklist']=self.terminal[clientid]['blacklist'].split(",") + else: + self.terminal[clientid]['blacklist']=[] + if self.terminal[clientid]['whitelist']: #命令白名单 + self.terminal[clientid]['whitelist']=self.terminal[clientid]['whitelist'].split(",") + else: + self.terminal[clientid]['whitelist']=[] + async def onConnect(self,clientid,params): + "#当客户端发来连接时触发的回调函数" + try: + uid=params['uid'] + userinfo=get_cache(uid) + if not userinfo: + await self.send_client(clientid,self.__errorjson(msg="您尚未登录")) + return False + del_cache(uid) + authkey=get_cache("websocket") + del_cache("websocket") + id=str(params['id']) + self.__setterminal(clientid,id,userinfo) + if params['authkey']!=authkey: + await self.send_client(clientid,self.__successjson(msg="认证失败")) + return False + else: + if self.terminal[clientid]['types']=='共享连接': + await self.joinGroup(clientid,"Group"+id) #加入组 + try: + self.trans[id] + self.channel[id] + except Exception as e: + self.trans[id] = paramiko.Transport((self.terminal[clientid]['host'],int(self.terminal[clientid]['port']))) + self.trans[id].start_client() + self.trans[id].auth_password(username=self.terminal[clientid]['username'], password=self.terminal[clientid]['password']) + self.channel[id] = self.trans[id].open_session()# 打开一个通道 + self.channel[id].settimeout(7200) + self.channel[id].get_pty()# 获取一个终端 + self.channel[id].invoke_shell()# 激活器 + self.channel[id].setblocking(False) + while True: + await asyncio.sleep(0.002) + try: + rst = self.channel[id].recv(1024) + except:pass + else: + rst = rst.decode('utf8') + if rst: + resdata=rst.split("\n") + if '\u001b[K' in rst or '\b\u001b[K' == rst or "\b \b"==rst: #删除符 + if(self.cmdstr): + self.cmdstr = self.cmdstr[:-1] + elif '\b' in rst: #多个删除符 + cmdstr=rst.split("\b") + for k in cmdstr: + if not k: + self.cmdstr = self.cmdstr[:-1] + for k in cmdstr: + if k: + self.cmdstr+=k + elif len(resdata)==1 and self.cmdstr!=rst and "\n" not in rst and '\u0007'!=rst: + self.cmdstr+=rst + try: + await self.sendToGroup("Group"+id,self.__successjson(rst)) + except: + await self.sendToGroup("Group"+id,self.__errorjson("获取执行结果失败")) + else: + await self.send_client(clientid,self.__successjson("连接成功\n当前使用的ssh服务是一个共享连接\n你发送的命令会被该组的所有人看到\n["+self.terminal[clientid]['username']+"@"+self.terminal[clientid]['host']+":"+self.terminal[clientid]['port']+" ]# ")) + else: #独立连接(为每个客户端分配一个连接) + self.trans[id+clientid] = paramiko.Transport((self.terminal[clientid]['host'],int(self.terminal[clientid]['port']))) + self.trans[id+clientid].start_client() + self.trans[id+clientid].auth_password(username=self.terminal[clientid]['username'], password=self.terminal[clientid]['password']) + self.channel[id+clientid] = self.trans[id+clientid].open_session()# 打开一个通道 + self.channel[id+clientid].settimeout(7200) + self.channel[id+clientid].get_pty()# 获取一个终端 + self.channel[id+clientid].invoke_shell()# 激活器 + self.channel[id+clientid].setblocking(False) + while True: + await asyncio.sleep(0.002) + try: + rst = self.channel[id+clientid].recv(1024) + except:pass + else: + rst = rst.decode('utf8') + if rst: + resdata=rst.split("\n") + if '\u001b[K' in rst or '\b\u001b[K' == rst or "\b \b"==rst: #删除符 + if(self.cmdstr): + self.cmdstr = self.cmdstr[:-1] + elif '\b' in rst: #多个删除符 + cmdstr=rst.split("\b") + for k in cmdstr: + if not k: + self.cmdstr = self.cmdstr[:-1] + for k in cmdstr: + if k: + self.cmdstr+=k + elif len(resdata)==1 and self.cmdstr!=rst and "\n" not in rst and '\u0007'!=rst: + self.cmdstr+=rst + try: + await self.send_client(clientid,self.__successjson(rst)) + except: + await self.send_client(clientid,self.__errorjson("获取执行结果失败")) + await self.send_client(clientid,self.__successjson("连接成功")) + return True + except Exception as e: + if config.app['app_debug']: + print("连接失败error",clientid,traceback.format_exc()) + await self.send_client(clientid,self.__errorjson(msg="认证失败error"+str(e))) + return False + async def onMessage(self,clientid,recv_text): + "当客户端发来数据时触发的回调函数" + await asyncio.sleep(0.001) + id=self.terminal[clientid]['id'] + try: + data=json_decode(recv_text) + except: + await self.send_client(clientid,self.__errorjson(msg="必须是标准json字符串格式")) + else: + try: + types=data['types'] + except: + await self.send_client(clientid,self.__errorjson(msg="消息格式错误,缺少types")) + else: + if types=='cmd': + cmd=parse.unquote(data['cmd']) + # try: + # self.cmdstr=parse.unquote(data['cmdstr']) + # except KeyError: + # self.cmdstr='' + zxstatus=False + errmsg='' + # if '\u001b[A'==cmd or '\u001b[B'==cmd: #禁止方向键上下 + # zxstatus=False + # errmsg="方向键上和方向键下被禁止" + # else: + # zxstatus=True + if '\u001b[D'==cmd or '\u001b[C'==cmd: #禁止方向键左右 + errmsg="方向键左和方向键右被禁止" + else: + if self.cmdstr: + if len(self.cmdstr) > 2000: + errmsg="该命令不允许执行,因为您的命令字符超过2000" + elif self.terminal[clientid]['whitelist']: #命令白名单 + for k in self.terminal[clientid]['whitelist']: + if k == self.cmdstr: + zxstatus=True + errmsg="该命令不允许执行,因为您的命令不在白名单中" + elif self.terminal[clientid]['blacklist']: #命令黑名单 + zxstatus=True + for k in self.terminal[clientid]['blacklist']: + if k in self.cmdstr: + zxstatus=False + errmsg="该命令不允许执行,因为您的命令出现了被禁用的命令‘"+str(k)+"’" + break + else: + zxstatus=True + if zxstatus: + if self.terminal[clientid]['types']=='共享连接': + if ('\n' in cmd or '\r' in cmd) and self.cmdstr: + try: + sqlite("shelllog",model_intapp_index_path).insert({"uid":self.terminal[clientid]['userinfo']['id'],"icon":self.terminal[clientid]['userinfo']['icon'],"name":self.terminal[clientid]['userinfo']['name'],"host":self.terminal[clientid]['host'],"port":str(self.terminal[clientid]['port']),"username":self.terminal[clientid]['username'],"password":self.terminal[clientid]['password'],"cmd":self.cmdstr,"addtime":times()}) + except: + print("该命令未被执行,因为无法记录日志",traceback.format_exc()) + self.channel[id].send("\n") + await self.send_client(clientid,self.__errorjson(msg="该命令未被执行,因为无法记录日志")) + else: + self.channel[id].send(cmd) + self.cmdstr='' + else: + self.channel[id].send(cmd) + # await self.send_client(clientid,self.__errorjson(cmd,msg="你的命令是")) + else: #独立连接 + if ('\n' in cmd or '\r' in cmd) and self.cmdstr: + try: + sqlite("shelllog",model_intapp_index_path).insert({"uid":self.terminal[clientid]['userinfo']['id'],"icon":self.terminal[clientid]['userinfo']['icon'],"name":self.terminal[clientid]['userinfo']['name'],"host":self.terminal[clientid]['host'],"port":str(self.terminal[clientid]['port']),"username":self.terminal[clientid]['username'],"password":self.terminal[clientid]['password'],"cmd":self.cmdstr,"addtime":times()}) + except: + print("该命令未被执行,因为无法记录日志",traceback.format_exc()) + self.channel[id].send("\n") + await self.send_client(clientid,self.__errorjson(msg="该命令未被执行,因为无法记录日志")) + else: + self.channel[id+clientid].send(cmd) + self.cmdstr='' + else: + self.channel[id+clientid].send(cmd) + else: + if self.terminal[clientid]['types']=='共享连接': + self.channel[id].send("\b") + else:#独立连接 + self.channel[id+clientid].send("\b") + await self.send_client(clientid,self.__errorjson(msg=errmsg)) + else: + await self.send_client(clientid,self.__errorjson(msg="暂不支持types为"+str(types)+"的消息")) + async def onClose(self,clientid): + "客户端与服务端连接断开时触发的回调函数" + id=self.terminal[clientid]['id'] + if self.terminal[clientid]['types']=='共享连接': + pass + else:#独立连接 + self.channel[id+clientid].close() + self.trans[id+clientid].close() + del self.channel[id+clientid] + del self.trans[id+clientid] + await self.sendToGroup("Group"+id,clientid+",已退出") #给当前组户发送消息 + print("onClose",clientid) +def start(): + if config.app['cli']: #cli模式下运行 + socket=sockets() + socket.start(ip='0.0.0.0',port='39020') + +def shell(id=''): + "命令行页面" + id=str(id) + if not id: + item=sqlite("terminal").where("host","127.0.0.1").field("id").find() + if not item: + return "请先配置终端" + else: + id=item['id'] + authkey=md5(randoms()) + set_cache("websocket",authkey,10) + set_cache(str(G.userinfo['id']),G.userinfo,10) + return response.tpl(WebSocketaddress="ws://"+request.HEADER.HTTP_HOST()+":39020?authkey="+authkey+"&id="+id+"&uid="+str(G.userinfo['id'])) +def terminallist(): + "终端列表页面" + return response.tpl() +def shelllog(): + "命令日志页面" + return response.tpl() +def deleteshelllog(): + "删除令日志" + try: + id=request.get_json() + sqlite("shelllog").where('id','in',id).delete() + except: + return errorjson(msg="失败") + else: + return successjson() +def getshelllog(id=0): + "获命令日志列表" + if id: + return successjson(sqlite("shelllog").field('id,uid,icon,name,host,port,username,cmd,addtime').find(id)) + where=None + kw=request.args.get('kw') + pagenow=request.args.get('pagenow') + pagesize=request.args.get('pagesize') + if kw: + where=[("uid","like","%"+str(kw)+"%"),'or',("name","like","%"+str(kw)+"%"),'or',("cmd","like","%"+str(kw)+"%"),'or',("host","like","%"+str(kw)+"%")] + if not pagenow: + pagenow=1 + else: + pagenow=int(pagenow) + if not pagesize: + pagesize=10 + else: + pagesize=int(pagesize) + lists=sqlite("shelllog").field("id,uid,icon,name,host,port,username,cmd,addtime").order("id desc").where(where).page(pagenow,pagesize).select() + count=sqlite("shelllog").where(where).count() + data=return_list(lists,count,pagenow,pagesize) + return successjson(data) +def getterminalpwd(id=0,tab='terminal'): + "获取终端密码" + if id: + return successjson(sqlite(tab).field('password').find(id)) + else: + return errorjson(msg="id不能为空") +def getterminallist(id=0): + "获取终端列表" + if id: + return successjson(sqlite("terminal").field('id,icon,title,host,port,types,username,blacklist,whitelist,addtime,updtime').find(id)) + where=None + kw=request.args.get('kw') + pagenow=request.args.get('pagenow') + pagesize=request.args.get('pagesize') + if kw: + where=[("title","like","%"+str(kw)+"%"),'or',("host","like","%"+str(kw)+"%")] + if not pagenow: + pagenow=1 + else: + pagenow=int(pagenow) + if not pagesize: + pagesize=10 + else: + pagesize=int(pagesize) + lists=sqlite("terminal").field("id,title,icon,host,port,types,username,addtime,blacklist,whitelist,updtime").where(where).page(pagenow,pagesize).select() + count=sqlite("terminal").where(where).count() + data=return_list(lists,count,pagenow,pagesize) + return successjson(data) +def addtermina(): + "添加终端" + data=request.get_json() + data['addtime']=times() + data['updtime']=times() + if sqlite("terminal").where("host",data['host']).count(): + return errorjson(msg="服务器"+data['host']+"已在列表中,请务重复添加") + if data['host'] and data['port'] and data['username'] and len(data['password'])>5: + try: + trans = paramiko.Transport((data['host'],int(data['port']))) + trans.start_client() + trans.auth_password(username=data['username'], password=data['password']) + channel = trans.open_session() + channel.settimeout(7200) + channel.get_pty() + channel.invoke_shell() + except: + return errorjson(msg="无法连接到ssh服务器,请检查配置是否正确") + else: + trans.close() + channel.close() + sqlite("terminal").insert(data) + else: + return errorjson(msg="配置信息格式错误") + return successjson() +def delete(): + "删除终端" + try: + id=request.get_json() + sqlite("terminal").where('id','in',id).delete() + except: + return errorjson(msg="失败") + else: + return successjson() +def update(id=0): + "修改终端配置" + data=request.get_json() + if not id: + id=data['id'] + try: + data['updtime']=times() + except:pass + else: + t=sqlite("terminal").where("id",id).find() + if len(data['password'])>5 and (t['host']!=data['host'] or t['port']!=data['port'] or t['username']!=data['username'] or t['password']!=data['password']): + try: + trans = paramiko.Transport((data['host'],int(data['port']))) + trans.start_client() + trans.auth_password(username=data['username'], password=data['password']) + channel = trans.open_session() + channel.settimeout(7200) + channel.get_pty() + channel.invoke_shell() + except: + return errorjson(msg="无法连接到ssh服务器,请检查配置是否正确") + else: + trans.close() + channel.close() + sqlite("terminal").where("id",id).update(data) + else: + del data['password'] + sqlite("terminal").where("id",id).update(data) + return successjson() \ No newline at end of file diff --git a/app/intapp/controller/index/tpl/admin/index.html b/app/intapp/controller/index/tpl/admin/index.html index 4ab6192b78a07c831d087e6f8e6f6f6b6c4f8ec8..e87ff5d7b91e8383d34196185c8361628860819f 100644 --- a/app/intapp/controller/index/tpl/admin/index.html +++ b/app/intapp/controller/index/tpl/admin/index.html @@ -26,8 +26,8 @@ -
-
+
+
   @@ -40,6 +40,11 @@ + + + @@ -84,9 +89,9 @@ - + @@ -150,7 +155,7 @@ var vm = new Vue({ openurl:function(url){ layer.open({ type: 2,title: false,shadeClose: true,shade: 0.8, - area: ['60%', '90%'],content:url + area: ['80%', '90%'],content:url }); }, getlist:function(){ diff --git a/app/intapp/controller/index/tpl/admin/role.html b/app/intapp/controller/index/tpl/admin/role.html index ca4bd6932cb7c173cf3d9a9f1c8fad03e8bf28c7..30637bd14ce4ff1d656c40409f063cf41d9527a3 100644 --- a/app/intapp/controller/index/tpl/admin/role.html +++ b/app/intapp/controller/index/tpl/admin/role.html @@ -67,26 +67,26 @@ - + width="98%"> + -
+
- {{item1.name}} + {{item1.name}}
- 取 消 - 修改 - 添加 + 取 消 + 修改 + 添加
@@ -127,7 +127,13 @@ var vm = new Vue({ self.get("/intapp/index/admin/getrolelist",{'kw':self.kw,'pagesize':self.data.pagesize,'pagenow':self.data.pagenow},'获取中...').then(function(res){ self.data=res.data self.get("/intapp/index/admin/getpluglist").then(function(res){ - self.getpluglist=res.data + self.getpluglist=[] + for(var i=0;i -
- +
+ @@ -92,8 +88,9 @@
-
+
+
@@ -101,163 +98,164 @@
- - - \ No newline at end of file + } +}) + + \ No newline at end of file diff --git a/app/intapp/controller/index/tpl/index/index.html b/app/intapp/controller/index/tpl/index/index.html index ee605a0afe13619a40ddf8bd978592ff67cf7b31..3b21db2938c0d57ae8f936ae7668282d23417d80 100644 --- a/app/intapp/controller/index/tpl/index/index.html +++ b/app/intapp/controller/index/tpl/index/index.html @@ -2,10 +2,12 @@ -kcwebplus +${userinfo['nickname']}-kcweb云管 + + @@ -21,18 +23,11 @@ @@ -106,9 +101,12 @@ - - - + + + @@ -124,17 +122,43 @@ - + + + + + + ${userinfo['name']} + + + + 设置登录密码 + + + + 重启 + + + + 退出系统 + + - +
@@ -152,24 +176,24 @@
-
+
- +
- +
-
Footer
+ - -
+ +
运行中 应用重启中 @@ -219,7 +243,7 @@ } setTimeout(function(){ self.checkserver(types) - },5000) + },1000) }); }, reboot:function(){ @@ -230,7 +254,7 @@ type: 'warning' }).then(function(){ self.netstatus=3 - self.post("/intapp/index/index/shell",{shell:'reboot'}).then(function(res){ + self.post("/intapp/index/index/reboot/reboot",{shell:'reboot'}).then(function(res){ // self.netstatus=1 setTimeout(function(){ self.reboot() @@ -239,7 +263,7 @@ self.netstatus=3 setTimeout(function(){ self.checkserver('reboot') - },5000) + },60000) }); }).catch(function(){}); }, @@ -251,7 +275,7 @@ type: 'warning' }).then(function(){ self.netstatus=2 - self.post("/intapp/index/index/shell",{shell:'bash server.sh'}).then(function(res){ + self.post("/intapp/index/index/reboot/app",{shell:'bash server.sh'}).then(function(res){ // self.netstatus=1 setTimeout(function(){ self.rekcwebplus() @@ -260,10 +284,28 @@ self.netstatus=2 setTimeout(function(){ self.checkserver('rekcwebplus') - },5000) + },3000) }); }).catch(function(){}); }, + setmypwd:function(){ + self=this + self.$prompt('请输新密码', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + inputValidator:function(value){ + if(value.length>=6&&value.length<=10){ + return true + }else{ + return "请输入6到10位字符" + } + } + }).then(function(value){ + self.post('/intapp/index/admin/setmypwd',{password:md5('kcw'+value.value)}).then(function(res){ + self.$message({type: 'success',message: '您的设置的密码是: ' + value.value}); + }) + }).catch(function(){}); + }, isIE:function() { if(!!window.ActiveXObject || "ActiveXObject" in window){ return true; diff --git a/app/intapp/controller/index/tpl/plan/index.html b/app/intapp/controller/index/tpl/plan/index.html index 8866c9aec8bfa5b7ac8bc329542392faea1f409b..117ebe3c418c5f9ff0b111d667c422da2fc39c2b 100644 --- a/app/intapp/controller/index/tpl/plan/index.html +++ b/app/intapp/controller/index/tpl/plan/index.html @@ -40,8 +40,9 @@ - + + @@ -50,6 +51,18 @@ + + +
同时备份到阿里云对象存储OSS + 需提前在系统配置中保存阿里云配置信息,已配置请忽略 +
+
+ + +
同时备份到阿里云对象存储OSS + 需提前在系统配置中保存阿里云配置信息,已配置请忽略 +
+
@@ -64,21 +77,6 @@
-
- -
- -
同时备份到阿里云对象存储OSS
- - - > - - 保存 -
- -
@@ -213,16 +211,18 @@ 每隔 {{scope.row.day}}天 执行命令:{{scope.row.value}} + 备份系统文稿数据,同时上传到阿里云对象存储oss 访问URL:{{scope.row.value}} 重启php-fpm,版本:{{scope.row.value}} - 备份mysql数据库 {{scope.row.value}},同时上传到阿里云对象存储oss + 备份mysql5.7数据库 ,同时上传到阿里云对象存储oss @@ -291,7 +291,7 @@ var vm = new Vue({ }) }) }, - setconfig:function(path=""){ + setconfig:function(path){ self=this self.post("/intapp/index/plan/setconfig/set/"+path,self.strconfig,'保存中...').then(function(res){ self.$message({type: 'success',message:res.msg}); @@ -346,7 +346,34 @@ var vm = new Vue({ handleCurrentChange:function(val) { self.data.pagenow=val self.obtain() - } + }, + change:function(key){ + if(key=='docgao'){ + this.form.value="python3.6 server.py intapp/index/setup/backup --cli" + this.form.name="备份文稿" + }else if(key=='backupmysql'){ + this.form.value="python3.6 server.py /intapp/soft/mysql/backups/whole/bankup_all_databases --cli" + this.form.name="备份mysql5.7" + }else{ + this.form.value="" + } + this.form.oss=false + }, + OSSchange:function(key){ + if(this.form.types=='docgao'){ + if(key){ + this.form.value="python3.6 server.py intapp/index/setup/backup/aliyun --cli" + }else{ + this.form.value="python3.6 server.py intapp/index/setup/backup --cli" + } + }else if(this.form.types=='backupmysql'){ + if(key){ + this.form.value="python3.6 server.py /intapp/soft/mysql/backups/whole/bankup_all_databases/1 --cli" + }else{ + this.form.value="python3.6 server.py /intapp/soft/mysql/backups/whole/bankup_all_databases --cli" + } + } + }, } }) diff --git a/app/intapp/controller/index/tpl/setup/index.html b/app/intapp/controller/index/tpl/setup/index.html index 8bf3c8c8bd124a5790336a174be1148a0a8b762a..2355ec3931876e83cb817e83db2c1c4d6c417326 100644 --- a/app/intapp/controller/index/tpl/setup/index.html +++ b/app/intapp/controller/index/tpl/setup/index.html @@ -64,18 +64,81 @@ -
-
- 恢复所有备份 +
+
+ 备份所有 + 恢复所有 + 下载备份文件
-
- 下载备份文件 - - - 点击上传备份文件 - +
+ + +
将备份文件拖到此处,或点击上传
+
只能上传压缩包zip文件
+
+
+

阿里云oss文稿备份点

+ + + + + + + + +
+
+

阿里云oss mysql备份点

+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + 保存 + + @@ -103,17 +166,10 @@ + + + + + + + + + +
+
+
+ + + + \ No newline at end of file diff --git a/app/intapp/controller/index/tpl/socket/shelllog.html b/app/intapp/controller/index/tpl/socket/shelllog.html new file mode 100644 index 0000000000000000000000000000000000000000..5a0c1445b0d43c17f2d4ab756818ce39f2f8be78 --- /dev/null +++ b/app/intapp/controller/index/tpl/socket/shelllog.html @@ -0,0 +1,154 @@ + + + + +kcwebplus + + + + + + + + + + + + + + + + + + + +
+
+    + + + 搜索 + + 终端管理 +
+
+ + + + + + + + + + + + + + + + + + + +
+    删除选中  + + +
+
+
+ + + diff --git a/app/intapp/controller/index/tpl/admin/role copy.html b/app/intapp/controller/index/tpl/socket/terminallist.html similarity index 44% rename from app/intapp/controller/index/tpl/admin/role copy.html rename to app/intapp/controller/index/tpl/socket/terminallist.html index 1e56cc7dfcb9a05a227ff63f6d18e3bbc10887ec..5e6e6016cdbae5fc39b08b90882c27f81687c1e6 100644 --- a/app/intapp/controller/index/tpl/admin/role copy.html +++ b/app/intapp/controller/index/tpl/socket/terminallist.html @@ -1,6 +1,5 @@ - kcwebplus @@ -27,31 +26,48 @@ -
-
+
+
   - + 搜索 - 添加角色 + 添加服务器 + 命令日志
- - - - - + + + + + + + + + + + + + + - + @@ -65,29 +81,48 @@
- - - - - - - -
- - - {{city.name}} - - -
-
- - 取 消 - 修改 - 添加 - + :title="terminaldialog.title" + :visible.sync="terminaldialog.status" + width="500px"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + 优先级大于命令黑名单,不填写表示不启用,否则在终端完整匹配该命令才能被执行 + + + + 优先级小于命令白名单,不填写表示不启用,否则在终端输入的命令只要包含该命令将会被阻止 + + + + 取 消 + 修改 + 添加 +
@@ -96,7 +131,7 @@ var vm = new Vue({ el: '#app', data: { winheight:document.documentElement.clientHeight,winwidth:document.documentElement.clientWidth, - admin:{title:'',icon:'',describes:'',roleroute:[]},admindialog:{status:false,title:'添加角色'},admindialog1:{status:false,title:'设置权限'}, + terminal:{title:'本机终端',icon:'https://img.kwebapp.cn/icon/terminal.png',host:'127.0.0.1',username:'root',port:'22',password:'',blacklist:'',whitelist:'',types:'共享连接'},terminaldialog:{status:false,title:'添加服务器'}, kw:'', data:{ 'pagesize':20, @@ -104,23 +139,6 @@ var vm = new Vue({ 'pagenow':1, 'lists':[] },Change:[], - check:{ - // cityOptions:['模块/插件市场', '管理员', '计划任务', '任务'], - checkAll: false, - checkedCities: [], - cities: [ - {name:'首页',value:'/intapp/index/index'}, - {name:'管理员',value:'/intapp/index/admin'}, - {name:'计划任务',value:'/intapp/index/plan'}, - {name:'任务队列',value:'/intapp/index/task'}, - {name:'系统配置',value:'/intapp/index/setup'}, - {name:'模块管理',value:'/intapp/index/modular'}, - {name:'插件管理',value:'/intapp/index/plug'}, - {name:'导航管理',value:'/intapp/index/menu'}, - ], - isIndeterminate: true - } - }, mounted:function(){ self=this @@ -131,50 +149,39 @@ var vm = new Vue({ self.getlist() }, methods: { - test:function(){ - // console.log(this.check.checkedCities) - }, - handleCheckedCitiesChange(value) { - let checkedCount = value.length; - this.check.checkAll = checkedCount === this.check.cities.length; - this.check.isIndeterminate = checkedCount > 0 && checkedCount < this.check.cities.length; + hrefs:function(url){ + window.location.href=url; }, openurl:function(url){ layer.open({ type: 2,title: false,shadeClose: true,shade: 0.8, - area: ['60%', '60%'],content:url + area: ['80%', '90%'],content:url }); }, getlist:function(){ self=this - self.get("/intapp/index/admin/getrolelist",{'kw':self.kw,'pagesize':self.data.pagesize,'pagenow':self.data.pagenow},'获取中...').then(function(res){ + self.get("/intapp/index/socket/getterminallist",{'kw':self.kw,'pagesize':self.data.pagesize,'pagenow':self.data.pagenow},'获取中...').then(function(res){ self.data=res.data - self.get("/intapp/index/plug/getpluglist").then(function(res){ - self.check.cities=[ - {name:'首页',value:'/intapp/index/index'}, - {name:'管理员',value:'/intapp/index/admin'}, - {name:'计划任务',value:'/intapp/index/plan'}, - {name:'任务队列',value:'/intapp/index/task'}, - {name:'系统配置',value:'/intapp/index/setup'}, - {name:'模块管理',value:'/intapp/index/modular'}, - {name:'插件管理',value:'/intapp/index/plug'}, - {name:'导航管理',value:'/intapp/index/menu'}, - ] - for(var i=0;i=3.10.0','Mako>=1.1.2','requests>=2.23.0','six>=1.12.0','watchdog>=0.9.0'], #第三方包 + install_requires = ['pymongo>=3.10.0','Mako>=1.1.2','requests>=2.23.0','six>=1.12.0','watchdog>=0.9.0','websockets==8.1'], #第三方包 package_data = { '': ['*.html', '*.js','*.css','*.jpg','*.png','*.gif'], } diff --git a/kcweb/tpl/err.html b/kcweb/tpl/err.html new file mode 100644 index 0000000000000000000000000000000000000000..2cfaad455dbc4e36dfdb29bfdab6bf9fcd5ce0cd --- /dev/null +++ b/kcweb/tpl/err.html @@ -0,0 +1,55 @@ + + + + +${title} + + + + + + + + + + + + + +
+
+ +
+ + + + ${content}
+
+ +
+ + + diff --git a/kcweb/utill/db/mysql.py b/kcweb/utill/db/mysql.py index 3235a9a3082e664870f7ca6ee0efd4c09d634633..52f1f8dfea7884590590a1cc68c621136a3c28cc 100644 --- a/kcweb/utill/db/mysql.py +++ b/kcweb/utill/db/mysql.py @@ -8,6 +8,7 @@ class mysql: """数据库实例""" __config=dbconfig __conn={} #数据库链接对象 + __connlists=[] #短连接列表 用于关闭 __cursor=None #游标对象 __errorcount=dbconfig['break'] #允许最大链接错误次数 __errorcounts=0 #默认链接错误次数 @@ -17,21 +18,43 @@ class mysql: __masteridentifier='' # 主服务器标识 __slaveidentifier='' # 从服务器标识 def __del__(self): - if not self.__config['pattern'] and self.__conn: - if isinstance(self.__conn,dict): + pass + def close(self): + "关闭连接,该方法无需理会,框架自动完成" + if not self.__config['pattern'] and mysql.__conn: + if self.__connlists: + for k in self.__connlists: + try: + mysql.__conn[k].close() + except Exception as e: + if self.__config['debug']: + print("mysql短连接关闭失败",str(e),k) + else: + if self.__config['debug']: + print("mysql短连接关闭成功",k) + mysql.__connlists=[] + mysql.__conn={} + elif isinstance(mysql.__conn,dict): i=0 for thost in self.__config['host']: identifier=thost+str(self.__config['port'][i])+self.__config['user'][i]+self.__config['password'][i]+self.__config['db'][i] - for k in self.__conn[identifier]: + print(mysql.__conn) + for k in mysql.__conn[identifier]: try: k['obj'].close() + if self.__config['debug']: + print("mysql短连接已关闭",k) except Exception as e: - print(k,"连接关闭失败",str(e)) + if self.__config['debug']: + print("mysql短连接关闭失败",str(e),k) i+=1 - self.__conn={} - elif isinstance(self.__conn,object): - self.__conn.close() - self.__conn={} + mysql.__conn={} + self.__connlists=[] + elif isinstance(mysql.__conn,object): + mysql.__conn[self.__masteridentifier].close() + mysql.__conn={} + if self.__config['debug']: + print("mysql短连接已关闭",mysql.__conn) __dbcount=1 def __setdbcount(self): "设置数据库配置总数量" @@ -61,10 +84,10 @@ class mysql: try: k['obj'].close() if self.__config['debug']: - print(k,"关闭成功") + print("mysql长连接关闭成功",k) except Exception as e: if self.__config['debug']: - print(k,"连接关闭失败",str(e)) + print(k,"mysql长连接关闭失败",str(e)) mysql.__conn[identifier]=[] __dbobjident=None #集中式(单一服务器)并且长连接模式下随机服务器链接标识 和 分布式(主从服务器)模式下随机服务器链接标识 def __connects(self,typess="DQL"): @@ -88,7 +111,14 @@ class mysql: if self.__config['debug']: print("第%s次创建数据库链接对象,长连接模式" % (self.__errorcounts+1)) else: - mysql.__conn=connect(host=self.__config['host'][0], port=self.__config['port'][0], user=self.__config['user'][0], password=self.__config['password'][0], db=self.__config['db'][0], charset=self.__config['charset']) + self.__masteridentifier=self.__config['host'][0]+str(self.__config['port'][0])+self.__config['user'][0]+self.__config['password'][0]+self.__config['db'][0] # 服务器标识 + try: + mysql.__conn[self.__masteridentifier] + except KeyError: # 铺获未知异常 + mysql.__conn[self.__masteridentifier]=connect(host=self.__config['host'][0], port=self.__config['port'][0], user=self.__config['user'][0], password=self.__config['password'][0], db=self.__config['db'][0], charset=self.__config['charset']) + self.__connlists.append(self.__masteridentifier) + if self.__config['debug']: + print("mysql短连接已创建",self.__masteridentifier,mysql.__conn[self.__masteridentifier]) elif self.__config['deploy']==1: # 分布式(主从服务器) if self.__config['pattern']: # 长连接情况下 j=0 @@ -114,7 +144,7 @@ class mysql: j=j+1 mysql.__conn[self.__masteridentifier]=masterlistsdb if self.__config['debug']: - print("次创建数据库链接对象,长连接模式(主)" % (self.__errorcounts+1)) + print("%d次创建数据库链接对象,长连接模式(主)" % (self.__errorcounts+1)) if self.__slaveidentifier not in mysql.__conn or len(mysql.__conn[self.__slaveidentifier]) log 2>&1 & -sleep 2 -nohup ./server --h 0.0.0.0 --p 39002 --w 2 start > log 2>&1 & -nohup ./server --h 0.0.0.0 --p 39003 --w 2 start > log 2>&1 & \ No newline at end of file +nohup ./server --h 0.0.0.0 --p 39001 --w 1 start > server.log 2>&1 & +nohup ./server --h 0.0.0.0 --p 39002 --w 2 start > server.log 2>&1 & +nohup ./server --h 0.0.0.0 --p 39003 --w 2 start > server.log 2>&1 & +nohup python3.6kcw_plus server.py intapp/index/pub/clistartplan --cli > server.log 2>&1 & +nohup python3.6kcw_plus server.py intapp/index/socket/start --cli > server.log 2>&1 & \ No newline at end of file