Merge branch 'dev' into bug/verbose-console

This commit is contained in:
Zedifus 2023-02-17 00:47:54 +00:00
commit 69480c8ff7
38 changed files with 511 additions and 82 deletions

View File

@ -3,6 +3,9 @@
### New features
- Add better feedback for uploads with a progress bar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546))
- Add ignored exit codes for crash detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/553))
- Allow users to change the directory where Crafty Stores Servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/539)) <br>
*(Only for non-docker, docker users should change host volume mount)*
- Add host storage display option to the dashboard ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/551))
### Bug fixes
- Fix exception related to page data on server start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/544))
- Fix logical issue with uploading dynamic files ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/555))
@ -12,7 +15,8 @@
- Cleanup authentication helpers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/545))
- Optimize file upload progress WS ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546))
- Truncate sidebar servers to a max of 10 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/552))
- Upgrade to FA 6. Add Translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549))
- Upgrade to FA 6. Add Translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549))([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/558))
- Forge installer and Java Detection improvements ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/559))
### Lang
- Add additional translations to backups page strings ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/543))
- Add additional missing translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549))

View File

@ -195,3 +195,14 @@ class ManagementController:
def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
self.management_helper.del_excluded_backup_dir(server_id, dir_to_del)
# **********************************************************************************
# Crafty Methods
# **********************************************************************************
@staticmethod
def get_master_server_dir():
return HelpersManagement.get_master_server_dir()
@staticmethod
def set_master_server_dir(server_dir):
HelpersManagement.set_master_server_dir(server_dir)

View File

@ -102,6 +102,7 @@ class ServersController(metaclass=Singleton):
server_obj.server_id
)
server_instance.update_server_instance()
return ret
def get_history_stats(self, server_id, days):
@ -163,10 +164,9 @@ class ServersController(metaclass=Singleton):
return server["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")
raise Exception(f"Unable to find server object for server id {server_id}")
raise ValueError(f"Unable to find server object for server id {server_id}")
def init_all_servers(self):
servers = self.get_all_defined_servers()
self.failed_servers = []
@ -227,7 +227,6 @@ class ServersController(metaclass=Singleton):
)
def check_server_loaded(self, server_id_to_check: int):
logger.info(f"Checking to see if we already registered {server_id_to_check}")
for server in self.servers_list:

View File

@ -19,7 +19,6 @@ class Server:
self.description = data.get("description")
# print(self.description)
if isinstance(self.description, dict):
# cat server
if "translate" in self.description:
self.description = self.description["translate"]
@ -124,7 +123,7 @@ def ping(ip, port):
try:
k = sock.recv(1)
if not k:
raise Exception()
raise ValueError()
except:
return 0
k = k[0]

View File

@ -104,7 +104,6 @@ class ServerJars:
logger.error(f"Unable to update serverjars.com cache file: {e}")
def refresh_cache(self):
cache_file = self.helper.serverjar_cache
cache_old = self.helper.is_file_older_than_x_days(cache_file)

View File

@ -211,7 +211,6 @@ class Stats:
@staticmethod
def get_world_size(server_path):
total_size = 0
total_size = Helpers.get_dir_size(server_path)
@ -221,7 +220,6 @@ class Stats:
return level_total_size
def get_server_players(self, server_id):
server = HelperServers.get_server_data_by_id(server_id)
logger.info(f"Getting players for server {server}")
@ -295,7 +293,6 @@ class Stats:
@staticmethod
def parse_server_raknet_ping(ping_obj: object):
try:
server_icon = base64.encodebytes(ping_obj["icon"])
except Exception as e:

View File

@ -15,6 +15,7 @@ from app.classes.shared.permission_helper import PermissionHelper
logger = logging.getLogger(__name__)
# **********************************************************************************
# User_Crafty Class
# **********************************************************************************

View File

