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 ### New features
- Add better feedback for uploads with a progress bar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546)) - 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)) - 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 ### Bug fixes
- Fix exception related to page data on server start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/544)) - 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)) - 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)) - 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)) - 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)) - 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 ### Lang
- Add additional translations to backups page strings ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/543)) - 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)) - 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): 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) 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_obj.server_id
) )
server_instance.update_server_instance() server_instance.update_server_instance()
return ret return ret
def get_history_stats(self, server_id, days): def get_history_stats(self, server_id, days):
@ -163,10 +164,9 @@ class ServersController(metaclass=Singleton):
return server["server_obj"] return server["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}") 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): def init_all_servers(self):
servers = self.get_all_defined_servers() servers = self.get_all_defined_servers()
self.failed_servers = [] self.failed_servers = []
@ -227,7 +227,6 @@ class ServersController(metaclass=Singleton):
) )
def check_server_loaded(self, server_id_to_check: int): def check_server_loaded(self, server_id_to_check: int):
logger.info(f"Checking to see if we already registered {server_id_to_check}") logger.info(f"Checking to see if we already registered {server_id_to_check}")
for server in self.servers_list: for server in self.servers_list:

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ from app.classes.shared.main_models import DatabaseShortcuts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# ********************************************************************************** # **********************************************************************************
# Audit_Log Class # Audit_Log Class
# ********************************************************************************** # **********************************************************************************
@ -46,6 +47,7 @@ class CraftySettings(BaseModel):
cookie_secret = CharField(default="") cookie_secret = CharField(default="")
login_photo = CharField(default="login_1.jpg") login_photo = CharField(default="login_1.jpg")
login_opacity = IntegerField(default=100) login_opacity = IntegerField(default=100)
master_server_dir = CharField(default="")
class Meta: class Meta:
table_name = "crafty_settings" table_name = "crafty_settings"
@ -271,6 +273,19 @@ class HelpersManagement:
CraftySettings.id == 1 CraftySettings.id == 1
).execute() ).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 # Schedules Methods
# ********************************************************************************** # **********************************************************************************

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import html
import zipfile import zipfile
import pathlib import pathlib
import ctypes import ctypes
import shutil
import subprocess import subprocess
import itertools import itertools
from datetime import datetime from datetime import datetime
@ -94,7 +95,7 @@ class Helpers:
try: try:
# Get tags from Gitlab, select the latest and parse the semver # Get tags from Gitlab, select the latest and parse the semver
response = get( 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: if response.status_code == 200:
remote_version = pkg_version.parse(json.loads(response.text)[0]["name"]) remote_version = pkg_version.parse(json.loads(response.text)[0]["name"])
@ -131,7 +132,7 @@ class Helpers:
try: try:
# Get minecraft server download page # Get minecraft server download page
# (hopefully the don't change the structure) # (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 # Search for our string targets
win_download_url = re.search(target_win, download_page.text).group(0) 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}") logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
return False 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 @staticmethod
def find_java_installs(): def find_java_installs():
# If we're windows return oracle java versions, # If we're windows return oracle java versions,
@ -281,7 +298,7 @@ class Helpers:
@staticmethod @staticmethod
def check_port(server_port): def check_port(server_port):
try: try:
ip = get("https://api.ipify.org").content.decode("utf8") ip = get("https://api.ipify.org", timeout=1).content.decode("utf8")
except: except:
ip = "google.com" ip = "google.com"
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -417,6 +434,7 @@ class Helpers:
"allow_nsfw_profile_pictures": False, "allow_nsfw_profile_pictures": False,
"enable_user_self_delete": False, "enable_user_self_delete": False,
"reset_secrets_on_next_boot": False, "reset_secrets_on_next_boot": False,
"monitored_mounts": Helpers.get_all_mounts(),
} }
def get_all_settings(self): def get_all_settings(self):
@ -436,10 +454,26 @@ class Helpers:
return data return data
@staticmethod @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) server_path = os.path.realpath(server_path)
root_dir = os.path.realpath(root_dir) root_dir = os.path.realpath(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) relative = os.path.relpath(server_path, root_dir)
if relative.startswith(os.pardir): if relative.startswith(os.pardir):
@ -596,7 +630,6 @@ class Helpers:
# open our file # open our file
with open(file_name, "r", encoding="utf-8") as f: with open(file_name, "r", encoding="utf-8") as f:
# seek # seek
f.seek(0, 2) f.seek(0, 2)
@ -752,7 +785,7 @@ class Helpers:
use_ssl=True, use_ssl=True,
) # + "?d=404" ) # + "?d=404"
try: try:
if requests.head(url).status_code != 404: if requests.head(url, timeout=1).status_code != 404:
profile_url = url profile_url = url
except Exception as e: except Exception as e:
logger.debug(f"Could not pull resource from Gravatar with error {e}") logger.debug(f"Could not pull resource from Gravatar with error {e}")
@ -761,7 +794,6 @@ class Helpers:
@staticmethod @staticmethod
def get_file_contents(path: str, lines=100): def get_file_contents(path: str, lines=100):
contents = "" contents = ""
if os.path.exists(path) and os.path.isfile(path): if os.path.exists(path) and os.path.isfile(path):
@ -782,12 +814,10 @@ class Helpers:
return False return False
def create_session_file(self, ignore=False): def create_session_file(self, ignore=False):
if ignore and os.path.exists(self.session_file): if ignore and os.path.exists(self.session_file):
os.remove(self.session_file) os.remove(self.session_file)
if os.path.exists(self.session_file): if os.path.exists(self.session_file):
file_data = self.get_file_contents(self.session_file) file_data = self.get_file_contents(self.session_file)
try: try:
data = json.loads(file_data) data = json.loads(file_data)
@ -887,15 +917,16 @@ class Helpers:
try: try:
os.makedirs(path) os.makedirs(path)
logger.debug(f"Created Directory : {path}") logger.debug(f"Created Directory : {path}")
return True
# directory already exists - non-blocking error # directory already exists - non-blocking error
except FileExistsError: except FileExistsError:
pass return True
except PermissionError as e: except PermissionError as e:
logger.critical(f"Check generated exception due to permssion error: {e}") logger.critical(f"Check generated exception due to permssion error: {e}")
return False
def create_self_signed_cert(self, cert_dir=None): def create_self_signed_cert(self, cert_dir=None):
if cert_dir is None: if cert_dir is None:
cert_dir = os.path.join(self.config_dir, "web", "certs") cert_dir = os.path.join(self.config_dir, "web", "certs")
@ -977,6 +1008,15 @@ class Helpers:
def is_os_windows(): def is_os_windows():
return os.name == "nt" 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 @staticmethod
def wtol_path(w_path): def wtol_path(w_path):
l_path = w_path.replace("\\", "/") l_path = w_path.replace("\\", "/")
@ -1047,7 +1087,6 @@ class Helpers:
return output return output
def generate_dir(self, folder, output=""): def generate_dir(self, folder, output=""):
dir_list = [] dir_list = []
unsorted_files = [] unsorted_files = []
file_list = os.listdir(folder) file_list = os.listdir(folder)

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import platform
import shutil import shutil
import time import time
import logging import logging
import threading
from peewee import DoesNotExist from peewee import DoesNotExist
# TZLocal is set as a hidden import on win pipeline # TZLocal is set as a hidden import on win pipeline
@ -347,7 +348,7 @@ class Controller:
elif root_create_data["create_type"] == "import_zip": elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory # TODO: Copy files from the zip file to the new server directory
server_file = create_data["jarfile"] server_file = create_data["jarfile"]
raise Exception("Not yet implemented") raise NotImplementedError("Not yet implemented")
_create_server_properties_if_needed( _create_server_properties_if_needed(
create_data["server_properties_port"], create_data["server_properties_port"],
) )
@ -379,7 +380,7 @@ class Controller:
logger.error(f"Server import failed with error: {ex}") logger.error(f"Server import failed with error: {ex}")
elif root_create_data["create_type"] == "import_zip": elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory # 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) _create_server_properties_if_needed(0, True)
@ -401,7 +402,7 @@ class Controller:
logger.error(f"Server import failed with error: {ex}") logger.error(f"Server import failed with error: {ex}")
elif root_create_data["create_type"] == "import_zip": elif root_create_data["create_type"] == "import_zip":
# TODO: Copy files from the zip file to the new server directory # 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) _create_server_properties_if_needed(0, True)
@ -941,7 +942,6 @@ class Controller:
def remove_server(self, server_id, files): def remove_server(self, server_id, files):
counter = 0 counter = 0
for server in self.servers.servers_list: for server in self.servers.servers_list:
# if this is the droid... im mean server we are looking for... # if this is the droid... im mean server we are looking for...
if str(server["server_id"]) == str(server_id): if str(server["server_id"]) == str(server_id):
server_data = self.servers.get_server_data(server_id) server_data = self.servers.get_server_data(server_id)
@ -1003,3 +1003,122 @@ class Controller:
@staticmethod @staticmethod
def clear_support_status(): def clear_support_status():
HelperUsers.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.jar_update_url = server_data.executable_update_url
self.name = server_data.server_name self.name = server_data.server_name
self.server_object = server_data self.server_object = server_data
self.stats_helper.select_database()
self.reload_server_settings()
def reload_server_settings(self): def reload_server_settings(self):
server_data = HelperServers.get_server_data_by_id(self.server_id) server_data = HelperServers.get_server_data_by_id(self.server_id)
@ -445,7 +447,7 @@ class ServerInstance:
) )
except Exception as ex: except Exception as ex:
# Checks for java on initial fail # Checks for java on initial fail
if os.system("java -version") == 32512: if not self.helper.detect_java():
if user_id: if user_id:
self.helper.websocket_helper.broadcast_user( self.helper.websocket_helper.broadcast_user(
user_id, user_id,
@ -590,7 +592,6 @@ class ServerInstance:
# We need to grab the exact forge version number. # We need to grab the exact forge version number.
# We know we can find it here in the run.sh/bat script. # We know we can find it here in the run.sh/bat script.
try: try:
# Getting the forge version from the executable command # Getting the forge version from the executable command
version = re.findall( version = re.findall(
r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar", r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
@ -850,7 +851,6 @@ class ServerInstance:
return True return True
def crash_detected(self, name): def crash_detected(self, name):
# clear the old scheduled watcher task # clear the old scheduled watcher task
self.server_scheduler.remove_job(f"c_{self.server_id}") self.server_scheduler.remove_job(f"c_{self.server_id}")
# remove the stats polling job since server is stopped # 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 return self.process.pid if self.process is not None else None
def detect_crash(self): def detect_crash(self):
logger.info(f"Detecting possible crash for server: {self.name} ") logger.info(f"Detecting possible crash for server: {self.name} ")
running = self.check_running() running = self.check_running()
@ -935,7 +934,6 @@ class ServerInstance:
self.stats_helper.sever_crashed() self.stats_helper.sever_crashed()
# if we haven't tried to restart more 3 or more times # if we haven't tried to restart more 3 or more times
if self.restart_count <= 3: if self.restart_count <= 3:
# start the server if needed # start the server if needed
server_restarted = self.crash_detected(self.name) server_restarted = self.crash_detected(self.name)
@ -1461,7 +1459,6 @@ class ServerInstance:
Console.critical("Can't broadcast server status to websocket") Console.critical("Can't broadcast server status to websocket")
def get_servers_stats(self): def get_servers_stats(self):
server_stats = {} server_stats = {}
logger.info("Getting Stats for Server " + self.name + " ...") logger.info("Getting Stats for Server " + self.name + " ...")
@ -1548,7 +1545,6 @@ class ServerInstance:
return server_stats return server_stats
def get_server_players(self): def get_server_players(self):
server = HelperServers.get_server_data_by_id(self.server_id) server = HelperServers.get_server_data_by_id(self.server_id)
logger.info(f"Getting players for server {server}") logger.info(f"Getting players for server {server}")
@ -1569,7 +1565,6 @@ class ServerInstance:
return [] return []
def get_raw_server_stats(self, server_id): def get_raw_server_stats(self, server_id):
try: try:
server = HelperServers.get_server_obj(server_id) server = HelperServers.get_server_obj(server_id)
except: except:
@ -1718,7 +1713,6 @@ class ServerInstance:
return server_stats return server_stats
def record_server_stats(self): def record_server_stats(self):
server_stats = self.get_servers_stats() server_stats = self.get_servers_stats()
self.stats_helper.insert_server_stats(server_stats) self.stats_helper.insert_server_stats(server_stats)

View File

@ -4,6 +4,7 @@ import logging
import threading import threading
import asyncio import asyncio
import datetime import datetime
import json
from tzlocal import get_localzone from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError from tzlocal.utils import ZoneInfoNotFoundError
@ -672,7 +673,6 @@ class TasksManager:
host_stats = HelpersManagement.get_latest_hosts_stats() host_stats = HelpersManagement.get_latest_hosts_stats()
while True: while True:
if host_stats.get( if host_stats.get(
"cpu_usage" "cpu_usage"
) != HelpersManagement.get_latest_hosts_stats().get( ) != HelpersManagement.get_latest_hosts_stats().get(
@ -687,6 +687,7 @@ class TasksManager:
host_stats = HelpersManagement.get_latest_hosts_stats() host_stats = HelpersManagement.get_latest_hosts_stats()
if len(self.helper.websocket_helper.clients) > 0: if len(self.helper.websocket_helper.clients) > 0:
# There are clients # There are clients
try:
self.helper.websocket_helper.broadcast_page( self.helper.websocket_helper.broadcast_page(
"/panel/dashboard", "/panel/dashboard",
"update_host_stats", "update_host_stats",
@ -697,6 +698,24 @@ class TasksManager:
"cpu_max_freq": host_stats.get("cpu_max_freq"), "cpu_max_freq": host_stats.get("cpu_max_freq"),
"mem_percent": host_stats.get("mem_percent"), "mem_percent": host_stats.get("mem_percent"),
"mem_usage": host_stats.get("mem_usage"), "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) time.sleep(1)

View File

@ -575,6 +575,31 @@ class AjaxHandler(BaseHandler):
self.controller.server_jars.manual_refresh_cache() self.controller.server_jars.manual_refresh_cache()
return 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 @tornado.web.authenticated
def delete(self, page): def delete(self, page):
api_key, _, exec_user = self.current_user api_key, _, exec_user = self.current_user

View File

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

View File

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

View File

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

View File

@ -26,7 +26,6 @@ login_schema = {
class ApiAuthLoginHandler(BaseApiHandler): class ApiAuthLoginHandler(BaseApiHandler):
def post(self): def post(self):
try: try:
data = json.loads(self.request.body) data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e: 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]}) self.finish_json(200, {"status": "ok", "data": auth_data[0]})
def post(self): def post(self):
auth_data = self.authenticate_user() auth_data = self.authenticate_user()
if not auth_data: if not auth_data:
return return

View File

@ -404,6 +404,14 @@ class ServerHandler(BaseHandler):
jar_type, server_type, server_version = server_parts jar_type, server_type, server_version = server_parts
# TODO: add server type check here and call the correct server # TODO: add server type check here and call the correct server
# add functions if not a jar # 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( new_server_id = self.controller.create_jar_server(
jar_type, jar_type,
server_type, server_type,
@ -552,7 +560,6 @@ class ServerHandler(BaseHandler):
self.get_remote_ip(), self.get_remote_ip(),
) )
else: else:
new_server_id = self.controller.create_bedrock_server( new_server_id = self.controller.create_bedrock_server(
server_name, server_name,
exec_user["user_id"], exec_user["user_id"],

View File

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

View File

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

View File

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

View File

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

View File

@ -42,7 +42,6 @@
}, },
}); });
}); });
</script> </script>
{% end %} {% end %}
<div class="row"> <div class="row">
@ -50,7 +49,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row"> <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="d-flex">
<div class="wrapper"> <div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }} <h5 class="mb-1 font-weight-medium text-primary"> {{ translate('dashboard', 'host', data['lang']) }}
@ -72,7 +71,7 @@
</div> </div>
</div> </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="d-flex">
<div class="wrapper"> <div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }} <h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'servers', data['lang']) }}
@ -88,7 +87,7 @@
</div> </div>
</div> </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="d-flex">
<div class="wrapper"> <div class="wrapper">
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }} <h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'players', data['lang']) }}
@ -101,6 +100,43 @@
</div> </div>
</div> </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> </div>
</div> </div>
@ -891,6 +927,32 @@
cpu_usage.textContent = hostStats.cpu_usage; cpu_usage.textContent = hostStats.cpu_usage;
mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`); mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`);
mem_percent.textContent = hostStats.mem_percent + '%'; 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 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang'])
}}</h4> }}</h4>
{% if data['user_data']['hints'] %} {% 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 %} {% end %}
<!-- TODO: Translate the following --> <!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{ <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', <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles',
data['lang']) }}</h4> data['lang']) }}</h4>
{% if data['user_data']['hints'] %} {% 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 %} {% end %}
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{ <div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newRole', data['lang']) }}</a></div> translate('panelConfig', 'newRole', data['lang']) }}</a></div>
@ -226,7 +230,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if data['superuser'] %} {% if data['superuser'] and not data["docker"] %}
<div class="row"> <div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card"> <div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card"> <div class="card">
@ -234,6 +238,24 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls', <h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls',
data['lang']) }}</h4> data['lang']) }}</h4>
</div> </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> </div>
</div> </div>
@ -247,10 +269,37 @@
</div> </div>
<style> <style>
.custom-picker {
border: 1px solid var(--outline);
}
.popover-body { .popover-body {
color: white !important; 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> </style>
<!-- content-wrapper ends --> <!-- content-wrapper ends -->
@ -258,6 +307,40 @@
{% block js %} {% block js %}
<script> <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 () { $(document).ready(function () {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) { if ($(window).width() < 1000) {

View File

@ -539,7 +539,7 @@
* @param {boolean} saved * @param {boolean} saved
*/ */
const setSaveStatus = (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'; document.getElementById('save_status').style.color = saved ? '#2fb689' : 'gray';
} }
['change', 'undo', 'redo'].forEach(event => editor.on(event, (event) => setSaveStatus(serverFileContent === editor.session.getValue()))) ['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.", "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?", "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.", "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?", "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.", "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: {}", "start-error": "Server {} failed to start with error code: {}",
@ -232,7 +233,11 @@
"superConfirmTitle": "Enable superuser? Are you sure?", "superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User", "user": "User",
"users": "Users", "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": { "customLogin": {
"customLoginPage": "Customise the Login Page", "customLoginPage": "Customise the Login Page",

View File

@ -230,6 +230,15 @@ if __name__ == "__main__":
running_mode = "Interactive" running_mode = "Interactive"
controller.set_project_root(application_path) 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"Execution Mode: {running_mode}")
Console.debug(f"Application path : '{application_path}'") Console.debug(f"Application path : '{application_path}'")