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.
- inpanel/__init__.py +29 -0
- inpanel/acme.py +289 -0
- inpanel/aliyuncs.py +269 -0
- inpanel/app.py +514 -0
- inpanel/apt.py +223 -0
- inpanel/base.py +200 -0
- inpanel/config.py +139 -0
- inpanel/data/config.ini +29 -0
- inpanel/data/history +5 -0
- inpanel/data/runlogs.ini +12 -0
- inpanel/data/update_info.ini +4 -0
- inpanel/dnf.py +225 -0
- inpanel/lib/__init__.py +9 -0
- inpanel/lib/async_process.py +98 -0
- inpanel/lib/filelock.py +82 -0
- inpanel/lib/magic.py +233 -0
- inpanel/lib/pxssh.py +310 -0
- inpanel/lib/pyDes.py +867 -0
- inpanel/mod/__init__.py +0 -0
- inpanel/mod/apt.py +316 -0
- inpanel/mod/certificate.py +268 -0
- inpanel/mod/config.py +372 -0
- inpanel/mod/cron.py +315 -0
- inpanel/mod/disk.py +322 -0
- inpanel/mod/dnf.py +239 -0
- inpanel/mod/file.py +722 -0
- inpanel/mod/firewall.py +199 -0
- inpanel/mod/firewall_base.py +137 -0
- inpanel/mod/firewall_firewalld.py +170 -0
- inpanel/mod/firewall_iptables.py +176 -0
- inpanel/mod/firewall_macos.py +213 -0
- inpanel/mod/firewall_ufw.py +128 -0
- inpanel/mod/ftp.py +231 -0
- inpanel/mod/httpd.py +1205 -0
- inpanel/mod/lighttpd.py +26 -0
- inpanel/mod/login.py +98 -0
- inpanel/mod/mysql.py +577 -0
- inpanel/mod/named.py +26 -0
- inpanel/mod/nginx.py +2327 -0
- inpanel/mod/package.py +33 -0
- inpanel/mod/package_apt.py +62 -0
- inpanel/mod/package_base.py +77 -0
- inpanel/mod/package_dnf.py +62 -0
- inpanel/mod/package_map.py +366 -0
- inpanel/mod/package_yum.py +64 -0
- inpanel/mod/php.py +289 -0
- inpanel/mod/process.py +291 -0
- inpanel/mod/proftpd.py +26 -0
- inpanel/mod/pureftpd.py +26 -0
- inpanel/mod/query.py +108 -0
- inpanel/mod/server.py +1319 -0
- inpanel/mod/service.py +262 -0
- inpanel/mod/setting.py +228 -0
- inpanel/mod/shell.py +78 -0
- inpanel/mod/ssh.py +280 -0
- inpanel/mod/system.py +220 -0
- inpanel/mod/task.py +1337 -0
- inpanel/mod/ufw.py +14 -0
- inpanel/mod/user.py +306 -0
- inpanel/mod/vsftpd.py +67 -0
- inpanel/mod/yum.py +243 -0
- inpanel/plugins/__init__.py +10 -0
- inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- inpanel/plugins/acme/static/index.html +10 -0
- inpanel/plugins/acme/static/index.js +13 -0
- inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
- inpanel/plugins/shadowsocks-libev/info.py +73 -0
- inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
- inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
- inpanel/public/css/global.css +138 -0
- inpanel/public/favicon.ico +0 -0
- inpanel/public/images/alipay.jpg +0 -0
- inpanel/public/images/loading.gif +0 -0
- inpanel/public/images/weixin.jpg +0 -0
- inpanel/public/index.html +94 -0
- inpanel/public/js/controllers/controllers.js +566 -0
- inpanel/public/js/controllers/database.js +590 -0
- inpanel/public/js/controllers/ecs.js +624 -0
- inpanel/public/js/controllers/file.js +940 -0
- inpanel/public/js/controllers/plugins.js +105 -0
- inpanel/public/js/controllers/service.js +1460 -0
- inpanel/public/js/controllers/site.js +1241 -0
- inpanel/public/js/controllers/utils.js +2319 -0
- inpanel/public/js/core.js +221 -0
- inpanel/public/js/directives.js +784 -0
- inpanel/public/js/filters.js +395 -0
- inpanel/public/js/services.js +257 -0
- inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
- inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
- inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
- inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
- inpanel/public/lib/bootstrap/js/npm.js +13 -0
- inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
- inpanel/public/lib/codemirror/codemirror.css +173 -0
- inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
- inpanel/public/lib/md5/md5.js +379 -0
- inpanel/public/lib/md5/md5.min.js +1 -0
- inpanel/public/partials/database/index.html +172 -0
- inpanel/public/partials/database/mysql/dbedit.html +462 -0
- inpanel/public/partials/database/mysql/dbnew.html +348 -0
- inpanel/public/partials/database/mysql/useredit.html +310 -0
- inpanel/public/partials/database/mysql/usernew.html +97 -0
- inpanel/public/partials/directives/message.html +20 -0
- inpanel/public/partials/directives/navbar.html +25 -0
- inpanel/public/partials/directives/selector.html +57 -0
- inpanel/public/partials/directives/srvbase.html +72 -0
- inpanel/public/partials/directives/srvext.html +34 -0
- inpanel/public/partials/directives/srvfile.html +23 -0
- inpanel/public/partials/directives/srvinstall.html +34 -0
- inpanel/public/partials/directives/srvlog.html +23 -0
- inpanel/public/partials/directives/srvminiop.html +28 -0
- inpanel/public/partials/directives/srvuninstall.html +38 -0
- inpanel/public/partials/directives/srvupdate.html +30 -0
- inpanel/public/partials/ecs/account.html +156 -0
- inpanel/public/partials/ecs/ecs.html +198 -0
- inpanel/public/partials/ecs/index.html +40 -0
- inpanel/public/partials/ecs/setings.html +459 -0
- inpanel/public/partials/file/file.html +501 -0
- inpanel/public/partials/file/trash.html +95 -0
- inpanel/public/partials/ftp.html +36 -0
- inpanel/public/partials/log.html +83 -0
- inpanel/public/partials/login.html +79 -0
- inpanel/public/partials/logout.html +22 -0
- inpanel/public/partials/main.html +534 -0
- inpanel/public/partials/plugins/index.html +55 -0
- inpanel/public/partials/plugins/plugins.html +13 -0
- inpanel/public/partials/secure.html +13 -0
- inpanel/public/partials/service/apache.html +147 -0
- inpanel/public/partials/service/cron.html +71 -0
- inpanel/public/partials/service/index.html +293 -0
- inpanel/public/partials/service/iptables.html +40 -0
- inpanel/public/partials/service/lighttpd.html +63 -0
- inpanel/public/partials/service/mariadb.html +113 -0
- inpanel/public/partials/service/memcache.html +36 -0
- inpanel/public/partials/service/minio.html +44 -0
- inpanel/public/partials/service/mongodb.html +44 -0
- inpanel/public/partials/service/mysql.html +113 -0
- inpanel/public/partials/service/named.html +61 -0
- inpanel/public/partials/service/nginx.html +296 -0
- inpanel/public/partials/service/ntp.html +40 -0
- inpanel/public/partials/service/php.html +250 -0
- inpanel/public/partials/service/proftpd.html +59 -0
- inpanel/public/partials/service/pureftpd.html +59 -0
- inpanel/public/partials/service/redis.html +40 -0
- inpanel/public/partials/service/samba.html +58 -0
- inpanel/public/partials/service/sendmail.html +40 -0
- inpanel/public/partials/service/ssh.html +186 -0
- inpanel/public/partials/service/tomcat.html +65 -0
- inpanel/public/partials/service/vsftpd.html +195 -0
- inpanel/public/partials/setting.html +299 -0
- inpanel/public/partials/site/apache/site.html +277 -0
- inpanel/public/partials/site/index.html +344 -0
- inpanel/public/partials/site/nginx/site.html +558 -0
- inpanel/public/partials/sorry.html +19 -0
- inpanel/public/partials/storage/autofm.html +103 -0
- inpanel/public/partials/storage/backup.html +42 -0
- inpanel/public/partials/storage/index.html +360 -0
- inpanel/public/partials/storage/migrate.html +93 -0
- inpanel/public/partials/storage/movedata.html +98 -0
- inpanel/public/partials/storage/remote.html +77 -0
- inpanel/public/partials/utils/cron.html +486 -0
- inpanel/public/partials/utils/firewall.html +266 -0
- inpanel/public/partials/utils/index.html +193 -0
- inpanel/public/partials/utils/network.html +118 -0
- inpanel/public/partials/utils/process.html +239 -0
- inpanel/public/partials/utils/repository.html +373 -0
- inpanel/public/partials/utils/shell.html +28 -0
- inpanel/public/partials/utils/ssl.html +319 -0
- inpanel/public/partials/utils/time.html +109 -0
- inpanel/public/partials/utils/user.html +359 -0
- inpanel/public/robots.txt +2 -0
- inpanel/remote.py +109 -0
- inpanel/templates/file/preview.html +34 -0
- inpanel/templates/index.html +93 -0
- inpanel/utils.py +282 -0
- inpanel/web.py +1811 -0
- inpanel/yum.py +756 -0
- inpanel-1.2.1.dist-info/METADATA +399 -0
- inpanel-1.2.1.dist-info/RECORD +198 -0
- inpanel-1.2.1.dist-info/WHEEL +5 -0
- inpanel-1.2.1.dist-info/entry_points.txt +2 -0
- inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
- 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
|
+
|