@ -20,6 +20,7 @@ from app.classes.shared.main_models import DatabaseShortcuts
logger = logging.getLogger(__name__)
# **********************************************************************************
# Audit_Log Class
# **********************************************************************************
@ -46,6 +47,7 @@ class CraftySettings(BaseModel):
cookie_secret = CharField(default="")
login_photo = CharField(default="login_1.jpg")
login_opacity = IntegerField(default=100)
master_server_dir = CharField(default="")
class Meta:
table_name = "crafty_settings"
@ -271,6 +273,19 @@ class HelpersManagement:
CraftySettings.id == 1
).execute()
@staticmethod
def get_master_server_dir():
settings = CraftySettings.select(CraftySettings.master_server_dir).where(
CraftySettings.id == 1
)
return settings[0].master_server_dir
@staticmethod
def set_master_server_dir(server_dir):
CraftySettings.update({CraftySettings.master_server_dir: server_dir}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
# Schedules Methods
# **********************************************************************************

View File

@ -15,6 +15,7 @@ from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__)
# **********************************************************************************
# Roles Class
# **********************************************************************************

View File

@ -16,6 +16,7 @@ from app.classes.shared.permission_helper import PermissionHelper
logger = logging.getLogger(__name__)
# **********************************************************************************
# Role Servers Class
# **********************************************************************************

View File

@ -29,6 +29,7 @@ logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger("peewee")
peewee_logger.setLevel(logging.INFO)
# **********************************************************************************
# Servers Stats Class
# **********************************************************************************

View File

@ -15,6 +15,7 @@ from app.classes.models.base_model import BaseModel
logger = logging.getLogger(__name__)
# **********************************************************************************
# Servers Model
# **********************************************************************************

View File

@ -21,6 +21,7 @@ from app.classes.models.roles import Roles, HelperRoles
logger = logging.getLogger(__name__)
# **********************************************************************************
# Users Class
# **********************************************************************************
@ -58,6 +59,7 @@ PUBLIC_USER_ATTRS: t.Final = [
"lang", # maybe remove?
]
# **********************************************************************************
# API Keys Class
# **********************************************************************************

View File

@ -76,7 +76,7 @@ class Authentication:
output = self.check(token)
if output is None:
raise Exception("Invalid token")
raise ValueError("Invalid token")
return output
def check_bool(self, token) -> bool:

View File

@ -58,7 +58,6 @@ class MainPrompt(cmd.Cmd):
Console.info("Unknown migration command")
def do_set_passwd(self, line):
try:
username = str(line).lower()
# If no user is found it returns None

View File

