InPanel 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. inpanel/__init__.py +29 -0
  2. inpanel/acme.py +289 -0
  3. inpanel/aliyuncs.py +269 -0
  4. inpanel/app.py +514 -0
  5. inpanel/apt.py +223 -0
  6. inpanel/base.py +200 -0
  7. inpanel/config.py +139 -0
  8. inpanel/data/config.ini +29 -0
  9. inpanel/data/history +5 -0
  10. inpanel/data/runlogs.ini +12 -0
  11. inpanel/data/update_info.ini +4 -0
  12. inpanel/dnf.py +225 -0
  13. inpanel/lib/__init__.py +9 -0
  14. inpanel/lib/async_process.py +98 -0
  15. inpanel/lib/filelock.py +82 -0
  16. inpanel/lib/magic.py +233 -0
  17. inpanel/lib/pxssh.py +310 -0
  18. inpanel/lib/pyDes.py +867 -0
  19. inpanel/mod/__init__.py +0 -0
  20. inpanel/mod/apt.py +316 -0
  21. inpanel/mod/certificate.py +268 -0
  22. inpanel/mod/config.py +372 -0
  23. inpanel/mod/cron.py +315 -0
  24. inpanel/mod/disk.py +322 -0
  25. inpanel/mod/dnf.py +239 -0
  26. inpanel/mod/file.py +722 -0
  27. inpanel/mod/firewall.py +199 -0
  28. inpanel/mod/firewall_base.py +137 -0
  29. inpanel/mod/firewall_firewalld.py +170 -0
  30. inpanel/mod/firewall_iptables.py +176 -0
  31. inpanel/mod/firewall_macos.py +213 -0
  32. inpanel/mod/firewall_ufw.py +128 -0
  33. inpanel/mod/ftp.py +231 -0
  34. inpanel/mod/httpd.py +1205 -0
  35. inpanel/mod/lighttpd.py +26 -0
  36. inpanel/mod/login.py +98 -0
  37. inpanel/mod/mysql.py +577 -0
  38. inpanel/mod/named.py +26 -0
  39. inpanel/mod/nginx.py +2327 -0
  40. inpanel/mod/package.py +33 -0
  41. inpanel/mod/package_apt.py +62 -0
  42. inpanel/mod/package_base.py +77 -0
  43. inpanel/mod/package_dnf.py +62 -0
  44. inpanel/mod/package_map.py +366 -0
  45. inpanel/mod/package_yum.py +64 -0
  46. inpanel/mod/php.py +289 -0
  47. inpanel/mod/process.py +291 -0
  48. inpanel/mod/proftpd.py +26 -0
  49. inpanel/mod/pureftpd.py +26 -0
  50. inpanel/mod/query.py +108 -0
  51. inpanel/mod/server.py +1319 -0
  52. inpanel/mod/service.py +262 -0
  53. inpanel/mod/setting.py +228 -0
  54. inpanel/mod/shell.py +78 -0
  55. inpanel/mod/ssh.py +280 -0
  56. inpanel/mod/system.py +220 -0
  57. inpanel/mod/task.py +1337 -0
  58. inpanel/mod/ufw.py +14 -0
  59. inpanel/mod/user.py +306 -0
  60. inpanel/mod/vsftpd.py +67 -0
  61. inpanel/mod/yum.py +243 -0
  62. inpanel/plugins/__init__.py +10 -0
  63. inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  64. inpanel/plugins/acme/static/index.html +10 -0
  65. inpanel/plugins/acme/static/index.js +13 -0
  66. inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
  67. inpanel/plugins/shadowsocks-libev/info.py +73 -0
  68. inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
  69. inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
  70. inpanel/public/css/global.css +138 -0
  71. inpanel/public/favicon.ico +0 -0
  72. inpanel/public/images/alipay.jpg +0 -0
  73. inpanel/public/images/loading.gif +0 -0
  74. inpanel/public/images/weixin.jpg +0 -0
  75. inpanel/public/index.html +94 -0
  76. inpanel/public/js/controllers/controllers.js +566 -0
  77. inpanel/public/js/controllers/database.js +590 -0
  78. inpanel/public/js/controllers/ecs.js +624 -0
  79. inpanel/public/js/controllers/file.js +940 -0
  80. inpanel/public/js/controllers/plugins.js +105 -0
  81. inpanel/public/js/controllers/service.js +1460 -0
  82. inpanel/public/js/controllers/site.js +1241 -0
  83. inpanel/public/js/controllers/utils.js +2319 -0
  84. inpanel/public/js/core.js +221 -0
  85. inpanel/public/js/directives.js +784 -0
  86. inpanel/public/js/filters.js +395 -0
  87. inpanel/public/js/services.js +257 -0
  88. inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
  89. inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
  90. inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
  91. inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
  92. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
  93. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
  94. inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
  95. inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
  96. inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
  97. inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
  98. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  99. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  100. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  101. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  102. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  103. inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
  104. inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
  105. inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
  106. inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
  107. inpanel/public/lib/bootstrap/js/npm.js +13 -0
  108. inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
  109. inpanel/public/lib/codemirror/codemirror.css +173 -0
  110. inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
  111. inpanel/public/lib/md5/md5.js +379 -0
  112. inpanel/public/lib/md5/md5.min.js +1 -0
  113. inpanel/public/partials/database/index.html +172 -0
  114. inpanel/public/partials/database/mysql/dbedit.html +462 -0
  115. inpanel/public/partials/database/mysql/dbnew.html +348 -0
  116. inpanel/public/partials/database/mysql/useredit.html +310 -0
  117. inpanel/public/partials/database/mysql/usernew.html +97 -0
  118. inpanel/public/partials/directives/message.html +20 -0
  119. inpanel/public/partials/directives/navbar.html +25 -0
  120. inpanel/public/partials/directives/selector.html +57 -0
  121. inpanel/public/partials/directives/srvbase.html +72 -0
  122. inpanel/public/partials/directives/srvext.html +34 -0
  123. inpanel/public/partials/directives/srvfile.html +23 -0
  124. inpanel/public/partials/directives/srvinstall.html +34 -0
  125. inpanel/public/partials/directives/srvlog.html +23 -0
  126. inpanel/public/partials/directives/srvminiop.html +28 -0
  127. inpanel/public/partials/directives/srvuninstall.html +38 -0
  128. inpanel/public/partials/directives/srvupdate.html +30 -0
  129. inpanel/public/partials/ecs/account.html +156 -0
  130. inpanel/public/partials/ecs/ecs.html +198 -0
  131. inpanel/public/partials/ecs/index.html +40 -0
  132. inpanel/public/partials/ecs/setings.html +459 -0
  133. inpanel/public/partials/file/file.html +501 -0
  134. inpanel/public/partials/file/trash.html +95 -0
  135. inpanel/public/partials/ftp.html +36 -0
  136. inpanel/public/partials/log.html +83 -0
  137. inpanel/public/partials/login.html +79 -0
  138. inpanel/public/partials/logout.html +22 -0
  139. inpanel/public/partials/main.html +534 -0
  140. inpanel/public/partials/plugins/index.html +55 -0
  141. inpanel/public/partials/plugins/plugins.html +13 -0
  142. inpanel/public/partials/secure.html +13 -0
  143. inpanel/public/partials/service/apache.html +147 -0
  144. inpanel/public/partials/service/cron.html +71 -0
  145. inpanel/public/partials/service/index.html +293 -0
  146. inpanel/public/partials/service/iptables.html +40 -0
  147. inpanel/public/partials/service/lighttpd.html +63 -0
  148. inpanel/public/partials/service/mariadb.html +113 -0
  149. inpanel/public/partials/service/memcache.html +36 -0
  150. inpanel/public/partials/service/minio.html +44 -0
  151. inpanel/public/partials/service/mongodb.html +44 -0
  152. inpanel/public/partials/service/mysql.html +113 -0
  153. inpanel/public/partials/service/named.html +61 -0
  154. inpanel/public/partials/service/nginx.html +296 -0
  155. inpanel/public/partials/service/ntp.html +40 -0
  156. inpanel/public/partials/service/php.html +250 -0
  157. inpanel/public/partials/service/proftpd.html +59 -0
  158. inpanel/public/partials/service/pureftpd.html +59 -0
  159. inpanel/public/partials/service/redis.html +40 -0
  160. inpanel/public/partials/service/samba.html +58 -0
  161. inpanel/public/partials/service/sendmail.html +40 -0
  162. inpanel/public/partials/service/ssh.html +186 -0
  163. inpanel/public/partials/service/tomcat.html +65 -0
  164. inpanel/public/partials/service/vsftpd.html +195 -0
  165. inpanel/public/partials/setting.html +299 -0
  166. inpanel/public/partials/site/apache/site.html +277 -0
  167. inpanel/public/partials/site/index.html +344 -0
  168. inpanel/public/partials/site/nginx/site.html +558 -0
  169. inpanel/public/partials/sorry.html +19 -0
  170. inpanel/public/partials/storage/autofm.html +103 -0
  171. inpanel/public/partials/storage/backup.html +42 -0
  172. inpanel/public/partials/storage/index.html +360 -0
  173. inpanel/public/partials/storage/migrate.html +93 -0
  174. inpanel/public/partials/storage/movedata.html +98 -0
  175. inpanel/public/partials/storage/remote.html +77 -0
  176. inpanel/public/partials/utils/cron.html +486 -0
  177. inpanel/public/partials/utils/firewall.html +266 -0
  178. inpanel/public/partials/utils/index.html +193 -0
  179. inpanel/public/partials/utils/network.html +118 -0
  180. inpanel/public/partials/utils/process.html +239 -0
  181. inpanel/public/partials/utils/repository.html +373 -0
  182. inpanel/public/partials/utils/shell.html +28 -0
  183. inpanel/public/partials/utils/ssl.html +319 -0
  184. inpanel/public/partials/utils/time.html +109 -0
  185. inpanel/public/partials/utils/user.html +359 -0
  186. inpanel/public/robots.txt +2 -0
  187. inpanel/remote.py +109 -0
  188. inpanel/templates/file/preview.html +34 -0
  189. inpanel/templates/index.html +93 -0
  190. inpanel/utils.py +282 -0
  191. inpanel/web.py +1811 -0
  192. inpanel/yum.py +756 -0
  193. inpanel-1.2.1.dist-info/METADATA +399 -0
  194. inpanel-1.2.1.dist-info/RECORD +198 -0
  195. inpanel-1.2.1.dist-info/WHEEL +5 -0
  196. inpanel-1.2.1.dist-info/entry_points.txt +2 -0
  197. inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
  198. inpanel-1.2.1.dist-info/top_level.txt +1 -0
