diff --git a/README.md b/README.md
new file mode 100644
index 00000000..666efb42
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# Crafty Controller 4.0.0-alpha.2
+> Python based Control Panel for your Minecraft Server
+
+## What is Crafty Controller?
+Crafty Controller is a Minecraft Server Control Panel / Launcher. The purpose
+of Crafty Controller is to launch a Minecraft Server in the background and present
+a web interface for the server administrators to interact with their servers. Crafty
+is compatible with Docker, Linux, Windows 7, Windows 8 and Windows 10.
+
+## Documentation
+Temporary documentation available on [GitLab](https://gitlab.com/crafty-controller/crafty-commander/wikis/home)
+
+## Meta
+Project Homepage - https://craftycontrol.com
+
+Discord Server - https://discord.gg/9VJPhCE
+
+Git Repository - https://gitlab.com/crafty-controller/crafty-web
\ No newline at end of file
diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py
index dc76bffa..1006ab2c 100644
--- a/app/classes/minecraft/serverjars.py
+++ b/app/classes/minecraft/serverjars.py
@@ -71,6 +71,29 @@ class ServerJars:
data = self._read_cache()
return data.get('servers')
+ def get_serverjar_data_sorted(self):
+ data = self.get_serverjar_data()
+
+ def str_to_int(x, counter=0):
+ try:
+ return ord(x[0]) + str_to_int(x[1:], counter + 1) + len(x)
+ except IndexError:
+ return 0
+
+ def to_int(x):
+ try:
+ return int(x)
+ except ValueError:
+ temp = x.split('-')
+ return to_int(temp[0]) + str_to_int(temp[1]) / 100000
+
+ sort_key_fn = lambda x: [to_int(y) for y in x.split('.')]
+
+ for key in data.keys():
+ data[key] = sorted(data[key], key=sort_key_fn)
+
+ return data
+
def _check_api_alive(self):
logger.info("Checking serverjars.com API status")
diff --git a/app/classes/shared/controller.py b/app/classes/shared/controller.py
index ce021d37..26d0411f 100644
--- a/app/classes/shared/controller.py
+++ b/app/classes/shared/controller.py
@@ -316,7 +316,7 @@ class Controller:
server_command = 'java -Xms{}M -Xmx{}M -jar {} nogui'.format(helper.float_to_string(min_mem),
helper.float_to_string(max_mem),
full_jar_path)
- print('command: ' + server_command)
+ logger.debug('command: ' + server_command)
server_log_file = "{}/logs/latest.log".format(new_server_dir)
server_stop = "stop"
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 5c3c8343..424d89ff 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -159,7 +159,7 @@ class Helpers:
version = "{}.{}.{}-{}".format(version_data.get('major', '?'),
version_data.get('minor', '?'),
version_data.get('sub', '?'),
- version_data.get('patch', '?'))
+ version_data.get('meta', '?'))
return str(version)
def do_exit(self):
@@ -195,6 +195,9 @@ class Helpers:
(r'(\[.+?/ERROR\])', r'\1'),
(r'(\w+?\[/\d+?\.\d+?\.\d+?\.\d+?\:\d+?\])', r'\1'),
(r'\[(\d\d:\d\d:\d\d)\]', r'[\1]'),
+ (r'(\[.+? INFO\])', r'\1'),
+ (r'(\[.+? WARN\])', r'\1'),
+ (r'(\[.+? ERROR\])', r'\1')
]
# highlight users keywords
@@ -590,4 +593,10 @@ class Helpers:
return True
+ @staticmethod
+ def remove_prefix(text, prefix):
+ if text.startswith(prefix):
+ return text[len(prefix):]
+ return text
+
helper = Helpers()
diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py
index 2266b256..e698226e 100644
--- a/app/classes/shared/server.py
+++ b/app/classes/shared/server.py
@@ -11,6 +11,7 @@ import schedule
import logging.config
import zipfile
from threading import Thread
+import html
from app.classes.shared.helpers import helper
@@ -29,6 +30,48 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1)
+class ServerOutBuf:
+ lines = {}
+ def __init__(self, p, server_id):
+ self.p = p
+ self.server_id = str(server_id)
+ # Buffers text for virtual_terminal_lines config number of lines
+ self.max_lines = helper.get_setting('virtual_terminal_lines')
+ self.line_buffer = ''
+ ServerOutBuf.lines[self.server_id] = []
+
+ def check(self):
+ while self.p.isalive():
+ char = self.p.read(1)
+ if char == os.linesep:
+ ServerOutBuf.lines[self.server_id].append(self.line_buffer)
+ self.new_line_handler(self.line_buffer)
+ self.line_buffer = ''
+ # Limit list length to self.max_lines:
+ if len(ServerOutBuf.lines[self.server_id]) > self.max_lines:
+ ServerOutBuf.lines[self.server_id].pop(0)
+ else:
+ self.line_buffer += char
+
+ def new_line_handler(self, new_line):
+ new_line = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', new_line)
+ new_line = re.sub('[A-z]{2}\b\b', '', new_line)
+ highlighted = helper.log_colors(html.escape(new_line))
+
+ logger.debug('Broadcasting new virtual terminal line')
+
+ # TODO: Do not send data to clients who do not have permission to view this server's console
+ websocket_helper.broadcast_page_params(
+ '/panel/server_detail',
+ {
+ 'id': self.server_id
+ },
+ 'vterm_new_line',
+ {
+ 'line': highlighted + '
'
+ }
+ )
+
class Server:
@@ -88,7 +131,7 @@ class Server:
def run_threaded_server(self):
# start the server
- self.server_thread = threading.Thread(target=self.start_server, daemon=True)
+ self.server_thread = threading.Thread(target=self.start_server, daemon=True, name='{}_server_thread'.format(self.server_id))
self.server_thread.start()
def setup_server_run_command(self):
@@ -137,6 +180,7 @@ class Server:
logger.info("Linux Detected")
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
+<<<<<<< app/classes/shared/server.py
try:
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
except Exception as ex:
@@ -148,6 +192,15 @@ class Server:
return False
websocket_helper.broadcast('send_start_reload', {
})
+=======
+
+ self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
+ out_buf = ServerOutBuf(self.process, self.server_id)
+
+ logger.debug('Starting virtual terminal listener for server {}'.format(self.name))
+ threading.Thread(target=out_buf.check, daemon=True, name='{}_virtual_terminal'.format(self.server_id)).start()
+
+>>>>>>> app/classes/shared/server.py
self.is_crashed = False
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py
index 20b0ffa4..572918db 100644
--- a/app/classes/shared/tasks.py
+++ b/app/classes/shared/tasks.py
@@ -210,7 +210,7 @@ class TasksManager:
host_stats = db_helper.get_latest_hosts_stats()
if len(websocket_helper.clients) > 0:
# There are clients
- websocket_helper.broadcast('update_host_stats', {
+ websocket_helper.broadcast_page('/panel/dashboard', 'update_host_stats', {
'cpu_usage': host_stats.get('cpu_usage'),
'cpu_cores': host_stats.get('cpu_cores'),
'cpu_cur_freq': host_stats.get('cpu_cur_freq'),
@@ -218,13 +218,9 @@ class TasksManager:
'mem_percent': host_stats.get('mem_percent'),
'mem_usage': host_stats.get('mem_usage')
})
- time.sleep(4)
- else:
- # Stats are same
- time.sleep(8)
+ time.sleep(4)
def log_watcher(self):
- console.debug('in log_watcher')
helper.check_for_old_logs(db_helper)
schedule.every(6).hours.do(lambda: helper.check_for_old_logs(db_helper)).tag('log-mgmt')
diff --git a/app/classes/shared/translation.py b/app/classes/shared/translation.py
index 5809e592..8099ac09 100644
--- a/app/classes/shared/translation.py
+++ b/app/classes/shared/translation.py
@@ -13,19 +13,19 @@ class Translation():
self.translations_path = os.path.join(helper.root_dir, 'app', 'translations')
self.cached_translation = None
self.cached_translation_lang = None
+ self.lang_file_exists = []
def translate(self, page, word):
translated_word = None
lang = helper.get_setting('language')
fallback_lang = 'en_EN'
- lang_file_exists = helper.check_file_exists(
- os.path.join(
- self.translations_path, lang + '.json'
- )
- )
+ if lang not in self.lang_file_exists and \
+ helper.check_file_exists(os.path.join(self.translations_path, lang + '.json')):
+ self.lang_file_exists.append(lang)
+
translated_word = self.translate_inner(page, word, lang) \
- if lang_file_exists else self.translate_inner(page, word, fallback_lang)
+ if lang in self.lang_file_exists else self.translate_inner(page, word, fallback_lang)
if translated_word:
if isinstance(translated_word, dict): return json.dumps(translated_word)
diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py
index 700994b6..bca9397e 100644
--- a/app/classes/web/ajax_handler.py
+++ b/app/classes/web/ajax_handler.py
@@ -5,12 +5,15 @@ import tornado.escape
import bleach
import os
import shutil
+import html
+import re
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.models import db_helper
from app.classes.shared.helpers import helper
+from app.classes.shared.server import ServerOutBuf
logger = logging.getLogger(__name__)
@@ -47,7 +50,7 @@ class AjaxHandler(BaseHandler):
if server_id is None:
logger.warning("Server ID not found in server_log ajax call")
self.redirect("/panel/error?error=Server ID Not Found")
- return False
+ return
server_id = bleach.clean(server_id)
@@ -55,20 +58,23 @@ class AjaxHandler(BaseHandler):
if not server_data:
logger.warning("Server Data not found in server_log ajax call")
self.redirect("/panel/error?error=Server ID Not Found")
+ return
if not server_data['log_path']:
logger.warning("Log path not found in server_log ajax call ({})".format(server_id))
if full_log:
log_lines = helper.get_setting('max_log_lines')
+ data = helper.tail_file(server_data['log_path'], log_lines)
else:
- log_lines = helper.get_setting('virtual_terminal_lines')
+ data = ServerOutBuf.lines.get(server_id, [])
- data = helper.tail_file(server_data['log_path'], log_lines)
for d in data:
try:
- line = helper.log_colors(d)
+ d = re.sub('(\033\\[(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> )', '', d)
+ d = re.sub('[A-z]{2}\b\b', '', d)
+ line = helper.log_colors(html.escape(d))
self.write('{}
'.format(line))
# self.write(d.encode("utf-8"))
@@ -85,14 +91,14 @@ class AjaxHandler(BaseHandler):
file_path = self.get_argument('file_path', None)
server_id = self.get_argument('id', None)
- if not self.check_server_id(server_id, 'get_file'): return False
+ if not self.check_server_id(server_id, 'get_file'): return
else: server_id = bleach.clean(server_id)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in get_file ajax call ({})".format(file_path))
console.warning("Invalid path in get_file ajax call ({})".format(file_path))
- return False
+ return
error = None
@@ -113,7 +119,7 @@ class AjaxHandler(BaseHandler):
elif page == "get_tree":
server_id = self.get_argument('id', None)
- if not self.check_server_id(server_id, 'get_tree'): return False
+ if not self.check_server_id(server_id, 'get_tree'): return
else: server_id = bleach.clean(server_id)
self.write(db_helper.get_server_data_by_id(server_id)['path'] + '\n' +
@@ -149,16 +155,15 @@ class AjaxHandler(BaseHandler):
file_name = self.get_body_argument('file_name', default=None, strip=True)
file_path = os.path.join(file_parent, file_name)
server_id = self.get_argument('id', None)
- print(server_id)
- if not self.check_server_id(server_id, 'create_file'): return False
+ if not self.check_server_id(server_id, 'create_file'): return
else: server_id = bleach.clean(server_id)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in create_file ajax call ({})".format(file_path))
console.warning("Invalid path in create_file ajax call ({})".format(file_path))
- return False
+ return
# Create the file by opening it
with open(file_path, 'w') as file_object:
@@ -169,16 +174,15 @@ class AjaxHandler(BaseHandler):
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
dir_path = os.path.join(dir_parent, dir_name)
server_id = self.get_argument('id', None)
- print(server_id)
- if not self.check_server_id(server_id, 'create_dir'): return False
+ if not self.check_server_id(server_id, 'create_dir'): return
else: server_id = bleach.clean(server_id)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
- return False
+ return
# Create the directory
os.mkdir(dir_path)
@@ -191,7 +195,7 @@ class AjaxHandler(BaseHandler):
console.warning("delete {} for server {}".format(file_path, server_id))
- if not self.check_server_id(server_id, 'del_file'): return False
+ if not self.check_server_id(server_id, 'del_file'): return
else: server_id = bleach.clean(server_id)
server_info = db_helper.get_server_data_by_id(server_id)
@@ -200,7 +204,7 @@ class AjaxHandler(BaseHandler):
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in del_file ajax call ({})".format(file_path))
console.warning("Invalid path in del_file ajax call ({})".format(file_path))
- return False
+ return
# Delete the file
os.remove(file_path)
@@ -208,11 +212,10 @@ class AjaxHandler(BaseHandler):
elif page == "del_dir":
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
server_id = self.get_argument('id', None)
- print(server_id)
console.warning("delete {} for server {}".format(dir_path, server_id))
- if not self.check_server_id(server_id, 'del_dir'): return False
+ if not self.check_server_id(server_id, 'del_dir'): return
else: server_id = bleach.clean(server_id)
server_info = db_helper.get_server_data_by_id(server_id)
@@ -220,7 +223,7 @@ class AjaxHandler(BaseHandler):
or not helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in del_file ajax call ({})".format(dir_path))
console.warning("Invalid path in del_file ajax call ({})".format(dir_path))
- return False
+ return
# Delete the directory
# os.rmdir(dir_path) # Would only remove empty directories
@@ -232,18 +235,15 @@ class AjaxHandler(BaseHandler):
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
- print(file_contents)
- print(file_path)
- print(server_id)
- if not self.check_server_id(server_id, 'save_file'): return False
+ if not self.check_server_id(server_id, 'save_file'): return
else: server_id = bleach.clean(server_id)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in save_file ajax call ({})".format(file_path))
console.warning("Invalid path in save_file ajax call ({})".format(file_path))
- return False
+ return
# Open the file in write mode and store the content in file_object
with open(file_path, 'w') as file_object:
@@ -253,21 +253,20 @@ class AjaxHandler(BaseHandler):
item_path = self.get_body_argument('item_path', default=None, strip=True)
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
server_id = self.get_argument('id', None)
- print(server_id)
- if not self.check_server_id(server_id, 'rename_item'): return False
+ if not self.check_server_id(server_id, 'rename_item'): return
else: server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_item ajax call")
console.warning("Invalid path(s) in rename_item ajax call")
- return False
+ return
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], item_path) \
or not helper.check_path_exists(os.path.abspath(item_path)):
logger.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
console.warning("Invalid old name path in rename_item ajax call ({})".format(server_id))
- return False
+ return
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
@@ -275,7 +274,7 @@ class AjaxHandler(BaseHandler):
or helper.check_path_exists(os.path.abspath(new_item_path)):
logger.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
console.warning("Invalid new name path in rename_item ajax call ({})".format(server_id))
- return False
+ return
# RENAME
os.rename(item_path, new_item_path)
@@ -283,7 +282,7 @@ class AjaxHandler(BaseHandler):
if server_id is None:
logger.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
console.warning("Server ID not defined in {} ajax call ({})".format(page_name, server_id))
- return False
+ return
else:
server_id = bleach.clean(server_id)
@@ -291,5 +290,5 @@ class AjaxHandler(BaseHandler):
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
console.warning("Server ID not found in {} ajax call ({})".format(page_name, server_id))
- return False
+ return
return True
diff --git a/app/classes/web/api_handler.py b/app/classes/web/api_handler.py
index 933b6235..50035c9f 100644
--- a/app/classes/web/api_handler.py
+++ b/app/classes/web/api_handler.py
@@ -41,11 +41,11 @@ class ApiHandler(BaseHandler):
else:
logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token")
- return False
+ return
except Exception as e:
log.warning("An error occured while authenticating an API user: %s", e)
self.access_denied("unknown"), "an error occured while authenticating the user"
- return False
+ return
class ServersStats(ApiHandler):
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index ebb2dfd9..bbe9ef7d 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -201,7 +201,7 @@ class PanelHandler(BaseHandler):
#if not db_helper.server_id_authorized(server_id, exec_user_id):
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
self.redirect("/panel/error?error=Invalid Server ID")
- return False
+ return
server_info = db_helper.get_server_data_by_id(server_id)
backup_file = os.path.abspath(os.path.join(server_info["backup_path"], file))
@@ -250,7 +250,7 @@ class PanelHandler(BaseHandler):
#if not db_helper.server_id_authorized(server_id, exec_user_id):
if not db_helper.server_id_authorized_from_roles(int(server_id), exec_user_id):
self.redirect("/panel/error?error=Invalid Server ID")
- return False
+ return
server = self.controller.get_server_obj(server_id).backup_server()
self.redirect("/panel/server_detail?id={}&subpage=backup".format(server_id))
diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py
index 3e9913db..4388f909 100644
--- a/app/classes/web/public_handler.py
+++ b/app/classes/web/public_handler.py
@@ -37,9 +37,6 @@ class PublicHandler(BaseHandler):
def get(self, page=None):
- self.clear_cookie("user")
- self.clear_cookie("user_data")
-
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
page_data = {
@@ -59,9 +56,16 @@ class PublicHandler(BaseHandler):
elif page == "error":
template = "public/error.html"
+ elif page == "logout":
+ self.clear_cookie("user")
+ self.clear_cookie("user_data")
+ self.redirect('/public/login')
+ return
+
# if we have no page, let's go to login
else:
self.redirect('/public/login')
+ return
self.render(
template,
@@ -82,14 +86,18 @@ class PublicHandler(BaseHandler):
# if we don't have a user
if not user_data:
next_page = "/public/error?error=Login Failed"
+ self.clear_cookie("user")
+ self.clear_cookie("user_data")
self.redirect(next_page)
- return False
+ return
# if they are disabled
if not user_data.enabled:
next_page = "/public/error?error=Login Failed"
+ self.clear_cookie("user")
+ self.clear_cookie("user_data")
self.redirect(next_page)
- return False
+ return
login_result = helper.verify_pass(entered_password, user_data.password)
@@ -118,6 +126,8 @@ class PublicHandler(BaseHandler):
next_page = "/panel/dashboard"
self.redirect(next_page)
else:
+ self.clear_cookie("user")
+ self.clear_cookie("user_data")
# log this failed login attempt
db_helper.add_to_audit_log(user_data.user_id, "Tried to log in", 0, self.get_remote_ip())
self.redirect('/public/error?error=Login Failed')
diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py
index bbdbbdae..1f7dea57 100644
--- a/app/classes/web/server_handler.py
+++ b/app/classes/web/server_handler.py
@@ -61,7 +61,7 @@ class ServerHandler(BaseHandler):
if page == "step1":
- page_data['server_types'] = server_jar_obj.get_serverjar_data()
+ page_data['server_types'] = server_jar_obj.get_serverjar_data_sorted()
template = "server/wizard.html"
self.render(
@@ -94,7 +94,7 @@ class ServerHandler(BaseHandler):
for server in db_helper.get_all_defined_servers():
if server['server_name'] == name:
return True
- return False
+ return
server_data = db_helper.get_server_data_by_id(server_id)
server_uuid = server_data.get('server_uuid')
@@ -105,8 +105,6 @@ class ServerHandler(BaseHandler):
name_counter += 1
new_server_name = server_data.get('server_name') + " (Copy {})".format(name_counter)
- console.debug('new_server_name: "{}"'.format(new_server_name))
-
new_server_uuid = helper.create_uuid()
while os.path.exists(os.path.join(helper.servers_dir, new_server_uuid)):
new_server_uuid = helper.create_uuid()
@@ -143,7 +141,6 @@ class ServerHandler(BaseHandler):
}).execute()
self.controller.init_all_servers()
- console.debug('initted all servers')
return
@@ -163,14 +160,14 @@ class ServerHandler(BaseHandler):
if not server_name:
self.redirect("/panel/error?error=Server name cannot be empty!")
- return False
+ return
if import_type == 'import_jar':
good_path = self.controller.verify_jar_server(import_server_path, import_server_jar)
if not good_path:
self.redirect("/panel/error?error=Server path or Server Jar not found!")
- return False
+ return
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
db_helper.add_to_audit_log(exec_user_data['user_id'],
@@ -182,12 +179,12 @@ class ServerHandler(BaseHandler):
good_path = self.controller.verify_zip_server(import_server_path)
if not good_path:
self.redirect("/panel/error?error=Zip file not found!")
- return False
+ return
new_server_id = self.controller.import_zip_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
- return False
+ return
db_helper.add_to_audit_log(exec_user_data['user_id'],
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
@@ -195,7 +192,7 @@ class ServerHandler(BaseHandler):
else:
if len(server_parts) != 2:
self.redirect("/panel/error?error=Invalid server data")
- return False
+ return
server_type, server_version = server_parts
# todo: add server type check here and call the correct server add functions if not a jar
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py
index ced565ea..e3f51619 100644
--- a/app/classes/web/tornado.py
+++ b/app/classes/web/tornado.py
@@ -159,7 +159,7 @@ class Webserver:
console.info("Server Init Complete: Listening For Connections:")
- self.ioloop = tornado.ioloop.IOLoop.instance()
+ self.ioloop = tornado.ioloop.IOLoop.current()
self.ioloop.start()
def stop_web_server(self):
diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py
index 107acdc5..ad98c2ab 100644
--- a/app/classes/web/websocket_handler.py
+++ b/app/classes/web/websocket_handler.py
@@ -1,13 +1,21 @@
import json
import logging
+import asyncio
-import tornado.websocket
-from app.classes.shared.console import console
+from urllib.parse import parse_qsl
from app.classes.shared.models import Users, db_helper
+from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__)
+try:
+ import tornado.websocket
+
+except ModuleNotFoundError as e:
+ logger.critical("Import Error: Unable to load {} module".format(e, e.name))
+ console.critical("Import Error: Unable to load {} module".format(e, e.name))
+ sys.exit(1)
class SocketHandler(tornado.websocket.WebSocketHandler):
@@ -15,6 +23,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
self.controller = controller
self.tasks_manager = tasks_manager
self.translator = translator
+ self.io_loop = tornado.ioloop.IOLoop.current()
def get_remote_ip(self):
remote_ip = self.request.headers.get("X-Real-IP") or \
@@ -35,6 +44,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
+ logger.debug('Checking WebSocket authentication')
if self.check_auth():
self.handle()
else:
@@ -42,10 +52,15 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
self.close()
db_helper.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip())
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication')
+ logger.warning('Someone tried to connect via WebSocket without proper authentication')
def handle(self):
-
- websocket_helper.addClient(self)
+ self.page = self.get_query_argument('page')
+ self.page_query_params = dict(parse_qsl(helper.remove_prefix(
+ self.get_query_argument('page_query_params'),
+ '?'
+ )))
+ websocket_helper.add_client(self)
logger.debug('Opened WebSocket connection')
# websocket_helper.broadcast('notification', 'New client connected')
@@ -56,7 +71,13 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
def on_close(self):
- websocket_helper.removeClient(self)
+ websocket_helper.remove_client(self)
logger.debug('Closed WebSocket connection')
# websocket_helper.broadcast('notification', 'Client disconnected')
+ async def write_message_int(self, message):
+ self.write_message(message)
+
+ def write_message_helper(self, message):
+ asyncio.run_coroutine_threadsafe(self.write_message_int(message), self.io_loop.asyncio_loop)
+
diff --git a/app/classes/web/websocket_helper.py b/app/classes/web/websocket_helper.py
index 37a85cb4..99935ad3 100644
--- a/app/classes/web/websocket_helper.py
+++ b/app/classes/web/websocket_helper.py
@@ -1,31 +1,75 @@
import json
import logging
+import sys, threading, asyncio
from app.classes.shared.console import console
logger = logging.getLogger(__name__)
-class WebSocketHelper:
- clients = set()
- def addClient(self, client):
+try:
+ import tornado.ioloop
+
+except ModuleNotFoundError as e:
+ logger.critical("Import Error: Unable to load {} module".format(e, e.name))
+ console.critical("Import Error: Unable to load {} module".format(e, e.name))
+ sys.exit(1)
+
+class WebSocketHelper:
+ def __init__(self):
+ self.clients = set()
+
+ def add_client(self, client):
self.clients.add(client)
- def removeClient(self, client):
- self.clients.add(client)
-
- def send_message(self, client, event_type, data):
+ def remove_client(self, client):
+ self.clients.remove(client)
+
+ def send_message(self, client, event_type: str, data):
if client.check_auth():
message = str(json.dumps({'event': event_type, 'data': data}))
- client.write_message(message)
+ client.write_message_helper(message)
- def broadcast(self, event_type, data):
- logger.debug('Sending: ' + str(json.dumps({'event': event_type, 'data': data})))
+ def broadcast(self, event_type: str, data):
+ logger.debug('Sending to {} clients: {}'.format(len(self.clients), json.dumps({'event': event_type, 'data': data})))
for client in self.clients:
try:
self.send_message(client, event_type, data)
- except:
- pass
+ except Exception as e:
+ logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
+
+ def broadcast_page(self, page: str, event_type: str, data):
+ def filter_fn(client):
+ return client.page == page
+
+ clients = list(filter(filter_fn, self.clients))
+
+ logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
+
+ for client in clients:
+ try:
+ self.send_message(client, event_type, data)
+ except Exception as e:
+ logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
+
+ def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
+ def filter_fn(client):
+ if client.page != page:
+ return False
+ for key, param in params.items():
+ if param != client.page_query_params.get(key, None):
+ return False
+ return True
+
+ clients = list(filter(filter_fn, self.clients))
+
+ logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
+
+ for client in clients:
+ try:
+ self.send_message(client, event_type, data)
+ except Exception as e:
+ logger.exception('Error catched while sending WebSocket message to {}'.format(client.get_remote_ip()))
def disconnect_all(self):
console.info('Disconnecting WebSocket clients')
diff --git a/app/config/config.json b/app/config/config.json
index f6b866f3..27f08bb8 100644
--- a/app/config/config.json
+++ b/app/config/config.json
@@ -10,7 +10,7 @@
"stats_update_frequency": 30,
"delete_default_json": false,
"show_contribute_link": true,
- "virtual_terminal_lines": 10,
+ "virtual_terminal_lines": 70,
"max_log_lines": 700,
"keywords": ["help", "chunk"]
-}
\ No newline at end of file
+}
diff --git a/app/config/logging.json b/app/config/logging.json
index 8c524b01..c6ccd7b1 100644
--- a/app/config/logging.json
+++ b/app/config/logging.json
@@ -8,7 +8,7 @@
"tornado_access": {
"format": "%(asctime)s - [Tornado] - [Access] - %(levelname)s - %(message)s"
},
- "schedule": {
+ "schedule": {
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
}
},
diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html
index 1a8e9bc1..e414341c 100644
--- a/app/frontend/templates/base.html
+++ b/app/frontend/templates/base.html
@@ -173,8 +173,9 @@
let listenEvents = [];
try {
-
- var wsInternal = new WebSocket('wss://' + location.host + '/ws');
+ pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
+ page = 'page=' + encodeURIComponent(location.pathname)
+ var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
wsInternal.onopen = function() {
console.log('opened WebSocket connection:', wsInternal)
};
diff --git a/app/frontend/templates/notify.html b/app/frontend/templates/notify.html
index 09e6c7da..5cafcacd 100644
--- a/app/frontend/templates/notify.html
+++ b/app/frontend/templates/notify.html
@@ -29,7 +29,7 @@
{% end %}
Activity
- Sign Out
+ Sign Out
\ No newline at end of file
diff --git a/app/frontend/templates/panel/server_files.html b/app/frontend/templates/panel/server_files.html
index de1cc745..fe8501f9 100644
--- a/app/frontend/templates/panel/server_files.html
+++ b/app/frontend/templates/panel/server_files.html
@@ -166,8 +166,9 @@
margin-left: 10px;
}
- /* Style the caret/arrow */
- .tree-caret {
+ /* Style the items */
+ .tree-item,
+ .files-tree-title {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
diff --git a/app/frontend/templates/panel/server_term.html b/app/frontend/templates/panel/server_term.html
index 68e75b34..4fbeee8b 100644
--- a/app/frontend/templates/panel/server_term.html
+++ b/app/frontend/templates/panel/server_term.html
@@ -65,7 +65,7 @@