@ -15,6 +15,7 @@ import html
import zipfile
import pathlib
import ctypes
import shutil
import subprocess
import itertools
from datetime import datetime
@ -94,7 +95,7 @@ class Helpers:
try:
# Get tags from Gitlab, select the latest and parse the semver
response = get(
"https://gitlab.com/api/v4/projects/20430749/repository/tags"
"https://gitlab.com/api/v4/projects/20430749/repository/tags", timeout=1
)
if response.status_code == 200:
remote_version = pkg_version.parse(json.loads(response.text)[0]["name"])
@ -131,7 +132,7 @@ class Helpers:
try:
# Get minecraft server download page
# (hopefully the don't change the structure)
download_page = get(url, headers=headers)
download_page = get(url, headers=headers, timeout=1)
# Search for our string targets
win_download_url = re.search(target_win, download_page.text).group(0)
@ -145,6 +146,22 @@ class Helpers:
logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
return False
def detect_java(self):
if len(self.find_java_installs()) > 0:
return True
# We'll use this as a fallback for systems
# That do not properly setup reg keys or
# Update alternatives
if self.is_os_windows():
if shutil.which("java.exe"):
return True
else:
if shutil.which("java"):
return True
return False
@staticmethod
def find_java_installs():
# If we're windows return oracle java versions,
@ -281,7 +298,7 @@ class Helpers:
@staticmethod
def check_port(server_port):
try:
ip = get("https://api.ipify.org").content.decode("utf8")
ip = get("https://api.ipify.org", timeout=1).content.decode("utf8")
except:
ip = "google.com"
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -417,6 +434,7 @@ class Helpers:
"allow_nsfw_profile_pictures": False,
"enable_user_self_delete": False,
"reset_secrets_on_next_boot": False,
"monitored_mounts": Helpers.get_all_mounts(),
}
def get_all_settings(self):
@ -436,11 +454,27 @@ class Helpers:
return data
@staticmethod
def is_subdir(server_path, root_dir):
def get_all_mounts():
mounts = []
for item in psutil.disk_partitions(all=False):
mounts.append(item.mountpoint)
return mounts
def is_subdir(self, server_path, root_dir):
server_path = os.path.realpath(server_path)
root_dir = os.path.realpath(root_dir)
relative = os.path.relpath(server_path, root_dir)
if self.is_os_windows():
try:
relative = os.path.relpath(server_path, root_dir)
except:
# Windows will crash out if two paths are on different
# Drives We can happily return false if this is the case.
# Since two different drives will not be relative to eachother.
return False
else:
relative = os.path.relpath(server_path, root_dir)
if relative.startswith(os.pardir):
return False
@ -596,7 +630,6 @@ class Helpers:
# open our file
with open(file_name, "r", encoding="utf-8") as f:
# seek
f.seek(0, 2)
@ -752,7 +785,7 @@ class Helpers:
use_ssl=True,
) # + "?d=404"
try:
if requests.head(url).status_code != 404:
if requests.head(url, timeout=1).status_code != 404:
profile_url = url
except Exception as e:
logger.debug(f"Could not pull resource from Gravatar with error {e}")
@ -761,7 +794,6 @@ class Helpers:
@staticmethod
def get_file_contents(path: str, lines=100):
contents = ""
if os.path.exists(path) and os.path.isfile(path):
@ -782,12 +814,10 @@ class Helpers:
return False
def create_session_file(self, ignore=False):
if ignore and os.path.exists(self.session_file):
os.remove(self.session_file)
if os.path.exists(self.session_file):
file_data = self.get_file_contents(self.session_file)
try:
data = json.loads(file_data)
@ -887,15 +917,16 @@ class Helpers:
try:
os.makedirs(path)
logger.debug(f"Created Directory : {path}")
return True
# directory already exists - non-blocking error
except FileExistsError:
pass
return True
except PermissionError as e:
logger.critical(f"Check generated exception due to permssion error: {e}")
return False
def create_self_signed_cert(self, cert_dir=None):
if cert_dir is None:
cert_dir = os.path.join(self.config_dir, "web", "certs")
@ -977,6 +1008,15 @@ class Helpers:
def is_os_windows():
return os.name == "nt"
@staticmethod
def is_env_docker():
path = "/proc/self/cgroup"
return (
os.path.exists("/.dockerenv")
or os.path.isfile(path)
and any("docker" in line for line in open(path, encoding="utf-8"))
)
@staticmethod
def wtol_path(w_path):
l_path = w_path.replace("\\", "/")
@ -1047,7 +1087,6 @@ class Helpers:
return output
def generate_dir(self, folder, output=""):
dir_list = []
unsorted_files = []
file_list = os.listdir(folder)

View File

@ -226,7 +226,6 @@ class ImportHelpers:
download_thread.start()
def download_threaded_bedrock_server(self, path, new_id):
# downloads zip from remote url
try:
bedrock_url = Helpers.get_latest_bedrock_url()

View File

@ -10,7 +10,6 @@ class Install:
)
def do_install(self):
# are we in a venv?
if not self.is_venv():
print("Crafty Requires a venv to install")

View File

