mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'dev' into tweak/java-check
This commit is contained in:
commit
c27e1e667d
@ -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,7 @@
|
||||
- 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))
|
||||
### 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))
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -15,6 +15,7 @@ from app.classes.shared.permission_helper import PermissionHelper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# User_Crafty Class
|
||||
# **********************************************************************************
|
||||
|
@ -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
|
||||
# **********************************************************************************
|
||||
|
@ -15,6 +15,7 @@ from app.classes.shared.helpers import Helpers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Roles Class
|
||||
# **********************************************************************************
|
||||
|
@ -16,6 +16,7 @@ from app.classes.shared.permission_helper import PermissionHelper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Role Servers Class
|
||||
# **********************************************************************************
|
||||
|
@ -29,6 +29,7 @@ logger = logging.getLogger(__name__)
|
||||
peewee_logger = logging.getLogger("peewee")
|
||||
peewee_logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Servers Stats Class
|
||||
# **********************************************************************************
|
||||
|
@ -15,6 +15,7 @@ from app.classes.models.base_model import BaseModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# **********************************************************************************
|
||||
# Servers Model
|
||||
# **********************************************************************************
|
||||
|
@ -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
|
||||
# **********************************************************************************
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -95,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"])
|
||||
@ -132,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)
|
||||
@ -300,7 +300,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)
|
||||
@ -436,6 +436,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):
|
||||
@ -455,11 +456,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
|
||||
@ -615,7 +632,6 @@ class Helpers:
|
||||
|
||||
# open our file
|
||||
with open(file_name, "r", encoding="utf-8") as f:
|
||||
|
||||
# seek
|
||||
f.seek(0, 2)
|
||||
|
||||
@ -771,7 +787,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}")
|
||||
@ -780,7 +796,6 @@ class Helpers:
|
||||
|
||||
@staticmethod
|
||||
def get_file_contents(path: str, lines=100):
|
||||
|
||||
contents = ""
|
||||
|
||||
if os.path.exists(path) and os.path.isfile(path):
|
||||
@ -801,12 +816,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)
|
||||
@ -906,15 +919,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")
|
||||
|
||||
@ -996,6 +1010,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("\\", "/")
|
||||
@ -1045,9 +1068,9 @@ class Helpers:
|
||||
if filename not in self.ignored_names:
|
||||
output += f"""<li id="{dpath}li" class="tree-item"
|
||||
data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
data-name="{filename}" onclick="getDirView(event)">
|
||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||
@ -1066,7 +1089,6 @@ class Helpers:
|
||||
return output
|
||||
|
||||
def generate_dir(self, folder, output=""):
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
@ -1087,9 +1109,9 @@ class Helpers:
|
||||
if filename not in self.ignored_names:
|
||||
output += f"""<li id="{dpath}li" class="tree-item"
|
||||
data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}"
|
||||
class="tree-caret tree-ctx-item tree-folder">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}"
|
||||
data-name="{filename}" onclick="getDirView(event)">
|
||||
<i style="color: var(--info);" class="far fa-folder"></i>
|
||||
<i style="color: var(--info);" class="far fa-folder-open"></i>
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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",
|
||||
)
|
||||
|
@ -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)
|
||||
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -560,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"],
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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']}")
|
||||
|
@ -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++) {
|
||||
|
@ -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' });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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> {{
|
||||
@ -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> {{
|
||||
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> <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) {
|
||||
|
@ -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())))
|
||||
|
18
app/migrations/20230126_master_server.py
Normal file
18
app/migrations/20230126_master_server.py
Normal 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.
|
||||
"""
|
@ -233,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",
|
||||
@ -609,4 +613,4 @@
|
||||
"manager": "Manager",
|
||||
"selectManager": "Select Manager for User"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
main.py
9
main.py
@ -227,6 +227,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}'")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user