inpanel/web.py ADDED
@@ -0,0 +1,1811 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2017-2026 Jackson Dou
4
+ # All rights reserved.
5
+ #
6
+ # InPanel is distributed under the terms of The New BSD License.
7
+ # The full license can be found in 'LICENSE'.
8
+
9
+ '''Module for Web Querying'''
10
+
11
+ import binascii
12
+ import os
13
+ import re
14
+ import time
15
+ from base64 import b64decode, b64encode
16
+ from datetime import datetime
17
+ from functools import partial
18
+ from hashlib import md5
19
+ from json import dumps, loads
20
+ from logging import info as loginfo
21
+ from pathlib import Path
22
+ from shlex import quote
23
+ from uuid import uuid4
24
+
25
+ from . import aliyuncs
26
+ from .mod import disk
27
+ from .mod import ftp
28
+ from .mod import yum
29
+ from .mod import login
30
+ from .mod import query
31
+ from .mod import firewall
32
+ from .mod.task import TaskManager
33
+ from . import mod
34
+ import tornado
35
+ import tornado.escape
36
+ import tornado.httpclient
37
+ import tornado.ioloop
38
+ import tornado.web
39
+ from . import utils
40
+ from .base import app_api, app_name, machine, os_name, os_versint, version_info
41
+ from .lib import pyDes
42
+ from .mod import config, setting, server
43
+ from .mod.certificate import Certificate
44
+ from tornado.escape import to_unicode as _d
45
+ from tornado.escape import utf8 as _u
46
+
47
+
48
+ class Application(tornado.web.Application):
49
+ def __init__(self, handlers=None, default_host="", transforms=None, **settings):
50
+ settings['arch'] = machine
51
+ settings['os_name'] = os_name.lower()
52
+ if machine == 'i686' and os_versint == 5:
53
+ settings['arch'] = 'i386'
54
+ settings['data_path'] = str(Path(settings['data_path']))
55
+ settings['package_path'] = str(Path(settings['data_path']) / 'packages')
56
+
57
+ tornado.web.Application.__init__(self, handlers, default_host, transforms, **settings)
58
+
59
+ class RequestHandler(tornado.web.RequestHandler):
60
+
61
+ def initialize(self):
62
+ """Parse JSON data to argument list.
63
+ """
64
+ self.config = config.load_config()
65
+ self.runlogs = config.runlogs_config()
66
+
67
+ content_type = self.request.headers.get("Content-Type", "")
68
+ if content_type.startswith("application/json"):
69
+ try:
70
+ arguments = loads(self.request.body.decode('utf-8'))
71
+ for name, value in arguments.items():
72
+ if isinstance(value, str):
73
+ value = value
74
+ elif isinstance(value, bool):
75
+ value = value and 'on' or 'off'
76
+ else:
77
+ value = ''
78
+ self.request.arguments.setdefault(name, []).append(value)
79
+ except:
80
+ pass
81
+
82
+ def set_default_headers(self):
83
+ self.set_header('Server', app_name)
84
+ if 'Origin' in self.request.headers:
85
+ self.set_header('Access-Control-Allow-Origin', self.request.headers.get('Origin'))
86
+ self.set_header('Access-Control-Allow-Headers', 'X-ACCESS-TOKEN, Content-Type')
87
+ self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
88
+
89
+ def options(self, *args, **kwargs):
90
+ self.set_status(204)
91
+ self.finish()
92
+
93
+ def check_xsrf_cookie(self):
94
+ # check for the access token
95
+ if self.get_argument("_access", None) or self.request.headers.get("X-ACCESS-TOKEN"):
96
+ if self.config.get('auth', 'accesskeyenable') != 'on':
97
+ raise tornado.web.HTTPError(403, "Access Token Not Allowed")
98
+ else:
99
+ # check xsrf cookie
100
+ token = (self.get_argument("_xsrf", None) or self.request.headers.get("X-XSRF-TOKEN"))
101
+ if not token:
102
+ raise tornado.web.HTTPError(403, "'_xsrf' argument missing from POST")
103
+ if self.xsrf_token != token:
104
+ raise tornado.web.HTTPError(403, "XSRF cookie does not match POST argument")
105
+
106
+ def authed(self):
107
+ # check for the access token
108
+ access_token = (self.get_argument("_access", None) or self.request.headers.get("X-ACCESS-TOKEN"))
109
+
110
+ if access_token:
111
+ if self.config.get('auth', 'accesskeyenable') != 'on':
112
+ raise tornado.web.HTTPError(403, 'Access Token Not Allowed')
113
+ elif access_token != self.config.get('auth', 'accesskey'):
114
+ raise tornado.web.HTTPError(403, 'Access Token Error')
115
+ else:
116
+ cur_authed = self.get_secure_cookie('authed', None, 30.0/1440)
117
+ if not cur_authed:
118
+ raise tornado.web.HTTPError(403, "Please Login First")
119
+ # get the cookie within 30 mins
120
+ if cur_authed.decode('utf-8') == 'yes':
121
+ # regenerate the cookie timestamp per 5 mins
122
+ if self.get_secure_cookie('authed', None, 5.0/1440) == None:
123
+ self.set_secure_cookie('authed', 'yes', None)
124
+
125
+ def getlastactive(self):
126
+ # get last active from cookie
127
+ cv = self.get_cookie('authed', False)
128
+ try:
129
+ return int(cv.split('|')[1])
130
+ except:
131
+ return 0
132
+
133
+ @property
134
+ def xsrf_token(self):
135
+ if not hasattr(self, "_xsrf_token"):
136
+ token = self.get_cookie("XSRF-TOKEN") # or self.request.headers.get("X-XSRF-TOKEN"))
137
+ # token = (self.get_cookie("XSRF-TOKEN") or self.request.headers.get("X-XSRF-TOKEN"))
138
+ if not token:
139
+ token = binascii.b2a_hex(uuid4().bytes)
140
+ expires_days = 30 if self.current_user else None
141
+ self.set_cookie("XSRF-TOKEN", token, expires_days=expires_days)
142
+ self._xsrf_token = token
143
+ return self._xsrf_token
144
+
145
+
146
+ class IndexHandler(tornado.web.RequestHandler):
147
+ def set_default_headers(self):
148
+ self.set_header('Server', app_name)
149
+
150
+ def get(self):
151
+ data = {
152
+ 'htmlTitle': '{{ htmlTitle }}', # js template code
153
+ 'releasetime': version_info['releasetime'],
154
+ 'template_path': ''
155
+ }
156
+ self.render("index.html", **data)
157
+
158
+ class StaticFileHandler(tornado.web.StaticFileHandler):
159
+ def set_default_headers(self):
160
+ self.set_header('Server', app_name)
161
+
162
+ def authed(self):
163
+ # check for the access token
164
+ self.config = config.load_config()
165
+ access_token = (self.get_argument("_access", None) or self.request.headers.get("X-ACCESS-TOKEN"))
166
+ if access_token and self.config.get('auth', 'accesskeyenable') == 'on':
167
+ if access_token != self.config.get('auth', 'accesskey'):
168
+ raise tornado.web.HTTPError(403, 'Access Token Error')
169
+ # print('access_token matched')
170
+ # return
171
+ else:
172
+ cur_authed = self.get_secure_cookie('authed', None, 30.0/1440)
173
+ if not cur_authed:
174
+ raise tornado.web.HTTPError(403, "Please login first")
175
+ # get the cookie within 30 mins
176
+ if cur_authed.decode('utf-8') == 'yes':
177
+ # regenerate the cookie timestamp per 5 mins
178
+ if self.get_secure_cookie('authed', None, 5.0 / 1440) is None:
179
+ self.set_secure_cookie('authed', 'yes', None)
180
+
181
+
182
+ class ErrorHandler(tornado.web.ErrorHandler):
183
+ def set_default_headers(self):
184
+ self.set_header('Server', app_name)
185
+
186
+
187
+ class FallbackHandler(tornado.web.FallbackHandler):
188
+ def set_default_headers(self):
189
+ self.set_header('Server', app_name)
190
+
191
+
192
+ class RedirectHandler(tornado.web.RedirectHandler):
193
+ def set_default_headers(self):
194
+ self.set_header('Server', app_name)
195
+
196
+
197
+ class FileDownloadHandler(StaticFileHandler):
198
+
199
+ def get(self, path):
200
+ self.authed()
201
+ self.set_header('Content-Type', 'application/octet-stream')
202
+ self.set_header('Content-Disposition', 'attachment; filename=%s' % Path(path))
203
+ self.set_header('Content-Transfer-Encoding', 'binary')
204
+ print('FileDownloadHandler', self.root, path)
205
+ return StaticFileHandler.get(self, path)
206
+ # buf_size = 4096
207
+ # with open(str(Path(self.root, path), 'rb', encoding='utf-8') as f:
208
+ # while True:
209
+ # data = f.read(buf_size)
210
+ # if not data:
211
+ # break
212
+ # self.write(data)
213
+ # self.finish()
214
+
215
+ class FileUploadHandler(RequestHandler):
216
+ def post(self):
217
+ self.authed()
218
+ path = self.get_argument('path', '/')
219
+
220
+ self.write('<body style="font-size:14px;overflow:hidden;margin:0;padding:0;">')
221
+
222
+ if not 'ufile' in self.request.files:
223
+ self.write('请选择要上传的文件!')
224
+ else:
225
+ self.write('正在上传...<br>')
226
+ for item in self.request.files['ufile']:
227
+ filename = re.split(r'[\\/]', item['filename'])[-1]
228
+ with open(str(Path(path) / filename), 'wb') as f:
229
+ f.write(item['body'])
230
+ self.write('%s 上传成功!<br>' % item['filename'])
231
+
232
+ self.write('</body>')
233
+
234
+
235
+ class FilePreviewHandler(RequestHandler):
236
+ '''FilePreviewHandler
237
+ TODO: support multi
238
+ '''
239
+ def get(self, path):
240
+ self.authed()
241
+ p = str(Path('/') / path)
242
+ if not Path(p).exists():
243
+ # logger.error("Kerberos failure: %s", err)
244
+ raise tornado.web.HTTPError(404, 'File Not Found', reason='File Not Found')
245
+ buffer = ''
246
+ mtype = 'image/png'
247
+ with open(p, 'rb') as f:
248
+ buffer = f.read()
249
+ data = {
250
+ 'mtype': 'image/png',
251
+ 'data': f'data:{mtype};base64,{str(b64encode(buffer), "utf-8")}'
252
+ }
253
+ self.render('file/preview.html', **data)
254
+
255
+
256
+ class VersionHandler(RequestHandler):
257
+ def get(self):
258
+ self.authed()
259
+ self.write({'code': 0, 'msg': '', 'data': version_info})
260
+
261
+
262
+ class XsrfHandler(RequestHandler):
263
+ """Write a XSRF token to cookie
264
+ """
265
+ def get(self):
266
+ self.xsrf_token
267
+
268
+
269
+ class AuthStatusHandler(RequestHandler):
270
+ """Check if client has been authorized
271
+ """
272
+ def check_xsrf_cookie(self):
273
+ pass
274
+
275
+ def get(self):
276
+ self.write({'lastactive': self.getlastactive()})
277
+
278
+ def post(self):
279
+ # authorize and update cookie
280
+ try:
281
+ self.authed()
282
+ self.write({'authed': 'yes'})
283
+ except:
284
+ self.write({'authed': 'no'})
285
+
286
+
287
+ class ClientHandler(RequestHandler):
288
+ """Get client infomation.
289
+ """
290
+ def get(self, argument):
291
+ if argument == 'ip':
292
+ self.write(self.request.remote_ip)
293
+
294
+
295
+ class LoginHandler(RequestHandler):
296
+ """Validate username and password.
297
+ """
298
+ def post(self):
299
+ username = self.get_argument('username', '')
300
+ password = self.get_argument('password', '')
301
+ result = login.handle_login(self.config, username, password)
302
+ if result['code'] >= 0:
303
+ self.set_secure_cookie('authed', 'yes', None)
304
+ self.write(result)
305
+
306
+
307
+ class LogoutHandler(RequestHandler):
308
+ """Logout
309
+ """
310
+ def post(self):
311
+ self.authed()
312
+ login.handle_logout()
313
+ self.clear_cookie('authed')
314
+
315
+
316
+ class SitePackageHandler(RequestHandler):
317
+ """Interface for querying site packages information.
318
+ """
319
+
320
+ def get(self, op):
321
+ self.authed()
322
+ if hasattr(self, op):
323
+ getattr(self, op)()
324
+ else:
325
+ self.write({'code': -1, 'msg': '未定义的操作!'})
326
+
327
+ async def getlist(self):
328
+ if not Path(self.settings['package_path']).exists():
329
+ Path(self.settings['package_path']).mkdir(parents=True, exist_ok=True)
330
+
331
+ packages = ''
332
+ packages_cachefile = str(Path(self.settings['package_path']) / '.meta')
333
+
334
+ # fetch from cache
335
+ if Path(packages_cachefile).exists():
336
+ # check the file modify time
337
+ mtime = os.stat(packages_cachefile).st_mtime
338
+ if time.time() - mtime < 86400: # cache 24 hours
339
+ with open(packages_cachefile, encoding='utf-8') as f:
340
+ packages = f.read()
341
+
342
+ # fetch from api
343
+ if not packages:
344
+ http_client = tornado.httpclient.AsyncHTTPClient()
345
+ response = await http_client.fetch(app_api['site_packages'])
346
+ if response.error:
347
+ self.write({'code': -1, 'msg': '获取网站系统列表失败!'})
348
+ self.finish()
349
+ return
350
+ else:
351
+ packages = response.body.decode('utf-8')
352
+ with open(packages_cachefile, 'w', encoding='utf-8') as f:
353
+ f.write(packages)
354
+
355
+ packages = tornado.escape.json_decode(packages)
356
+ self.write({'code': 0, 'msg':'', 'data': packages})
357
+
358
+ self.finish()
359
+
360
+ def getdownloadtask(self):
361
+ name = self.get_argument('name', '')
362
+ version = self.get_argument('version', '')
363
+
364
+ if not name or not version:
365
+ self.write({'code': -1, 'msg': '获取安装包下载地址失败!'})
366
+ return
367
+
368
+ # fetch package list from cache
369
+ packages_cachefile = str(Path(self.settings['package_path']) / '.meta')
370
+ if not Path(packages_cachefile).exists():
371
+ self.write({'code': -1, 'msg': '获取安装包下载地址失败!'})
372
+ return
373
+ with open(packages_cachefile, encoding='utf-8') as f:
374
+ packages = f.read()
375
+ packages = tornado.escape.json_decode(packages)
376
+
377
+ # check if name and version is available
378
+ package = None
379
+ for cate in packages:
380
+ for pkg in cate['packages']:
381
+ if pkg['code'] == name:
382
+ for v in pkg['versions']:
383
+ if v['code'] == version:
384
+ package = v
385
+ break
386
+ if package: break
387
+ if package: break
388
+ if not package:
389
+ self.write({'code': -1, 'msg': '获取安装包下载地址失败!'})
390
+ return
391
+
392
+ filename = '%s-%s' % (name, version)
393
+ workpath = str(Path(self.settings['package_path']) / filename)
394
+ if not Path(workpath).exists(): Path(workpath).mkdir(parents=True, exist_ok=True)
395
+
396
+ filenameext = '%s%s' % (filename, package['ext'])
397
+ filepath = str(Path(self.settings['package_path']) / filenameext)
398
+
399
+ self.write({'code': 0, 'msg': '', 'data': {
400
+ 'url': '%s&name=%s&version=%s' % (app_api['download_package'], name, version),
401
+ 'path': filepath,
402
+ 'temp': workpath,
403
+ }})
404
+
405
+
406
+ class QueryHandler(RequestHandler):
407
+ """Interface for querying server information.
408
+
409
+ Query one or more items, seperated by comma.
410
+ Examples:
411
+ /api/query/*
412
+ /api/query/server.*
413
+ /api/query/mod.service.*
414
+ /api/query/server.datetime,server.diskinfo
415
+ /api/query/config.fstab(sda1)
416
+ """
417
+ def get(self, items):
418
+ self.authed()
419
+ result = query.handle_query(items)
420
+ self.write(result)
421
+
422
+
423
+ class UtilsNetworkHandler(RequestHandler):
424
+ """Handler for network ifconfig.
425
+ """
426
+ def get(self, sec, ifname):
427
+ self.authed()
428
+ result = server.ServerInfo.network_handle_get(sec, ifname)
429
+ if result is not None:
430
+ self.write(result)
431
+
432
+ def post(self, sec, ifname):
433
+ self.authed()
434
+ if self.config.get('runtime', 'mode') == 'demo':
435
+ self.write({'code': -1, 'msg': '演示模式不允许修改网络设置!'})
436
+ return
437
+
438
+ args = {
439
+ 'hostname': self.get_argument('hostname', ''),
440
+ 'ip': self.get_argument('ip', ''),
441
+ 'mask': self.get_argument('mask', ''),
442
+ 'gw': self.get_argument('gw', ''),
443
+ 'nameservers': self.get_argument('nameservers', '')
444
+ }
445
+ result = server.ServerInfo.network_handle_post(sec, ifname, args)
446
+ if result is not None:
447
+ self.write(result)
448
+
449
+ class UtilsTimeHandler(RequestHandler):
450
+ """Handler for system datetime mod.setting.
451
+ """
452
+ def get(self, sec, region=None):
453
+ self.authed()
454
+ result = server.ServerInfo.handle_time_get(sec, region)
455
+ if result is not None:
456
+ self.write(result)
457
+
458
+ def post(self, sec, ifname):
459
+ self.authed()
460
+ if self.config.get('runtime', 'mode') == 'demo':
461
+ self.write({'code': -1, 'msg': '演示模式不允许时区设置!'})
462
+ return
463
+
464
+ timezone = self.get_argument('timezone', '')
465
+ result = server.ServerInfo.handle_time_post(self.config, sec, timezone)
466
+ if result is not None:
467
+ self.write(result)
468
+ class SettingHandler(RequestHandler):
469
+ """Settings for InPanel
470
+ """
471
+ async def get(self, section):
472
+ self.authed()
473
+ await setting.handle_get(self, section)
474
+
475
+ def post(self, section):
476
+ self.authed()
477
+ setting.handle_post(self, section)
478
+
479
+
480
+ class OperationHandler(RequestHandler):
481
+ ''''Server operation handler
482
+ '''
483
+
484
+ def post(self, op):
485
+ """Run a server operation
486
+ """
487
+ self.authed()
488
+ if hasattr(self, op):
489
+ getattr(self, op)()
490
+ else:
491
+ self.write({'code': -1, 'msg': '未定义的操作!'})
492
+
493
+ def reboot(self):
494
+ if self.config.get('runtime', 'mode') == 'demo':
495
+ self.write({'code': -1, 'msg': '演示模式不允许重启服务器!'})
496
+ return
497
+ self.write(server.ServerInfo.reboot())
498
+
499
+ def fdisk(self):
500
+ action = self.get_argument('action', '')
501
+ devname = self.get_argument('devname', '')
502
+
503
+ if action == 'add':
504
+ if self.config.get('runtime', 'mode') == 'demo':
505
+ self.write({'code': -1, 'msg': '演示模式不允许添加分区!'})
506
+ return
507
+
508
+ elif action == 'delete':
509
+ if self.config.get('runtime', 'mode') == 'demo':
510
+ self.write({'code': -1, 'msg': '演示模式不允许删除分区!'})
511
+ return
512
+
513
+ size = self.get_argument('size', '')
514
+ unit = self.get_argument('unit', '')
515
+ self.write(disk.handle_fdisk(action, devname, size, unit))
516
+
517
+ def service(self):
518
+ mod.service.web_handler(self)
519
+
520
+ def user(self):
521
+ mod.user.web_handler(self)
522
+
523
+ def file(self):
524
+ mod.file.web_handler(self)
525
+
526
+ def apache(self):
527
+ mod.httpd.web_handler(self)
528
+
529
+ def nginx(self):
530
+ mod.nginx.web_handler(self)
531
+
532
+ def mysql(self):
533
+ mod.mysql.web_handler(self)
534
+
535
+ def php(self):
536
+ mod.php.web_handler(self)
537
+
538
+ def ssh(self):
539
+ mod.ssh.web_handler(self)
540
+
541
+ def cron(self):
542
+ mod.cron.web_handler(self)
543
+
544
+ def vsftpd(self):
545
+ mod.vsftpd.web_handler(self)
546
+
547
+ def named(self):
548
+ mod.named.web_handler(self)
549
+
550
+ def lighttpd(self):
551
+ mod.lighttpd.web_handler(self)
552
+
553
+ def proftpd(self):
554
+ mod.proftpd.web_handler(self)
555
+
556
+ def pureftpd(self):
557
+ mod.pureftpd.web_handler(self)
558
+
559
+ def shell(self):
560
+ if self.config.get('runtime', 'mode') == 'demo':
561
+ self.write({'code': -1, 'msg': '演示模式不允许执行 shell 命令 !'})
562
+ return
563
+ action = self.get_argument('action', '')
564
+ cmd = self.get_argument('cmd', '')
565
+ cwd = self.get_argument('cwd', '')
566
+ if action == 'exec_command':
567
+ self.write({'code': 0, 'msg': '命令已发送', 'data': mod.shell.exec_command(cmd, cwd)})
568
+
569
+ class PageHandler(RequestHandler):
570
+ """Return single page.
571
+ """
572
+ def get(self, op, action):
573
+ try:
574
+ self.authed()
575
+ except:
576
+ self.write('<!DOCTYPE html><html><head><title>Permission Denied</title><meta charset="utf-8"/></head><body>没有权限,请<a href="/">登录</a>后再查看该页!<body></html>')
577
+ return
578
+ if hasattr(self, op):
579
+ getattr(self, op)(action)
580
+ else:
581
+ self.write('未定义的操作!')
582
+
583
+ def php(self, action):
584
+ if action == 'phpinfo':
585
+ # =PHPE9568F34-D428-11d2-A769-00AA001ACF42 (PHP Logo)
586
+ # =PHPE9568F35-D428-11d2-A769-00AA001ACF42 (Zend logo)
587
+ # =PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000 (PHP Credits)
588
+ # redirect them to http://mod.php.net/index.php?***
589
+ if self.request.query.startswith('=PHP'):
590
+ self.redirect('http://www.mod.php.net/index.php?%s' % self.request.query)
591
+ else:
592
+ self.write(mod.php.phpinfo())
593
+
594
+
595
+ class BackupHandler(RequestHandler):
596
+ def get(self):
597
+ self.authed()
598
+
599
+ if self.config.get('runtime', 'mode') == 'demo':
600
+ self.write('演示模式不允许执行此操作!')
601
+ return
602
+
603
+ path = os.path.join(self.settings['data_path'], 'config.ini')
604
+ if os.path.isfile(path):
605
+ self.set_header('Content-Type', 'application/octet-stream')
606
+ self.set_header('Content-disposition', 'attachment; filename=inpanel_backup_%s.bak' % time.strftime('%Y%m%d'))
607
+ self.set_header('Content-Transfer-Encoding', 'binary')
608
+ with open(path, encoding='utf-8') as f:
609
+ self.write(f.read())
610
+ else:
611
+ self.write('配置文件不存在!')
612
+
613
+ def authed(self):
614
+ access_token = (self.get_argument("_access", None) or self.request.headers.get("X-ACCESS-TOKEN"))
615
+ if access_token and self.config.get('auth', 'accesskeyenable') == 'on':
616
+ if access_token != self.config.get('auth', 'accesskey'):
617
+ raise tornado.web.HTTPError(403, 'Access Token Error')
618
+ else:
619
+ cur_authed = self.get_secure_cookie('authed', None, 30.0/1440)
620
+ if not cur_authed:
621
+ raise tornado.web.HTTPError(403, "Please Login First")
622
+ if cur_authed.decode('utf-8') == 'yes':
623
+ if self.get_secure_cookie('authed', None, 5.0/1440) == None:
624
+ self.set_secure_cookie('authed', 'yes', None)
625
+
626
+
627
+ class RestoreHandler(RequestHandler):
628
+ def post(self):
629
+ self.authed()
630
+
631
+ if self.config.get('runtime', 'mode') == 'demo':
632
+ self.write('演示模式不允许执行此操作!')
633
+ return
634
+
635
+ path = os.path.join(self.settings['data_path'], 'config.ini')
636
+
637
+ self.write('<body style="font-size:14px;overflow:hidden;margin:0;padding:0;">')
638
+
639
+ if not 'ufile' in self.request.files:
640
+ self.write('请选择备份配置文件!')
641
+ else:
642
+ self.write('正在上传...')
643
+ file = self.request.files['ufile'][0]
644
+ testpath = path+'.test'
645
+ with open(testpath, 'wb') as f:
646
+ f.write(file['body'])
647
+
648
+ try:
649
+ with open(path, 'wb') as f:
650
+ f.write(file['body'])
651
+ self.write('还原成功!')
652
+ except:
653
+ self.write('配置文件有误,还原失败!')
654
+
655
+ os.unlink(testpath)
656
+
657
+ self.write('</body>')
658
+
659
+
660
+ class BuyECSHandler(RequestHandler):
661
+ """Aliyun CPS program.
662
+ """
663
+ def get(self):
664
+ self.redirect('http://www.aliyun.com/cps/rebate?from_uid=zop0qMW4KbY=')
665
+
666
+
667
+ class AccountHandler(RequestHandler):
668
+ """ECS Account handler.
669
+ """
670
+ def get(self):
671
+ self.authed()
672
+ status = self.get_argument('status', '')
673
+
674
+ accounts = self.config.get('ecs', 'accounts')
675
+ try:
676
+ accounts = loads(accounts)
677
+ except:
678
+ accounts = []
679
+
680
+ accounts = sorted(accounts, key=lambda k:k['name'])
681
+ if status:
682
+ status = status == 'enable'
683
+ accounts = filter(lambda a: a['status'] == status, accounts)
684
+
685
+ if self.config.get('runtime', 'mode') == 'demo':
686
+ for i, account in enumerate(accounts):
687
+ accounts[i]['access_key_secret'] = '演示模式下密钥被保护'
688
+
689
+ self.write({'code': 0, 'msg': '成功加载 ECS 帐号列表!', 'data': accounts})
690
+
691
+ def post(self):
692
+ self.authed()
693
+ action = self.get_argument('action', '')
694
+
695
+ if self.config.get('runtime', 'mode') == 'demo':
696
+ self.write({'code': -1, 'msg': '演示模式不允许修改 ECS 帐号!'})
697
+ return
698
+
699
+ if action == 'add' or action == 'update':
700
+ name = self.get_argument('name', '')
701
+ access_key_id = self.get_argument('access_key_id', '')
702
+ access_key_secret = self.get_argument('access_key_secret', '')
703
+ status = self.get_argument('status', '')
704
+ newaccount = {
705
+ 'name': name,
706
+ 'access_key_id': access_key_id,
707
+ 'access_key_secret': access_key_secret,
708
+ 'status': status == 'on',
709
+ }
710
+ if action == 'update':
711
+ old_access_key_id = self.get_argument('old_access_key_id', '')
712
+
713
+ accounts = self.config.get('ecs', 'accounts')
714
+ try:
715
+ accounts = loads(accounts)
716
+ except:
717
+ accounts = []
718
+
719
+ if action == 'add':
720
+ for account in accounts:
721
+ if account['access_key_id'] == access_key_id:
722
+ self.write({'code': -1, 'msg': '添加失败!该 Access Key ID 已存在!'})
723
+ return
724
+ accounts.append(newaccount)
725
+ else:
726
+ found = False
727
+ for i, account in enumerate(accounts):
728
+ if account['access_key_id'] == old_access_key_id:
729
+ accounts[i] = newaccount
730
+ found = True
731
+ break
732
+ if not found:
733
+ self.write({'code': -1, 'msg': '更新失败!该 Access Key ID 不存在!'})
734
+ return
735
+
736
+ self.config.set('ecs', 'accounts', dumps(accounts))
737
+ if action == 'add':
738
+ self.write({'code': 0, 'msg': '新帐号添加成功!'})
739
+ else:
740
+ self.write({'code': 0, 'msg': '帐号更新成功!'})
741
+
742
+ elif action == 'delete':
743
+ access_key_id = self.get_argument('access_key_id', '')
744
+ accounts = self.config.get('ecs', 'accounts')
745
+ try:
746
+ accounts = loads(accounts)
747
+ except:
748
+ accounts = []
749
+
750
+ found = False
751
+ for i, account in enumerate(accounts):
752
+ if account['access_key_id'] == access_key_id:
753
+ del accounts[i]
754
+ found = True
755
+ break
756
+ if not found:
757
+ self.write({'code': -1, 'msg': '删除失败!该 Access Key ID 不存在!'})
758
+ return
759
+
760
+ self.config.set('ecs', 'accounts', dumps(accounts))
761
+ self.write({'code': 0, 'msg': '帐号删除成功!'})
762
+
763
+
764
+ class ECSHandler(RequestHandler):
765
+ def _get_secret(self, access_key_id):
766
+ accounts = self.config.get('ecs', 'accounts')
767
+ try:
768
+ accounts = loads(accounts)
769
+ except:
770
+ accounts = []
771
+
772
+ for account in accounts:
773
+ if account['access_key_id'] == access_key_id:
774
+ return account['access_key_secret']
775
+ return False
776
+
777
+ async def get(self, section):
778
+ self.authed()
779
+
780
+ if section == 'regions':
781
+ access_key_id = self.get_argument('access_key_id', '')
782
+ if not access_key_id:
783
+ self.write({'code': -1, 'msg': 'Access Key ID 不能为空!'})
784
+ self.finish()
785
+ return
786
+
787
+ access_key_secret = self._get_secret(access_key_id)
788
+ if access_key_secret == False:
789
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
790
+ self.finish()
791
+ return
792
+
793
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
794
+ result, data, reqid = await mod.shell.async_task(srv.DescribeRegions)
795
+ if not result:
796
+ self.write({'code': -1, 'msg': '地域列表加载失败!(%s)' % data['Message']})
797
+ self.finish()
798
+ return
799
+
800
+ if 'Regions' in data:
801
+ regions = data['Regions']
802
+ else:
803
+ regions = []
804
+
805
+ self.write({'code': 0, 'msg': '成功加载地域列表!', 'data': {'regions': regions}})
806
+ self.finish()
807
+
808
+ elif section == 'zones':
809
+ access_key_id = self.get_argument('access_key_id', '')
810
+ region_code = self.get_argument('region_code', '')
811
+
812
+ access_key_secret = self._get_secret(access_key_id)
813
+ if access_key_secret == False:
814
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
815
+ self.finish()
816
+ return
817
+
818
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
819
+ result, data, reqid = await mod.shell.async_task(srv.DescribeZones, RegionCode=region_code)
820
+ if not result:
821
+ self.write({'code': -1, 'msg': '可用区列表加载失败!(%s)' % data['Message']})
822
+ self.finish()
823
+ return
824
+
825
+ if 'Zones' in data:
826
+ zones = data['Zones']
827
+ else:
828
+ zones = []
829
+
830
+ self.write({'code': 0, 'msg': '成功加载可用区列表!', 'data': {'zones': zones}})
831
+ self.finish()
832
+
833
+ elif section == 'instances':
834
+ access_key_id = self.get_argument('access_key_id', '')
835
+ region_code = self.get_argument('region_code', '')
836
+ page_number = self.get_argument('page_number', '1')
837
+ page_size = self.get_argument('page_size', '10')
838
+
839
+ access_key_secret = self._get_secret(access_key_id)
840
+ if access_key_secret == False:
841
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
842
+ self.finish()
843
+ return
844
+
845
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
846
+ result, data, reqid = await mod.shell.async_task(srv.DescribeInstances, RegionCode=region_code, PageNumber=page_number, PageSize=page_size)
847
+ if not result:
848
+ self.write({'code': -1, 'msg': '云服务器列表加载失败!(%s)' % data['Message']})
849
+ self.finish()
850
+ return
851
+
852
+ if 'Instances' in data:
853
+ instances = data['Instances']
854
+ else:
855
+ instances = []
856
+
857
+ self.write({'code': 0, 'msg': '成功加载云服务器列表!', 'data': {
858
+ 'instances': instances,
859
+ 'total_number': data['InstanceTotalNumber'],
860
+ 'page_number': data['PageNumber'],
861
+ 'page_size': data['PageSize'],
862
+ }})
863
+ self.finish()
864
+
865
+ elif section == 'images':
866
+ access_key_id = self.get_argument('access_key_id', '')
867
+ region_code = self.get_argument('region_code', '')
868
+ page_number = self.get_argument('page_number', '1')
869
+ page_size = self.get_argument('page_size', '10')
870
+
871
+ access_key_secret = self._get_secret(access_key_id)
872
+ if access_key_secret == False:
873
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
874
+ self.finish()
875
+ return
876
+
877
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
878
+ result, data, reqid = await mod.shell.async_task(srv.DescribeImages, RegionCode=region_code, PageNumber=page_number, PageSize=page_size)
879
+ if not result:
880
+ self.write({'code': -1, 'msg': '系统镜像列表加载失败!(%s)' % data['Message']})
881
+ self.finish()
882
+ return
883
+
884
+ if 'Images' in data:
885
+ images = data['Images']
886
+ else:
887
+ images = []
888
+
889
+ self.write({'code': 0, 'msg': '成功加载系统镜像列表!', 'data': {
890
+ 'images': images,
891
+ 'total_number': data['ImageTotalNumber'],
892
+ 'page_number': data['PageNumber'],
893
+ 'page_size': data['PageSize'],
894
+ }})
895
+ self.finish()
896
+
897
+ elif section == 'disks':
898
+ access_key_id = self.get_argument('access_key_id', '')
899
+ instance_name = self.get_argument('instance_name', '')
900
+
901
+ access_key_secret = self._get_secret(access_key_id)
902
+ if access_key_secret == False:
903
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
904
+ self.finish()
905
+ return
906
+
907
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
908
+ result, data, reqid = await mod.shell.async_task(srv.DescribeDisks, InstanceName=instance_name)
909
+ if not result:
910
+ self.write({'code': -1, 'msg': '磁盘列表加载失败!(%s)' % data['Message']})
911
+ self.finish()
912
+ return
913
+
914
+ if 'Disks' in data:
915
+ disks = data['Disks']
916
+ else:
917
+ disks = []
918
+
919
+ self.write({'code': 0, 'msg': '成功加载磁盘列表!', 'data': {'disks': disks}})
920
+ self.finish()
921
+
922
+ elif section == 'snapshots':
923
+ access_key_id = self.get_argument('access_key_id', '')
924
+ instance_name = self.get_argument('instance_name', '')
925
+ disk_code = self.get_argument('disk_code', '')
926
+
927
+ access_key_secret = self._get_secret(access_key_id)
928
+ if access_key_secret == False:
929
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
930
+ self.finish()
931
+ return
932
+
933
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
934
+ result, data, reqid = await mod.shell.async_task(srv.DescribeSnapshots, InstanceName=instance_name, DiskCode=disk_code)
935
+ if not result:
936
+ self.write({'code': -1, 'msg': '磁盘快照列表加载失败!(%s)' % data['Message']})
937
+ self.finish()
938
+ return
939
+
940
+ if 'Snapshots' in data:
941
+ snapshots = data['Snapshots']
942
+ else:
943
+ snapshots = []
944
+
945
+ snapshots = sorted(snapshots, key=lambda k:k['CreateTime'], reverse=True)
946
+
947
+ self.write({'code': 0, 'msg': '成功加载磁盘快照列表!', 'data': {'snapshots': snapshots}})
948
+ self.finish()
949
+
950
+ elif section == 'accessinfo':
951
+ instance_name = self.get_argument('instance_name', '')
952
+ if not instance_name:
953
+ self.write({'code': -1, 'msg': '服务器不存在!'})
954
+ self.finish()
955
+ return
956
+
957
+ if not self.config.has_option('inpanel', instance_name):
958
+ accessinfo = {'accesskey': '', 'accessnet': 'public', 'accessport': '14433'}
959
+ else:
960
+ data = self.config.get('inpanel', instance_name)
961
+ data = data.split('|')
962
+ accessinfo = {
963
+ 'accesskey': data[0],
964
+ 'accessnet': data[1],
965
+ 'accessport': data[2],
966
+ }
967
+
968
+ self.write({'code': 0, 'msg': '', 'data': accessinfo})
969
+ self.finish()
970
+
971
+ else:
972
+ self.write({'code': -1, 'msg': '未定义的操作!'})
973
+ self.finish()
974
+
975
+ async def post(self, section):
976
+ self.authed()
977
+
978
+ if section in ('startinstance', 'stopinstance', 'rebootinstance', 'resetinstance'):
979
+
980
+ if self.config.get('runtime', 'mode') == 'demo':
981
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
982
+ self.finish()
983
+ return
984
+
985
+ access_key_id = self.get_argument('access_key_id', '')
986
+ instance_name = self.get_argument('instance_name', '')
987
+ if section in ('stopinstance', 'rebootinstance'):
988
+ force = self.get_argument('force', '') and 'true' or None
989
+ elif section == 'resetinstance':
990
+ image_code = self.get_argument('image_code', '')
991
+
992
+ access_key_secret = self._get_secret(access_key_id)
993
+ if access_key_secret == False:
994
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
995
+ self.finish()
996
+ return
997
+
998
+ opstr = {'startinstance': '启动', 'stopinstance': '停止', 'rebootinstance': '重启', 'resetinstance': '重置'}
999
+
1000
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
1001
+ if section == 'startinstance':
1002
+ result, data, reqid = await mod.shell.async_task(srv.StartInstance, instance_name)
1003
+ elif section == 'stopinstance':
1004
+ result, data, reqid = await mod.shell.async_task(srv.StopInstance, instance_name, ForceStop=force)
1005
+ elif section == 'rebootinstance':
1006
+ result, data, reqid = await mod.shell.async_task(srv.RebootInstance, instance_name, ForceStop=force)
1007
+ elif section == 'resetinstance':
1008
+ result, data, reqid = await mod.shell.async_task(srv.ResetInstance, instance_name, ImageCode=image_code)
1009
+ if not result:
1010
+ self.write({'code': -1, 'msg': '云服务器 %s %s失败!(%s)' % (instance_name, opstr[section], data['Message'])})
1011
+ self.finish()
1012
+ return
1013
+
1014
+ self.write({'code': 0, 'msg': '云服务器%s指令发送成功!' % opstr[section], 'data': data})
1015
+ self.finish()
1016
+
1017
+ elif section in ('createsnapshot', 'deletesnapshot', 'cancelsnapshot', 'rollbacksnapshot'):
1018
+
1019
+ if self.config.get('runtime', 'mode') == 'demo':
1020
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1021
+ self.finish()
1022
+ return
1023
+
1024
+ access_key_id = self.get_argument('access_key_id', '')
1025
+ instance_name = self.get_argument('instance_name', '')
1026
+ disk_code = self.get_argument('disk_code', '')
1027
+ if section in ('deletesnapshot', 'cancelsnapshot', 'rollbacksnapshot'):
1028
+ snapshot_code = self.get_argument('snapshot_code', '')
1029
+
1030
+ access_key_secret = self._get_secret(access_key_id)
1031
+ if access_key_secret == False:
1032
+ self.write({'code': -1, 'msg': '该帐号不存在!'})
1033
+ self.finish()
1034
+ return
1035
+
1036
+ opstr = {'createsnapshot': '创建', 'deletesnapshot': '删除', 'cancelsnapshot': '取消', 'rollbacksnapshot': '回滚'}
1037
+
1038
+ srv = aliyuncs.ECS(access_key_id, access_key_secret)
1039
+ if section == 'createsnapshot':
1040
+ result, data, reqid = await mod.shell.async_task(srv.CreateSnapshot, InstanceName=instance_name, DiskCode=disk_code)
1041
+ elif section == 'deletesnapshot':
1042
+ result, data, reqid = await mod.shell.async_task(srv.DeleteSnapshot, InstanceName=instance_name, DiskCode=disk_code, SnapshotCode=snapshot_code)
1043
+ elif section == 'cancelsnapshot':
1044
+ result, data, reqid = await mod.shell.async_task(srv.CancelSnapshotRequest, InstanceName=instance_name, SnapshotCode=snapshot_code)
1045
+ elif section == 'rollbacksnapshot':
1046
+ result, data, reqid = await mod.shell.async_task(srv.RollbackSnapshot, InstanceName=instance_name, DiskCode=disk_code, SnapshotCode=snapshot_code)
1047
+ if not result:
1048
+ self.write({'code': -1, 'msg': '快照%s失败!(%s)' % (opstr[section], data['Message'])})
1049
+ self.finish()
1050
+ return
1051
+
1052
+ self.write({'code': 0, 'msg': '快照%s指令发送成功!' % opstr[section], 'data': data})
1053
+ self.finish()
1054
+
1055
+ elif section == 'accessinfo':
1056
+
1057
+ if self.config.get('runtime', 'mode') == 'demo':
1058
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1059
+ self.finish()
1060
+ return
1061
+
1062
+ instance_name = self.get_argument('instance_name', '')
1063
+ accesskey = self.get_argument('accesskey', '')
1064
+ accessnet = self.get_argument('accessnet', '')
1065
+ accessport = self.get_argument('accessport', '')
1066
+
1067
+ if not instance_name:
1068
+ self.write({'code': -1, 'msg': '服务器不存在!'})
1069
+ self.finish()
1070
+ return
1071
+
1072
+ self.config.set('inpanel', instance_name, '%s|%s|%s' % (accesskey, accessnet, accessport))
1073
+
1074
+ self.write({'code': 0, 'msg': 'InPanel 远程控制设置保存成功!'})
1075
+ self.finish()
1076
+
1077
+ else:
1078
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1079
+ self.finish()
1080
+
1081
+
1082
+ class InPanelIndexHandler(RequestHandler):
1083
+ """Index page of InPanel.
1084
+ """
1085
+ def get(self, instance_name, ip, port):
1086
+ path = os.path.join(self.settings['inpanel_path'], 'index.html')
1087
+ with open(path, encoding='utf-8') as f:
1088
+ html = f.read()
1089
+ html = html.replace('<link rel="stylesheet" href="', '<link rel="stylesheet" href="/inpanel/')
1090
+ html = html.replace('<script src="', '<script src="/inpanel/')
1091
+ html = html.replace("var template_path = '';", "var template_path = '/inpanel';")
1092
+ self.write(html)
1093
+
1094
+
1095
+ class InPanelHandler(RequestHandler):
1096
+ """Operation proxy of InPanel.
1097
+ """
1098
+ def handle_response(self, response):
1099
+ if response.error and not isinstance(response.error, tornado.httpclient.HTTPError):
1100
+ loginfo("response has error %s", response.error)
1101
+ self.set_status(500)
1102
+ self.write("Internal server error:\n" + str(response.error))
1103
+ self.finish()
1104
+ else:
1105
+ self.set_status(response.code)
1106
+ for header in ('Date', 'Cache-Control', 'Content-Type', 'Etag', 'Location'):
1107
+ v = response.headers.get(header)
1108
+ if v:
1109
+ self.set_header(header, v)
1110
+ if response.body:
1111
+ self.write(response.body.decode('utf-8'))
1112
+ self.finish()
1113
+
1114
+ def forward(self, port=None, host=None):
1115
+ try:
1116
+ tornado.httpclient.AsyncHTTPClient().fetch(
1117
+ tornado.httpclient.HTTPRequest(
1118
+ url = "%s://%s:%s%s" % (
1119
+ self.request.protocol, host or "127.0.0.1",
1120
+ port or 80, self.request.uri),
1121
+ method=self.request.method,
1122
+ body=self.request.body,
1123
+ headers=self.request.headers,
1124
+ follow_redirects=False),
1125
+ self.handle_response)
1126
+ except tornado.httpclient.HTTPError as x:
1127
+ loginfo("tornado signalled HTTPError %s", x)
1128
+ if hasattr(x, 'response') and x.response:
1129
+ self.handle_response(x.response)
1130
+ except:
1131
+ self.set_status(500)
1132
+ self.write("Internal server error\n")
1133
+ self.finish()
1134
+
1135
+ def gen_token(self, instance_name):
1136
+ if not self.config.has_option('inpanel', instance_name):
1137
+ self.set_status(403)
1138
+ self.finish()
1139
+ return
1140
+ else:
1141
+ data = self.config.get('inpanel', instance_name)
1142
+ data = data.split('|')
1143
+ accesskey = data[0]
1144
+
1145
+ accesskey = b64decode(accesskey)
1146
+ key = accesskey[:24]
1147
+ iv = accesskey[24:]
1148
+ k = pyDes.triple_des(key, pyDes.CBC, iv, pad=None, padmode=pyDes.PAD_PKCS5)
1149
+ access_token = k.encrypt('timestamp:%d' % int(time.time()))
1150
+ access_token = b64encode(access_token)
1151
+ return access_token
1152
+
1153
+ def get(self, instance_name, ip, port, uri):
1154
+ self.authed()
1155
+ self.request.body = None
1156
+ self.request.uri = '/'+uri
1157
+ self.request.headers['X-ACCESS-TOKEN'] = self.gen_token(instance_name)
1158
+ self.forward(port, ip)
1159
+
1160
+ def post(self, instance_name, ip, port, uri):
1161
+ self.authed()
1162
+ self.request.uri = '/'+uri
1163
+ self.request.headers['X-ACCESS-TOKEN'] = self.gen_token(instance_name)
1164
+ self.forward(port, ip)
1165
+
1166
+
1167
+ class RepoYumHandler(RequestHandler):
1168
+ """Handler for YUM Request.
1169
+ """
1170
+ def get(self, sec, repo=None):
1171
+ self.authed()
1172
+ if self.config.get('runtime', 'mode') == 'demo':
1173
+ self.write({'code': -1, 'msg': '演示模式不允许设置 YUM !'})
1174
+ return
1175
+ if sec == 'list':
1176
+ items = yum.get_list()
1177
+ if items is None:
1178
+ self.write({'code': -1, 'msg': '获取配置失败!'})
1179
+ else:
1180
+ self.write({'code': 0, 'msg': '', 'data': items})
1181
+ elif sec == 'item':
1182
+ if repo is None:
1183
+ repo = self.get_argument('repo', None)
1184
+ if repo == None:
1185
+ self.write({'code': -1, 'msg': '配置文件不能为空!'})
1186
+ return
1187
+ data = yum.get_item(repo)
1188
+ if data is None:
1189
+ self.write({'code': -1, 'msg': '配置文件不存在!'})
1190
+ else:
1191
+ self.write({'code': 0, 'msg': '', 'data': data})
1192
+ else:
1193
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1194
+
1195
+ def post(self, sec, repo=None):
1196
+ self.authed()
1197
+ if self.config.get('runtime', 'mode') == 'demo':
1198
+ self.write({'code': -1, 'msg': '演示模式不允许设置 YUM !'})
1199
+ return
1200
+
1201
+ if sec in ('edit', 'add'):
1202
+ if repo is None:
1203
+ repo = self.get_argument('repo', None)
1204
+ if repo is None:
1205
+ self.write({'code': -1, 'msg': '配置文件不能为空!'})
1206
+ return
1207
+ serverid = self.get_argument('serverid', '')
1208
+ if serverid == '':
1209
+ self.write({'code': -1, 'msg': '仓库标识ID不能为空!'})
1210
+ return
1211
+ name = self.get_argument('name', '')
1212
+ if name == '':
1213
+ self.write({'code': -1, 'msg': '仓库名称不能为空!'})
1214
+ return
1215
+ baseurl = self.get_argument('baseurl', '')
1216
+ if baseurl == '':
1217
+ self.write({'code': -1, 'msg': '仓库路径不能为空!'})
1218
+ return
1219
+ enabled = self.get_argument('enabled', True)
1220
+ gpgcheck = self.get_argument('gpgcheck', False)
1221
+ data = {
1222
+ serverid: {
1223
+ 'name': name,
1224
+ 'enabled': 0 if not enabled else 1,
1225
+ 'baseurl': baseurl,
1226
+ 'gpgcheck': 0 if not gpgcheck else 1,
1227
+ 'gpgkey': ''
1228
+ }
1229
+ }
1230
+ if sec == 'edit':
1231
+ if not yum.item_exists(repo):
1232
+ self.write({'code': -1, 'msg': '配置文件不存在!'})
1233
+ return
1234
+ if yum.set_item(repo, data) is True:
1235
+ self.write({'code': 0, 'msg': '配置修改成功!'})
1236
+ else:
1237
+ self.write({'code': -1, 'msg': '配置修改失败!'})
1238
+ else:
1239
+ if yum.item_exists(repo):
1240
+ self.write({'code': -1, 'msg': '配置文件已存在!'})
1241
+ return
1242
+ if yum.add_item(repo, data) is True:
1243
+ self.write({'code': 0, 'msg': '配置添加成功!'})
1244
+ else:
1245
+ self.write({'code': -1, 'msg': '配置添加失败!'})
1246
+ elif sec == 'del':
1247
+ if repo is None:
1248
+ repo = self.get_argument('repo', None)
1249
+ if repo is None:
1250
+ self.write({'code': -1, 'msg': '配置文件不能为空!'})
1251
+ return
1252
+ if not yum.item_exists(repo):
1253
+ self.write({'code': -1, 'msg': '配置文件不存在!'})
1254
+ return
1255
+ if yum.del_item(repo) is True:
1256
+ self.write({'code': 0, 'msg': '配置文件已移入回收站!'})
1257
+ else:
1258
+ self.write({'code': -1, 'msg': '删除失败!'})
1259
+ else:
1260
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1261
+
1262
+
1263
+ class RepoDnfHandler(RequestHandler):
1264
+ """Handler for DNF Repository Request.
1265
+ """
1266
+ def get(self, sec, repo=None):
1267
+ self.authed()
1268
+ if self.config.get('runtime', 'mode') == 'demo':
1269
+ self.write({'code': -1, 'msg': '演示模式不允许设置 DNF !'})
1270
+ return
1271
+ if sec == 'list':
1272
+ result = mod.dnf.web_handler({'action': 'list'})
1273
+ self.write(result)
1274
+ elif sec == 'item':
1275
+ if repo is None:
1276
+ repo = self.get_argument('repo', None)
1277
+ result = mod.dnf.web_handler({'action': 'item', 'repo': repo})
1278
+ self.write(result)
1279
+ else:
1280
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1281
+
1282
+ def post(self, sec, repo=None):
1283
+ self.authed()
1284
+ if self.config.get('runtime', 'mode') == 'demo':
1285
+ self.write({'code': -1, 'msg': '演示模式不允许设置 DNF !'})
1286
+ return
1287
+
1288
+ context = {
1289
+ 'action': sec,
1290
+ 'repo': repo if repo else self.get_argument('repo', ''),
1291
+ 'serverid': self.get_argument('serverid', ''),
1292
+ 'name': self.get_argument('name', ''),
1293
+ 'baseurl': self.get_argument('baseurl', ''),
1294
+ 'enabled': self.get_argument('enabled', True),
1295
+ 'gpgcheck': self.get_argument('gpgcheck', False)
1296
+ }
1297
+
1298
+ result = mod.dnf.web_handler(context)
1299
+ self.write(result)
1300
+
1301
+
1302
+ class RepoAptHandler(RequestHandler):
1303
+ """Handler for APT Repository Request.
1304
+ """
1305
+ def get(self, sec, source=None):
1306
+ self.authed()
1307
+ if self.config.get('runtime', 'mode') == 'demo':
1308
+ self.write({'code': -1, 'msg': '演示模式不允许设置 APT !'})
1309
+ return
1310
+ if sec == 'list':
1311
+ result = mod.apt.web_handler({'action': 'list'})
1312
+ self.write(result)
1313
+ elif sec == 'item':
1314
+ if source is None:
1315
+ source = self.get_argument('source', None)
1316
+ result = mod.apt.web_handler({'action': 'item', 'source': source})
1317
+ self.write(result)
1318
+ else:
1319
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1320
+
1321
+ def post(self, sec, source=None):
1322
+ self.authed()
1323
+ if self.config.get('runtime', 'mode') == 'demo':
1324
+ self.write({'code': -1, 'msg': '演示模式不允许设置 APT !'})
1325
+ return
1326
+
1327
+ context = {
1328
+ 'action': sec,
1329
+ 'source': source if source else self.get_argument('source', ''),
1330
+ 'content': self.get_argument('content', ''),
1331
+ 'sources': self.get_argument('sources', [])
1332
+ }
1333
+
1334
+ result = mod.apt.web_handler(context)
1335
+ self.write(result)
1336
+
1337
+
1338
+ class FirewallHandler(RequestHandler):
1339
+ """Handler for Firewall Management."""
1340
+ def get(self, action):
1341
+ self.authed()
1342
+ if self.config.get('runtime', 'mode') == 'demo':
1343
+ self.write({'code': -1, 'msg': '演示模式不允许设置防火墙!'})
1344
+ return
1345
+
1346
+ context = {'action': action}
1347
+ result = mod.firewall.web_handler(context)
1348
+ self.write(result)
1349
+
1350
+ def post(self, action):
1351
+ self.authed()
1352
+ if self.config.get('runtime', 'mode') == 'demo':
1353
+ self.write({'code': -1, 'msg': '演示模式不允许设置防火墙!'})
1354
+ return
1355
+
1356
+ context = {
1357
+ 'action': action,
1358
+ 'port': self.get_argument('port', ''),
1359
+ 'protocol': self.get_argument('protocol', 'tcp'),
1360
+ 'zone': self.get_argument('zone', ''),
1361
+ 'ip': self.get_argument('ip', ''),
1362
+ 'action_type': self.get_argument('action_type', 'allow')
1363
+ }
1364
+
1365
+ result = mod.firewall.web_handler(context)
1366
+ self.write(result)
1367
+
1368
+
1369
+ class BackendHandler(RequestHandler):
1370
+ """Backend process manager
1371
+ """
1372
+ def initialize(self):
1373
+ self.task_manager = TaskManager(self.settings, self.config)
1374
+
1375
+ def get(self, jobname):
1376
+ """Get the status of the new process
1377
+ """
1378
+ self.authed()
1379
+ self.write(self.task_manager._get_job(jobname))
1380
+
1381
+ def post(self, jobname):
1382
+ """Create a new backend process
1383
+ """
1384
+ print('jobname: ', jobname)
1385
+ self.authed()
1386
+
1387
+ # centos/redhat only job
1388
+ if jobname in ('yum_repolist', 'yum_installrepo', 'yum_info',
1389
+ 'yum_install', 'yum_uninstall', 'yum_ext_info'):
1390
+ if self.settings['os_name'] not in ('centos', 'redhat'):
1391
+ self.write({'code': -1, 'msg': '不支持的系统类型!'})
1392
+ return
1393
+
1394
+ if self.config.get('runtime', 'mode') == 'demo':
1395
+ if jobname in ('update', 'datetime', 'swapon', 'swapoff', 'mount', 'umount', 'format'):
1396
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1397
+ return
1398
+
1399
+ if jobname == 'update':
1400
+ self.task_manager._call(self.task_manager.update)
1401
+ elif jobname in ('service_restart', 'service_start', 'service_stop'):
1402
+ name = self.get_argument('name', '')
1403
+ service = self.get_argument('service', '')
1404
+
1405
+ if self.config.get('runtime', 'mode') == 'demo':
1406
+ if service in ('network', 'sshd', 'inpanel', 'iptables'):
1407
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1408
+ return
1409
+
1410
+ if service not in mod.service.Service.service_items:
1411
+ self.write({'code': -1, 'msg': '未支持的服务!'})
1412
+ return
1413
+ if not name: name = service
1414
+ dummy, action = jobname.split('_')
1415
+ if service != '':
1416
+ self.task_manager._call(partial(self.task_manager.service, action, service, name))
1417
+ elif jobname == 'datetime':
1418
+ newdatetime = self.get_argument('datetime', '')
1419
+ # check datetime format
1420
+ try:
1421
+ datetime.strptime(newdatetime, '%Y-%m-%d %H:%M:%S')
1422
+ except:
1423
+ self.write({'code': -1, 'msg': '时间格式有错误!'})
1424
+ return
1425
+ self.task_manager._call(partial(self.task_manager.datetime, newdatetime))
1426
+ elif jobname in ('swapon', 'swapoff'):
1427
+ devname = self.get_argument('devname', '')
1428
+ if jobname == 'swapon':
1429
+ action = 'on'
1430
+ else:
1431
+ action = 'off'
1432
+ self.task_manager._call(partial(self.task_manager.swapon, action, devname))
1433
+ elif jobname in ('mount', 'umount'):
1434
+ devname = self.get_argument('devname', '')
1435
+ mountpoint = self.get_argument('mountpoint', '')
1436
+ fstype = self.get_argument('fstype', '')
1437
+ if jobname == 'mount':
1438
+ action = 'mount'
1439
+ else:
1440
+ action = 'umount'
1441
+ self.task_manager._call(partial(self.task_manager.mount, action, devname, mountpoint, fstype))
1442
+ elif jobname == 'format':
1443
+ devname = self.get_argument('devname', '')
1444
+ fstype = self.get_argument('fstype', '')
1445
+ self.task_manager._call(partial(self.task_manager.format, devname, fstype))
1446
+ elif jobname == 'yum_repolist':
1447
+ self.task_manager._call(self.task_manager.yum_repolist)
1448
+ elif jobname == 'yum_installrepo':
1449
+ repo = self.get_argument('repo', '')
1450
+ self.task_manager._call(partial(self.task_manager.yum_installrepo, repo))
1451
+ elif jobname == 'yum_info':
1452
+ pkg = self.get_argument('pkg', '')
1453
+ repo = self.get_argument('repo', '*')
1454
+ option = self.get_argument('option', '')
1455
+ if option == 'update':
1456
+ if not pkg in [v for k,vv in yum.yum_pkg_alias.items() for v in vv]:
1457
+ self.write({'code': -1, 'msg': '未支持的软件包!'})
1458
+ return
1459
+ else:
1460
+ option = 'install'
1461
+ if not pkg in yum.yum_pkg_alias:
1462
+ self.write({'code': -1, 'msg': '未支持的软件包!'})
1463
+ return
1464
+ if repo not in yum.yum_repolist + ('installed', '*'):
1465
+ self.write({'code': -1, 'msg': '未知的软件源 %s!' % repo})
1466
+ return
1467
+ self.task_manager._call(partial(self.task_manager.yum_info, pkg, repo, option))
1468
+ elif jobname in ('yum_install', 'yum_uninstall', 'yum_update'):
1469
+ repo = self.get_argument('repo', '')
1470
+ pkg = self.get_argument('pkg', '')
1471
+ ext = self.get_argument('ext', '')
1472
+ version = self.get_argument('version', '')
1473
+ release = self.get_argument('release', '')
1474
+
1475
+ if self.config.get('runtime', 'mode') == 'demo':
1476
+ if pkg in ('sshd', 'iptables'):
1477
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1478
+ return
1479
+
1480
+ if not pkg in yum.yum_pkg_relatives:
1481
+ self.write({'code': -1, 'msg': '软件包不存在!'})
1482
+ return
1483
+ if ext and not ext in yum.yum_pkg_relatives[pkg]:
1484
+ self.write({'code': -1, 'msg': '扩展不存在!'})
1485
+ return
1486
+ if jobname == 'yum_install':
1487
+ if repo not in yum.yum_repolist:
1488
+ self.write({'code': -1, 'msg': '未知的软件源 %s!' % repo})
1489
+ return
1490
+ handler = self.task_manager.yum_install
1491
+ elif jobname == 'yum_uninstall':
1492
+ handler = self.task_manager.yum_uninstall
1493
+ elif jobname == 'yum_update':
1494
+ handler = self.task_manager.yum_update
1495
+ self.task_manager._call(partial(handler, repo, pkg, version, release, ext))
1496
+ elif jobname == 'yum_ext_info':
1497
+ pkg = self.get_argument('pkg', '')
1498
+ if not pkg in yum.yum_pkg_relatives:
1499
+ self.write({'code': -1, 'msg': '软件包不存在!'})
1500
+ return
1501
+ self.task_manager._call(partial(self.task_manager.yum_ext_info, pkg))
1502
+ elif jobname in ('move', 'copy'):
1503
+ srcpath = self.get_argument('srcpath', '')
1504
+ despath = self.get_argument('despath', '')
1505
+
1506
+ if self.config.get('runtime', 'mode') == 'demo':
1507
+ if jobname == 'move':
1508
+ if not srcpath.startswith('/var/www') or not despath.startswith('/var/www'):
1509
+ self.write({'code': -1, 'msg': '演示模式不允许修改除 /var/www 以外的目录!'})
1510
+ return
1511
+ elif jobname == 'copy':
1512
+ if not despath.startswith('/var/www'):
1513
+ self.write({'code': -1, 'msg': '演示模式不允许修改除 /var/www 以外的目录!'})
1514
+ return
1515
+
1516
+ if not Path(srcpath).exists():
1517
+ if not Path(srcpath.strip('*')).exists():
1518
+ self.write({'code': -1, 'msg': '源路径不存在!'})
1519
+ return
1520
+ if jobname == 'copy':
1521
+ handler = self.task_manager.copy
1522
+ elif jobname == 'move':
1523
+ handler = self.task_manager.move
1524
+ self.task_manager._call(partial(handler, srcpath, despath))
1525
+ elif jobname == 'remove':
1526
+ paths = self.get_argument('paths', '')
1527
+ paths = paths.split(',')
1528
+
1529
+ if self.config.get('runtime', 'mode') == 'demo':
1530
+ for p in paths:
1531
+ if not p.startswith('/var/www') and not p.startswith(self.settings['package_path']):
1532
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下执行删除操作!'})
1533
+ return
1534
+
1535
+ self.task_manager._call(partial(self.task_manager.remove, paths))
1536
+ elif jobname == 'compress':
1537
+ zippath = self.get_argument('zippath', '')
1538
+ paths = self.get_argument('paths', '')
1539
+ paths = paths.split(',')
1540
+
1541
+ if self.config.get('runtime', 'mode') == 'demo':
1542
+ if not zippath.startswith('/var/www'):
1543
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下创建压缩包!'})
1544
+ return
1545
+ for p in paths:
1546
+ if not p.startswith('/var/www'):
1547
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下创建压缩包!'})
1548
+ return
1549
+
1550
+ self.task_manager._call(partial(self.task_manager.compress, zippath, paths))
1551
+ elif jobname == 'decompress':
1552
+ zippath = self.get_argument('zippath', '')
1553
+ despath = self.get_argument('despath', '')
1554
+
1555
+ if self.config.get('runtime', 'mode') == 'demo':
1556
+ if not zippath.startswith('/var/www') and not zippath.startswith(self.settings['package_path']) or \
1557
+ not despath.startswith('/var/www') and not despath.startswith(self.settings['package_path']):
1558
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下执行解压操作!'})
1559
+ return
1560
+
1561
+ self.task_manager._call(partial(self.task_manager.decompress, zippath, despath))
1562
+ elif jobname == 'ntpdate':
1563
+ server = self.get_argument('server', '')
1564
+ self.task_manager._call(partial(self.task_manager.ntpdate, server))
1565
+ elif jobname == 'chown':
1566
+ paths = self.get_argument('paths', '')
1567
+ paths = paths.split(',')
1568
+
1569
+ if self.config.get('runtime', 'mode') == 'demo':
1570
+ for p in paths:
1571
+ if not p.startswith('/var/www'):
1572
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下执行此操作!'})
1573
+ return
1574
+
1575
+ a_user = self.get_argument('user', '')
1576
+ a_group = self.get_argument('group', '')
1577
+ recursively = self.get_argument('recursively', '')
1578
+ option = recursively == 'on' and '-R' or ''
1579
+ self.task_manager._call(partial(self.task_manager.chown, paths, a_user, a_group, option))
1580
+ elif jobname == 'chmod':
1581
+ paths = self.get_argument('paths', '')
1582
+ paths = paths.split(',')
1583
+
1584
+ if self.config.get('runtime', 'mode') == 'demo':
1585
+ for p in paths:
1586
+ if not p.startswith('/var/www'):
1587
+ self.write({'code': -1, 'msg': '演示模式不允许在 /var/www 以外的目录下执行此操作!'})
1588
+ return
1589
+
1590
+ perms = self.get_argument('perms', '')
1591
+ recursively = self.get_argument('recursively', '')
1592
+ option = recursively == 'on' and '-R' or ''
1593
+ self.task_manager._call(partial(self.task_manager.chmod, paths, perms, option))
1594
+ elif jobname == 'wget':
1595
+ url = self.get_argument('url', '')
1596
+ path = self.get_argument('path', '')
1597
+
1598
+ if self.config.get('runtime', 'mode') == 'demo':
1599
+ if not path.startswith('/var/www') and not path.startswith(self.settings['package_path']):
1600
+ self.write({'code': -1, 'msg': '演示模式不允许下载到 /var/www 以外的目录!'})
1601
+ return
1602
+
1603
+ self.task_manager._call(partial(self.task_manager.wget, url, path))
1604
+ elif jobname == 'mysql_fupdatepwd':
1605
+ password = self.get_argument('password', '')
1606
+ passwordc = self.get_argument('passwordc', '')
1607
+ if password != passwordc:
1608
+ self.write({'code': -1, 'msg': '两次密码输入不一致!'})
1609
+ return
1610
+ self.task_manager._call(partial(self.task_manager.mysql_fupdatepwd, password))
1611
+ elif jobname == 'mysql_databases':
1612
+ password = self.get_argument('password', '')
1613
+ self.task_manager._call(partial(self.task_manager.mysql_databases, password))
1614
+ elif jobname == 'mysql_dbinfo':
1615
+ password = self.get_argument('password', '')
1616
+ dbname = self.get_argument('dbname', '')
1617
+ self.task_manager._call(partial(self.task_manager.mysql_dbinfo, password, dbname))
1618
+ elif jobname == 'mysql_users':
1619
+ password = self.get_argument('password', '')
1620
+ dbname = self.get_argument('dbname', '')
1621
+ self.task_manager._call(partial(self.task_manager.mysql_users, password, dbname))
1622
+ elif jobname == 'mysql_rename':
1623
+ password = self.get_argument('password', '')
1624
+ dbname = self.get_argument('dbname', '')
1625
+ newname = self.get_argument('newname', '')
1626
+ if dbname == newname:
1627
+ self.write({'code': -1, 'msg': '数据库名无变化!'})
1628
+ return
1629
+ self.task_manager._call(partial(self.task_manager.mysql_rename, password, dbname, newname))
1630
+ elif jobname == 'mysql_create':
1631
+ password = self.get_argument('password', '')
1632
+ dbname = self.get_argument('dbname', '')
1633
+ collation = self.get_argument('collation', '')
1634
+ self.task_manager._call(partial(self.task_manager.mysql_create, password, dbname, collation))
1635
+ elif jobname == 'mysql_export':
1636
+ password = self.get_argument('password', '')
1637
+ dbname = self.get_argument('dbname', '')
1638
+ path = self.get_argument('path', '')
1639
+
1640
+ if not path:
1641
+ self.write({'code': -1, 'msg': '请选择数据库导出目录!'})
1642
+ return
1643
+
1644
+ if self.config.get('runtime', 'mode') == 'demo':
1645
+ if not path.startswith('/var/www') and not path.startswith(self.settings['package_path']):
1646
+ self.write({'code': -1, 'msg': '演示模式不允许导出到 /var/www 以外的目录!'})
1647
+ return
1648
+
1649
+ self.task_manager._call(partial(self.task_manager.mysql_export, password, dbname, path))
1650
+ elif jobname == 'mysql_drop':
1651
+ password = self.get_argument('password', '')
1652
+ dbname = self.get_argument('dbname', '')
1653
+ self.task_manager._call(partial(self.task_manager.mysql_drop, password, dbname))
1654
+ elif jobname == 'mysql_createuser':
1655
+ password = self.get_argument('password', '')
1656
+ user = self.get_argument('user', '')
1657
+ host = self.get_argument('host', '')
1658
+ pwd = self.get_argument('pwd', '')
1659
+ self.task_manager._call(partial(self.task_manager.mysql_createuser, password, user, host, pwd))
1660
+ elif jobname == 'mysql_userprivs':
1661
+ password = self.get_argument('password', '')
1662
+ username = self.get_argument('username', '')
1663
+ if not '@' in username:
1664
+ self.write({'code': -1, 'msg': '用户不存在!'})
1665
+ return
1666
+ user, host = username.split('@', 1)
1667
+ self.task_manager._call(partial(self.task_manager.mysql_userprivs, password, user, host))
1668
+ elif jobname == 'mysql_updateuserprivs':
1669
+ password = self.get_argument('password', '')
1670
+ username = self.get_argument('username', '')
1671
+ privs = self.get_argument('privs', '')
1672
+ try:
1673
+ privs = tornado.escape.json_decode(privs)
1674
+ except:
1675
+ self.write({'code': -1, 'msg': '权限数据有误!'})
1676
+ return
1677
+ dbname = self.get_argument('dbname', '')
1678
+ if not '@' in username:
1679
+ self.write({'code': -1, 'msg': '用户不存在!'})
1680
+ return
1681
+ user, host = username.split('@', 1)
1682
+ privs = [
1683
+ priv.replace('_priv', '').replace('_', ' ').upper()
1684
+ .replace('CREATE TMP TABLE', 'CREATE TEMPORARY TABLES')
1685
+ .replace('SHOW DB', 'SHOW DATABASES')
1686
+ .replace('REPL CLIENT', 'REPLICATION CLIENT')
1687
+ .replace('REPL SLAVE', 'REPLICATION SLAVE')
1688
+ for priv, value in privs.items() if '_priv' in priv and value == 'Y']
1689
+ self.task_manager._call(partial(self.task_manager.mysql_updateuserprivs, password, user, host, privs, dbname))
1690
+ elif jobname == 'mysql_setuserpassword':
1691
+ password = self.get_argument('password', '')
1692
+ username = self.get_argument('username', '')
1693
+ if not '@' in username:
1694
+ self.write({'code': -1, 'msg': '用户不存在!'})
1695
+ return
1696
+ user, host = username.split('@', 1)
1697
+ pwd = self.get_argument('pwd', '')
1698
+ self.task_manager._call(partial(self.task_manager.mysql_setuserpassword, password, user, host, pwd))
1699
+ elif jobname == 'mysql_dropuser':
1700
+ password = self.get_argument('password', '')
1701
+ username = self.get_argument('username', '')
1702
+ if not '@' in username:
1703
+ self.write({'code': -1, 'msg': '用户不存在!'})
1704
+ return
1705
+ user, host = username.split('@', 1)
1706
+ user, host = mod.user.strip(), host.strip()
1707
+ if user == 'root' and host != '%':
1708
+ self.write({'code': -1, 'msg': '该用户不允许删除!'})
1709
+ return
1710
+ self.task_manager._call(partial(self.task_manager.mysql_dropuser, password, user, host))
1711
+ elif jobname == 'ssh_genkey':
1712
+ path = self.get_argument('path', '')
1713
+ password = self.get_argument('password', '')
1714
+ if not path:
1715
+ path = '/root/.ssh/sshkey_inpanel'
1716
+ self.task_manager._call(partial(self.task_manager.ssh_genkey, path, password))
1717
+ elif jobname == 'ssh_chpasswd':
1718
+ path = self.get_argument('path', '')
1719
+ oldpassword = self.get_argument('oldpassword', '')
1720
+ newpassword = self.get_argument('newpassword', '')
1721
+ if not path:
1722
+ path = '/root/.ssh/sshkey_inpanel'
1723
+ self.task_manager._call(partial(self.task_manager.ssh_chpasswd, path, oldpassword, newpassword))
1724
+ elif jobname in ('inpanel_install', 'inpanel_uninstall', 'inpanel_config'):
1725
+ if self.config.get('runtime', 'mode') == 'demo':
1726
+ self.write({'code': -1, 'msg': '演示模式不允许此类操作!'})
1727
+ return
1728
+ ssh_ip = self.get_argument('ssh_ip', '')
1729
+ ssh_port = self.get_argument('ssh_port', '22')
1730
+ ssh_user = self.get_argument('ssh_user', '')
1731
+ ssh_password = self.get_argument('ssh_password', '')
1732
+ instance_name = self.get_argument('instance_name', '')
1733
+ if jobname == 'inpanel_install':
1734
+ accessnet = self.get_argument('accessnet', 'public')
1735
+ accesskey = utils.gen_accesskey()
1736
+ accessport = '14433'
1737
+ elif jobname == 'inpanel_config':
1738
+ if not self.config.has_option('inpanel', instance_name):
1739
+ self.write({'code': -1, 'msg': '该服务器还未配置远程控制!'})
1740
+ return
1741
+ accessdata = self.config.get('inpanel', instance_name)
1742
+ accessdata = accessdata.split('|')
1743
+ accesskey = accessdata[0]
1744
+ if jobname == 'inpanel_install':
1745
+ self.task_manager._call(partial(self.task_manager.inpanel_install, ssh_ip, ssh_port, ssh_user, ssh_password, instance_name, accessnet, accessport, accesskey))
1746
+ elif jobname == 'inpanel_uninstall':
1747
+ self.task_manager._call(partial(self.task_manager.inpanel_uninstall, ssh_ip, ssh_port, ssh_user, ssh_password, instance_name))
1748
+ elif jobname == 'inpanel_config':
1749
+ self.task_manager._call(partial(self.task_manager.inpanel_config, ssh_ip, ssh_port, ssh_user, ssh_password, accesskey))
1750
+ elif jobname == 'uploadtoftp':
1751
+ address = self.get_argument('address', '')
1752
+ account = self.get_argument('account', '')
1753
+ password = self.get_argument('password', '')
1754
+ source = self.get_argument('source', '')
1755
+ target = self.get_argument('target', '')
1756
+ self.task_manager._call(partial(self.task_manager.uploadtoftp, address, account, password, source, target))
1757
+ else: # undefined job
1758
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1759
+ return
1760
+
1761
+ self.write({'code': 0, 'msg': ''})
1762
+
1763
+
1764
+ class SSLTLSHandler(RequestHandler):
1765
+ """Handler for SSL/TLS setting.
1766
+ """
1767
+ def get(self, sec, x=None):
1768
+ self.authed()
1769
+ if self.config.get('runtime', 'mode') == 'demo':
1770
+ self.write({'code': -1, 'msg': '演示模式不允许设置 SSL !'})
1771
+ return
1772
+ certs = Certificate()
1773
+ if sec == 'keys':
1774
+ keys_list = certs.get_keys_list()
1775
+ if keys_list is None:
1776
+ self.write({'code': -1, 'msg': '获取私钥失败!'})
1777
+ else:
1778
+ self.write({'code': 0, 'msg': '获取私钥成功!', 'list': keys_list})
1779
+ elif sec == 'crts':
1780
+ crts_list = certs.get_crts_list()
1781
+ if crts_list is None:
1782
+ self.write({'code': -1, 'msg': '获取证书失败!'})
1783
+ else:
1784
+ self.write({'code': 0, 'msg': '获取证书成功!', 'list': crts_list})
1785
+ elif sec == 'csrs':
1786
+ csrs_list = certs.get_csrs_list()
1787
+ if csrs_list is None:
1788
+ self.write({'code': -1, 'msg': '获取证书签名请求失败!'})
1789
+ else:
1790
+ self.write({'code': 0, 'msg': '获取证书签名请求成功!', 'list': csrs_list})
1791
+ elif sec == 'host':
1792
+ host_list = certs.get_host_list()
1793
+ if host_list is None:
1794
+ self.write({'code': -1, 'msg': '获取站点失败!'})
1795
+ else:
1796
+ self.write({'code': 0, 'msg': '获取站点成功!', 'list': host_list})
1797
+ else:
1798
+ self.write({'code': -1, 'msg': '未定义的操作!'})
1799
+
1800
+ def post(self, sec, x=None):
1801
+ self.authed()
1802
+ if self.config.get('runtime', 'mode') == 'demo':
1803
+ self.write({'code': -1, 'msg': '演示模式不允许设置 SSL !'})
1804
+ return
1805
+ action = self.get_argument('action', '')
1806
+ certs = Certificate()
1807
+
1808
+ if sec == 'keys':
1809
+ if action == 'add_domain_keys':
1810
+ self.write({'code': 0, 'msg': u'创建测试私钥成功!(正在测试的功能)'})
1811
+