@ -6,6 +6,7 @@ import platform
import shutil
import time
import logging
import threading
from peewee import DoesNotExist
# TZLocal is set as a hidden import on win pipeline
@ -347,7 +348,7 @@ class Controller:
elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory
server_file = create_data["jarfile"]
raise Exception("Not yet implemented")
raise NotImplementedError("Not yet implemented")
_create_server_properties_if_needed(
create_data["server_properties_port"],
)
@ -379,7 +380,7 @@ class Controller:
logger.error(f"Server import failed with error: {ex}")
elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory
raise Exception("Not yet implemented")
raise NotImplementedError("Not yet implemented")
_create_server_properties_if_needed(0, True)
@ -401,7 +402,7 @@ class Controller:
logger.error(f"Server import failed with error: {ex}")
elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory
raise Exception("Not yet implemented")
raise NotImplementedError("Not yet implemented")
_create_server_properties_if_needed(0, True)
@ -941,7 +942,6 @@ class Controller:
def remove_server(self, server_id, files):
counter = 0
for server in self.servers.servers_list:
# if this is the droid... im mean server we are looking for...
if str(server["server_id"]) == str(server_id):
server_data = self.servers.get_server_data(server_id)
@ -1003,3 +1003,122 @@ class Controller:
@staticmethod
def clear_support_status():
HelperUsers.clear_support_status()
def set_master_server_dir(self, server_dir):
# This method should only be used on a first run basis if the server dir is ""
self.helper.servers_dir = server_dir
HelpersManagement.set_master_server_dir(server_dir)
def update_master_server_dir(self, server_dir, user_id):
move_thread = threading.Thread(
name="dir_move",
target=self.t_update_master_server_dir,
daemon=True,
args=(
server_dir,
user_id,
),
)
move_thread.start()
def t_update_master_server_dir(self, server_dir, user_id):
server_dir = self.helper.wtol_path(server_dir)
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config", "move_status", "Checking dir"
)
current_master = self.helper.wtol_path(
HelpersManagement.get_master_server_dir()
)
if current_master == server_dir:
logger.info(
"Admin tried to change server dir to current server dir. Canceling..."
)
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config",
"move_status",
"done",
)
return
if self.helper.is_subdir(server_dir, current_master):
logger.info(
"Admin tried to change server dir to be inside a sub directory of the"
" current server dir. This will result in a copy loop."
)
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config",
"move_status",
"done",
)
return
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config", "move_status", "Checking permissions"
)
if not self.helper.ensure_dir_exists(os.path.join(server_dir, "servers")):
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": "Crafty failed to move server dir. "
"It seems Crafty lacks permission to write to "
"the new directory."
},
)
return
# set the cached serve dir
self.helper.servers_dir = server_dir
# set DB server dir
HelpersManagement.set_master_server_dir(server_dir)
servers = self.servers.get_all_defined_servers()
# move the servers
for server in servers:
server_path = server.get("path")
new_server_path = os.path.join(
server_dir, "servers", server.get("server_uuid")
)
if os.path.isdir(server_path):
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config",
"move_status",
f"Moving {server.get('server_name')}",
)
try:
self.file_helper.move_dir(
server_path,
new_server_path,
)
except FileExistsError as e:
logger.error(f"Failed to move server with error: {e}")
server_obj = self.servers.get_server_obj(server.get("server_id"))
# reset executable path
if current_master in server["executable"]:
server_obj.executable = str(server["executable"]).replace(
current_master, server_dir
)
# reset run command path
if current_master in server["execution_command"]:
server_obj.execution_command = str(server["execution_command"]).replace(
current_master, server_dir
)
# reset log path
if current_master in server["log_path"]:
server_obj.log_path = str(server["log_path"]).replace(
current_master, server_dir
)
server_obj.path = new_server_path
failed = False
for s in self.servers.failed_servers:
if int(s["server_id"]) == int(server.get("server_id")):
failed = True
if not failed:
self.servers.update_server(server_obj)
else:
self.servers.update_unloaded_server(server_obj)
self.servers.init_all_servers()
self.helper.websocket_helper.broadcast_page(
"/panel/panel_config",
"move_status",
"done",
)

View File

@ -158,6 +158,8 @@ class ServerInstance:
self.jar_update_url = server_data.executable_update_url
self.name = server_data.server_name
self.server_object = server_data
self.stats_helper.select_database()
self.reload_server_settings()
def reload_server_settings(self):
server_data = HelperServers.get_server_data_by_id(self.server_id)
@ -445,7 +447,7 @@ class ServerInstance:
)
except Exception as ex:
# Checks for java on initial fail
if os.system("java -version") == 32512:
if not self.helper.detect_java():
if user_id:
self.helper.websocket_helper.broadcast_user(
user_id,
@ -590,7 +592,6 @@ class ServerInstance:
# We need to grab the exact forge version number.
# We know we can find it here in the run.sh/bat script.
try:
# Getting the forge version from the executable command
version = re.findall(
r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
@ -850,7 +851,6 @@ class ServerInstance:
return True
def crash_detected(self, name):
# clear the old scheduled watcher task
self.server_scheduler.remove_job(f"c_{self.server_id}")
# remove the stats polling job since server is stopped
@ -912,7 +912,6 @@ class ServerInstance:
return self.process.pid if self.process is not None else None
def detect_crash(self):
logger.info(f"Detecting possible crash for server: {self.name} ")
running = self.check_running()
@ -935,7 +934,6 @@ class ServerInstance:
self.stats_helper.sever_crashed()
# if we haven't tried to restart more 3 or more times
if self.restart_count <= 3:
# start the server if needed
server_restarted = self.crash_detected(self.name)
@ -1461,7 +1459,6 @@ class ServerInstance:
Console.critical("Can't broadcast server status to websocket")
def get_servers_stats(self):
server_stats = {}
logger.info("Getting Stats for Server " + self.name + " ...")
@ -1548,7 +1545,6 @@ class ServerInstance:
return server_stats
def get_server_players(self):
server = HelperServers.get_server_data_by_id(self.server_id)
logger.info(f"Getting players for server {server}")
@ -1569,7 +1565,6 @@ class ServerInstance:
return []
def get_raw_server_stats(self, server_id):
try:
server = HelperServers.get_server_obj(server_id)
except:
@ -1718,7 +1713,6 @@ class ServerInstance:
return server_stats
def record_server_stats(self):
server_stats = self.get_servers_stats()
self.stats_helper.insert_server_stats(server_stats)

View File

@ -4,6 +4,7 @@ import logging
import threading
import asyncio
import datetime
import json
from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError
@ -672,7 +673,6 @@ class TasksManager:
host_stats = HelpersManagement.get_latest_hosts_stats()
while True:
if host_stats.get(
"cpu_usage"
) != HelpersManagement.get_latest_hosts_stats().get(
@ -687,18 +687,37 @@ class TasksManager:
host_stats = HelpersManagement.get_latest_hosts_stats()
if len(self.helper.websocket_helper.clients) > 0:
# There are clients
self.helper.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"),
"cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"),
},
)
try:
self.helper.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"),
"cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"),
"disk_usage": json.loads(
host_stats.get("disk_json").replace("'", '"')
),
"mounts": self.helper.get_setting("monitored_mounts"),
},
)
except:
self.helper.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"),
"cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"),
"disk_usage": {},
},
)
time.sleep(1)
def check_for_updates(self):

View File

@ -575,6 +575,31 @@ class AjaxHandler(BaseHandler):
self.controller.server_jars.manual_refresh_cache()
return
elif page == "update_server_dir":
for server in self.controller.servers.get_all_servers_stats():
if server["stats"]["running"]:
self.helper.websocket_helper.broadcast_user(
exec_user["user_id"],
"send_start_error",
{
"error": "You must stop all servers before "
"starting a storage migration."
},
)
return
if not superuser:
self.redirect("/panel/error?error=Not a super user")
return
if self.helper.is_env_docker():
self.redirect(
"/panel/error?error=This feature is not"
" supported on docker environments"
)
return
new_dir = urllib.parse.unquote(self.get_argument("server_dir"))
self.controller.update_master_server_dir(new_dir, exec_user["user_id"])
return
@tornado.web.authenticated
def delete(self, page):
api_key, _, exec_user = self.current_user

View File

@ -6,7 +6,6 @@ logger = logging.getLogger(__name__)
class DefaultHandler(BaseHandler):
# Override prepare() instead of get() to cover all possible HTTP methods.
def prepare(self, page=None): # pylint: disable=arguments-differ
if page is not None:

View File

@ -290,9 +290,11 @@ class PanelHandler(BaseHandler):
page_data: t.Dict[str, t.Any] = {
# todo: make this actually pull and compare version data
"update_available": self.helper.update_available,
"docker": self.helper.is_env_docker(),
"background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
"serverTZ": tz,
"monitored": self.helper.get_setting("monitored_mounts"),
"version_data": self.helper.get_version_string(),
"failed_servers": self.controller.servers.failed_servers,
"user_data": exec_user,
@ -332,7 +334,12 @@ class PanelHandler(BaseHandler):
else None,
"superuser": superuser,
}
try:
page_data["hosts_data"]["disk_json"] = json.loads(
page_data["hosts_data"]["disk_json"].replace("'", '"')
)
except:
page_data["hosts_data"]["disk_json"] = {}
if page == "unauthorized":
template = "panel/denied.html"
@ -841,6 +848,9 @@ class PanelHandler(BaseHandler):
page_data["auth-servers"] = auth_servers
page_data["role-servers"] = auth_role_servers
page_data["user-roles"] = user_roles
page_data[
"servers_dir"
] = self.controller.management.get_master_server_dir()
page_data["users"] = self.controller.users.user_query(exec_user["user_id"])
page_data["roles"] = self.controller.users.user_role_query(
@ -880,6 +890,7 @@ class PanelHandler(BaseHandler):
page_data["config-json"] = data
page_data["availables_languages"] = []
page_data["all_languages"] = []
page_data["all_partitions"] = self.helper.get_all_mounts()
for file in sorted(
os.listdir(

View File

@ -10,7 +10,6 @@ logger = logging.getLogger(__name__)
class PublicHandler(BaseHandler):
def set_current_user(self, user_id: str = None):
expire_days = self.helper.get_setting("cookie_expire")
# if helper comes back with false
@ -29,7 +28,6 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data")
def get(self, page=None):
error = bleach.clean(self.get_argument("error", "Invalid Login!"))
error_msg = bleach.clean(self.get_argument("error_msg", ""))
@ -81,7 +79,6 @@ class PublicHandler(BaseHandler):
)
def post(self, page=None):
error = bleach.clean(self.get_argument("error", "Invalid Login!"))
error_msg = bleach.clean(self.get_argument("error_msg", ""))
@ -96,7 +93,6 @@ class PublicHandler(BaseHandler):
page_data["query"] = self.request.query
if page == "login":
next_page = "/login"
if self.request.query:
next_page = "/login?" + self.request.query

View File

@ -26,7 +26,6 @@ login_schema = {
class ApiAuthLoginHandler(BaseApiHandler):
def post(self):
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:

View File

@ -631,7 +631,6 @@ class ApiServersIndexHandler(BaseApiHandler):
self.finish_json(200, {"status": "ok", "data": auth_data[0]})
def post(self):
auth_data = self.authenticate_user()
if not auth_data:
return

View File

@ -404,6 +404,14 @@ class ServerHandler(BaseHandler):
jar_type, server_type, server_version = server_parts
# TODO: add server type check here and call the correct server
# add functions if not a jar
if server_type == "forge" and not self.helper.detect_java():
translation = self.helper.translation.translate(
"error",
"installerJava",
self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
).format(server_name)
self.redirect(f"/panel/error?error={translation}")
return
new_server_id = self.controller.create_jar_server(
jar_type,
server_type,
@ -552,7 +560,6 @@ class ServerHandler(BaseHandler):
self.get_remote_ip(),
)
else:
new_server_id = self.controller.create_bedrock_server(
server_name,
exec_user["user_id"],

View File

@ -59,7 +59,6 @@ class Webserver:
@staticmethod
def log_function(handler):
info = {
"Status_Code": handler.get_status(),
"Method": handler.request.method,
@ -103,7 +102,6 @@ class Webserver:
logger.debug("Applied asyncio patch")
def run_tornado(self):
# let's verify we have an SSL cert
self.helper.create_self_signed_cert()

View File

@ -18,7 +18,6 @@ logger = logging.getLogger(__name__)
@tornado.web.stream_request_body
class UploadHandler(BaseHandler):
# noinspection PyAttributeOutsideInit
def initialize(
self,
@ -173,7 +172,6 @@ class UploadHandler(BaseHandler):
if not self.request.headers.get("X-Content-Type", None).startswith(
"image/"
):
return self.finish_json(
415,
{

View File

@ -79,7 +79,6 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
# pylint: disable=arguments-renamed
@staticmethod
def on_message(raw_message):
logger.debug(f"Got message from WebSocket connection {raw_message}")
message = json.loads(raw_message)
logger.debug(f"Event Type: {message['event']}, Data: {message['data']}")

View File

@ -71,7 +71,7 @@
<div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
});">Enable all Languages</button>
});">{{ translate('panelConfig', 'enableLang', data['lang']) }}</button>
<select id="lang_select" class="form-control selectpicker show-tick" data-icon-base="fas"
data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for lang in data['all_languages'] %}
@ -86,6 +86,25 @@
rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
</div>
{% elif item[0] == 'monitored_mounts'%}
<div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#mount_select')).each(function(element) {
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
});">{{ translate('panelConfig', 'noMounts', data['lang']) }}</button>
<select id="mount_select" class="form-control selectpicker show-tick" data-icon-base="fas"
data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for mount in data['all_partitions'] %}
{% if mount in item[1] %}
<option selected>{{mount}}</option>
{% else %}
<option>{{mount}}</option>
{% end %}
{% end %}
</select>
<textarea id="monitored_mounts" name="{{item[0]}}" class="form-control list hidden"
rows="{{ len(data['all_partitions']) }}" value="{{','.join(item[1])}}"
hidden>{{','.join(item[1])}}</textarea>
</div>
{% elif isinstance(item[1], list) %}
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}"
class="form-control list">{{','.join(item[1])}}</textarea>
@ -160,6 +179,9 @@
let selected_Lang = $('#lang_select').val();
$('#disabled_lang').val(selected_Lang);
let mounts = $('#mount_select').val();
$('#monitored_mounts').val(mounts);
let class_list = document.getElementsByClassName("list");
let form_json = convertFormToJSON($("#config-form"));
for (let i = 0; i < class_list.length; i++) {

View File

@ -42,7 +42,6 @@
},
});
});
</script>
{% end %}
<div class="row">
@ -50,7 +49,7 @@
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-4 col-md-6">
<div class="col-xl-4 col-md-5">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}
@ -72,7 +71,7 @@
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
<div class="col-xl-4 col-md-4 mt-md-0 mt-4">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}
@ -88,7 +87,7 @@
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 mt-md-0 mt-4">
<div class="col-xl-4 col-md-3 mt-md-0 mt-4">
<div class="d-flex">
<div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}
@ -101,6 +100,43 @@
</div>
</div>
</div>
<div class="col-12 mt-4">
<div class="d-flex">
<div class="wrapper" style="width: 100%;">
<h5 class="mb-1 font-weight-medium text-primary">Storage
</h5>
<div id="storage_data">
<div class="row">
{% for item in data.get('hosts_data').get('disk_json') %}
{% if item["mount"] in data["monitored"] %}
<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading"
id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom"
title="{{item['mount']}}" style="max-width: 100%;"><i class="fas fa-hdd"></i>
{{item["mount"]}}</h4>
<div class="progress d-inline-block"
style="height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
<div class="progress-bar
{% if item['percent_used'] <= 58 %}
bg-success
{% elif 59 <= item['percent_used'] <= 75 %}
bg-warning
{% else %}
bg-danger
{% end %}
" role="progressbar" style="color: black; height: 100%; width: {{item['percent_used']}}%;"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">{{item["used"]}} /
{{item["total"]}}
</div>
</div>
</div>
{% end %}
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -891,6 +927,32 @@
cpu_usage.textContent = hostStats.cpu_usage;
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
mem_percent.textContent = hostStats.mem_percent + '%';
var storage_html = '<div class="row">';
for (i = 0; i < hostStats.disk_usage.length; i++) {
if (hostStats.mounts.includes(hostStats.disk_usage[i].mount)) {
storage_html += `<div id="{{item['device']}}" class="col-xl-3 col-lg-3 col-md-4 col-12">
<h4 class="mb-0 font-weight-semibold d-inline-block text-truncate storage-heading" id="title_{{item['device']}}" data-toggle="tooltip" data-placement="bottom" title="${hostStats.disk_usage[i].mount}" style="max-width: 100%;"><i class="fas fa-hdd"></i> ${hostStats.disk_usage[i].mount}</h4>
<div class="progress" style="display: inline-block; height: 20px; width: 100%; background-color: rgb(139, 139, 139) !important;">
<div class="progress-bar`;
if (hostStats.disk_usage[i].percent_used <= 58) {
storage_html += ` bg-success`;
} else if (hostStats.disk_usage[i].percent_used <= 75 && hostStats.disk_usage[i].percent_used >= 59) {
storage_html += ` bg-warning`;
} else {
storage_html += ` bg-danger`;
}
storage_html += `" role="progressbar" style="color: black; height: 100%; width: ${hostStats.disk_usage[i].percent_used}%;"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">${hostStats.disk_usage[i].used} / ${hostStats.disk_usage[i].total}
</div>
</div>
</div>`;
}
}
storage_html += `</div>`;
$(".storage-heading").tooltip('hide');
$("#storage_data").html(storage_html);
$("#storage_data").tooltip({ selector: '.storage-heading' });
});
}

View File

@ -50,7 +50,9 @@
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang'])
}}</h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
<!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{
@ -148,7 +150,9 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles',
data['lang']) }}</h4>
{% if data['user_data']['hints'] %}
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
{% end %}
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newRole', data['lang']) }}</a></div>
@ -226,7 +230,7 @@
</div>
</div>
</div>
{% if data['superuser'] %}
{% if data['superuser'] and not data["docker"] %}
<div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
@ -234,6 +238,24 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls',
data['lang']) }}</h4>
</div>
<br>
<form id="server-path">
<div class="form-group">
<label for="global_server_path">{{ translate('panelConfig', 'globalServer',
data['lang']) }}<small class="text-muted ml-1"> - {{ translate('panelConfig', 'globalExplain',
data['lang']) }}</small></label>
<div class="input-group">
<input type="text" id="global_server_path" class="form-control" name="global_server_path"
placeholder="/var/opt/servers" value="{{data['servers_dir']}}" directory>
<div class="input-group-append">
<span type="button" class="btn btn-outline-default custom-picker">/servers/</span>
</div>
</div>
</div>
<button class="btn btn-success" type="submit">Submit</button>&nbsp;<span id="submit-status"></span>
<br>
<span id="submit-list"></span>
</form>
</div>
</div>
</div>
@ -247,10 +269,37 @@
</div>
<style>
.custom-picker {
border: 1px solid var(--outline);
}
.popover-body {
color: white !important;
;
}
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
-webkit-animation: ellipsis steps(4, end) 900ms infinite;
animation: ellipsis steps(4, end) 900ms infinite;
content: "\2026";
/* ascii code for the ellipsis character */
width: 0px;
}
@keyframes ellipsis {
to {
width: 1.25em;
}
}
@-webkit-keyframes ellipsis {
to {
width: 1.25em;
}
}
</style>
<!-- content-wrapper ends -->
@ -258,6 +307,40 @@
{% block js %}
<script>
if (webSocket) {
webSocket.on('move_status', function (message) {
if (message === "done") {
$("#submit-list").removeClass("loading");
$("#submit-list").html("");
$("#submit-status").html('<i class="fa fa-check"></i>');
} else {
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
$("#submit-list").html(message);
$("#submit-list").addClass("loading");
}
});
}
$("#server-path").submit(function (e) {
var token = getCookie("_xsrf")
e.preventDefault();
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
let path = $("#global_server_path").val();
let encoded = encodeURIComponent(path);
console.log(path)
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
dataType: "text",
url: '/ajax/update_server_dir',
data: {
"server_dir": encoded,
},
});
});
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {

View File

@ -539,7 +539,7 @@
* @param {boolean} saved
*/
const setSaveStatus = (saved) => {
document.getElementById('save_status').innerHTML = `<i class="fal ${saved ? "fa-file-check" : "fa-file"}"></i>`;
document.getElementById('save_status').innerHTML = `<i class="${saved ? "fa-solid fa-file-circle-check" : "fa-regular fa-file"}"></i>`;
document.getElementById('save_status').style.color = saved ? '#2fb689' : 'gray';
}
['change', 'undo', 'redo'].forEach(event => editor.on(event, (event) => setSaveStatus(serverFileContent === editor.session.getValue())))

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns(
"crafty_settings", master_server_dir=peewee.CharField(default="")
)
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("crafty_settings", ["master_server_dir"])
"""
Write your rollback migrations here.
"""

View File

@ -179,6 +179,7 @@
"internet": "We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.",
"no-file": "We can't seem to locate the requested file. Double check the path. Does Crafty have proper permissions?",
"noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server.",
"installerJava": "Failed to install {} : Forge Server Installs require Java. We have detected Java is not installed. Please install java then install the server.",
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"start-error": "Server {} failed to start with error code: {}",
@ -232,7 +233,11 @@
"superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User",
"users": "Users",
"title": "Crafty Configuration"
"title": "Crafty Configuration",
"enableLang": "Enable All Languages",
"noMounts": "Show no Mounts on Dash",
"globalServer": "Global Servers Directory",
"globalExplain": "Where Crafty stores all your server files. (We will append the path with /servers/[uuid of server])"
},
"customLogin": {
"customLoginPage": "Customise the Login Page",

View File

@ -230,6 +230,15 @@ if __name__ == "__main__":
running_mode = "Interactive"
controller.set_project_root(application_path)
master_server_dir = controller.management.get_master_server_dir()
if master_server_dir == "":
logger.debug("Could not find master server path. Setting default")
controller.set_master_server_dir(
os.path.join(controller.project_root, "servers")
)
else:
helper.servers_dir = master_server_dir
Console.debug(f"Execution Mode: {running_mode}")
Console.debug(f"Application path : '{application_path}'")