Merge branch 'dev' into bugfix/fix-forge-install

This commit is contained in:
Zedifus 2024-03-02 22:51:44 +00:00
commit 49fce1b5c4
16 changed files with 531 additions and 295 deletions

View File

@ -2,10 +2,15 @@
## --- [4.2.4] - 2023/TBD ## --- [4.2.4] - 2023/TBD
### New features ### New features
TBD TBD
### Refactor
- Refactor remote file downloads ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/719))
### Bug fixes ### Bug fixes
TBD - Fix Bedrock cert issues ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/719))
- Make sure default.json is read from correct location ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/714))
- Do not allow users at server limit to clone servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/718))
- Fix bug where you cannot get to config with unloaded server ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/9de08973b6bb2ddf91283c5c6b0e189ff34f7e24))
### Tweaks ### Tweaks
TBD - Bump pyOpenSSL & cryptography for CVE-2024-0727, CVE-2023-50782 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/716))
### Lang ### Lang
TBD TBD
<br><br> <br><br>

View File

@ -1,13 +1,14 @@
import os
import json import json
import threading import threading
import time import time
import shutil
import logging import logging
from datetime import datetime from datetime import datetime
import requests import requests
from app.classes.controllers.servers_controller import ServersController from app.classes.controllers.servers_controller import ServersController
from app.classes.models.server_permissions import PermissionsServers from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.websocket_manager import WebSocketManager from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,6 +25,113 @@ class ServerJars:
def get_paper_jars(): def get_paper_jars():
return PAPERJARS return PAPERJARS
def get_paper_versions(self, project):
"""
Retrieves a list of versions for a specified project from the PaperMC API.
Parameters:
project (str): The project name to query for available versions.
Returns:
list: A list of version strings available for the project. Returns an empty
list if the API call fails or if no versions are found.
This function makes a GET request to the PaperMC API to fetch available project
versions, The versions are returned in reverse order, with the most recent
version first.
"""
try:
response = requests.get(
f"{self.paper_base}/v2/projects/{project}/", timeout=2
)
response.raise_for_status()
api_data = response.json()
except Exception as e:
logger.error(f"Error loading project versions for {project}: {e}")
return []
versions = api_data.get("versions", [])
versions.reverse() # Ensure the most recent version comes first
return versions
def get_paper_build(self, project, version):
"""
Fetches the latest build for a specified project and version from PaperMC API.
Parameters:
project (str): Project name, typically a server software like 'paper'.
version (str): Project version to fetch the build number for.
Returns:
int or None: Latest build number if successful, None if not or on error.
This method attempts to query the PaperMC API for the latest build and
handles exceptions by logging errors and returning None.
"""
try:
response = requests.get(
f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/",
timeout=2,
)
response.raise_for_status()
api_data = response.json()
except Exception as e:
logger.error(f"Error fetching build for {project} {version}: {e}")
return None
builds = api_data.get("builds", [])
return builds[-1] if builds else None
def get_fetch_url(self, jar, server, version):
"""
Constructs the URL for downloading a server JAR file based on the server type.
Supports two main types of server JAR sources:
- ServerJars API for servers not in PAPERJARS.
- Paper API for servers available through the Paper project.
Parameters:
jar (str): Name of the JAR file.
server (str): Server software name (e.g., "paper").
version (str): Server version.
Returns:
str or None: URL for downloading the JAR file, or None if URL cannot be
constructed or an error occurs.
"""
try:
# Check if the server type is not specifically handled by Paper.
if server not in PAPERJARS:
return f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
# For Paper servers, attempt to get the build for the specified version.
paper_build_info = self.get_paper_build(server, version)
if paper_build_info is None:
# Log an error or handle the case where paper_build_info is None
logger.error(
"Error: Unable to get build information for server:"
f" {server}, version: {version}"
)
return None
build = paper_build_info.get("build")
if not build:
# Log an error or handle the case where build is None or not found
logger.error(
f"Error: Build number not found for server:"
f" {server}, version: {version}"
)
return None
# Construct and return the URL for downloading the Paper server JAR.
return (
f"{self.paper_base}/v2/projects/{server}/versions/{version}/"
f"builds/{build}/downloads/{server}-{version}-{build}.jar"
)
except Exception as e:
logger.error(f"An error occurred while constructing fetch URL: {e}")
return None
def _get_api_result(self, call_url: str): def _get_api_result(self, call_url: str):
full_url = f"{self.base_url}{call_url}" full_url = f"{self.base_url}{call_url}"
@ -44,40 +152,6 @@ class ServerJars:
return api_response return api_response
def get_paper_versions(self, project):
try:
response = requests.get(
f"{self.paper_base}/v2/projects/{project}/", timeout=2
)
response.raise_for_status()
api_data = json.loads(response.content)
except Exception as e:
logger.error(
f"Unable to load https://api.papermc.io/v2/projects/{project}/"
f"api due to error: {e}"
)
return {}
versions = api_data.get("versions", [])
versions.reverse()
return versions
def get_paper_build(self, project, version):
try:
response = requests.get(
f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/",
timeout=2,
)
response.raise_for_status()
api_data = json.loads(response.content)
except Exception as e:
logger.error(
f"Unable to load https://api.papermc.io/v2/projects/{project}/"
f"api due to error: {e}"
)
return {}
build = api_data.get("builds", [])[-1]
return build
def _read_cache(self): def _read_cache(self):
cache_file = self.helper.serverjar_cache cache_file = self.helper.serverjar_cache
cache = {} cache = {}
@ -213,55 +287,75 @@ class ServerJars:
update_thread.start() update_thread.start()
def a_download_jar(self, jar, server, version, path, server_id): def a_download_jar(self, jar, server, version, path, server_id):
"""
Downloads a server JAR file and performs post-download actions including
notifying users and setting import status.
This method waits for the server registration to complete, retrieves the
download URL for the specified server JAR file.
Upon successful download, it either runs the installer for
Forge servers or simply finishes the import process for other types. It
notifies server users about the completion of the download.
Parameters:
- jar (str): The name of the JAR file to download.
- server (str): The type of server software (e.g., 'forge', 'paper').
- version (str): The version of the server software.
- path (str): The local filesystem path where the JAR file will be saved.
- server_id (str): The unique identifier for the server being updated or
imported, used for notifying users and setting the import status.
Returns:
- bool: True if the JAR file was successfully downloaded and saved;
False otherwise.
The method ensures that the server is properly registered before proceeding
with the download and handles exceptions by logging errors and reverting
the import status if necessary.
"""
# delaying download for server register to finish # delaying download for server register to finish
time.sleep(3) time.sleep(3)
if server not in PAPERJARS:
fetch_url = f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}" fetch_url = self.get_fetch_url(jar, server, version)
else: if not fetch_url:
build = self.get_paper_build(server, version).get("build", None) return False
if not build:
return
fetch_url = (
f"{self.paper_base}/v2/projects"
f"/{server}/versions/{version}/builds/{build}/downloads/"
f"{server}-{version}-{build}.jar"
)
server_users = PermissionsServers.get_server_user_list(server_id) server_users = PermissionsServers.get_server_user_list(server_id)
# We need to make sure the server is registered before # Make sure the server is registered before updating its stats
# we submit a db update for it's stats.
while True: while True:
try: try:
ServersController.set_import(server_id) ServersController.set_import(server_id)
for user in server_users: for user in server_users:
WebSocketManager().broadcast_user(user, "send_start_reload", {}) WebSocketManager().broadcast_user(user, "send_start_reload", {})
break break
except Exception as ex: except Exception as ex:
logger.debug(f"server not registered yet. Delaying download - {ex}") logger.debug(f"Server not registered yet. Delaying download - {ex}")
# open a file stream # Initiate Download
with requests.get(fetch_url, timeout=2, stream=True) as r: jar_dir = os.path.dirname(path)
success = False jar_name = os.path.basename(path)
try: logger.info(fetch_url)
with open(path, "wb") as output: success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name)
shutil.copyfileobj(r.raw, output)
# If this is the newer forge version we will run the installer
if server == "forge":
ServersController.finish_import(server_id, True)
else:
ServersController.finish_import(server_id)
success = True # Post-download actions
except Exception as e: if success:
logger.error(f"Unable to save jar to {path} due to error:{e}") if server == "forge":
# If this is the newer Forge version, run the installer
ServersController.finish_import(server_id, True)
else:
ServersController.finish_import(server_id) ServersController.finish_import(server_id)
server_users = PermissionsServers.get_server_user_list(server_id)
# Notify users
for user in server_users: for user in server_users:
WebSocketManager().broadcast_user( WebSocketManager().broadcast_user(
user, "notification", "Executable download finished" user, "notification", "Executable download finished"
) )
time.sleep(3) time.sleep(3) # Delay for user notification
WebSocketManager().broadcast_user(user, "send_start_reload", {}) WebSocketManager().broadcast_user(user, "send_start_reload", {})
return success else:
logger.error(f"Unable to save jar to {path} due to download failure.")
ServersController.finish_import(server_id)
return success

View File

@ -172,9 +172,9 @@ class PermissionsServers:
RoleServers.server_id, RoleServers.permissions RoleServers.server_id, RoleServers.permissions
).where(RoleServers.role_id == role_id) ).where(RoleServers.role_id == role_id)
for role_server in role_servers: for role_server in role_servers:
permissions_dict[ permissions_dict[role_server.server_id_id] = (
role_server.server_id_id PermissionsServers.get_permissions(role_server.permissions)
] = PermissionsServers.get_permissions(role_server.permissions) )
return permissions_dict return permissions_dict
@staticmethod @staticmethod

View File

@ -5,6 +5,10 @@ import pathlib
import tempfile import tempfile
import zipfile import zipfile
from zipfile import ZipFile, ZIP_DEFLATED from zipfile import ZipFile, ZIP_DEFLATED
import urllib.request
import ssl
import time
import certifi
from app.classes.shared.helpers import Helpers from app.classes.shared.helpers import Helpers
from app.classes.shared.console import Console from app.classes.shared.console import Console
@ -19,6 +23,92 @@ class FileHelpers:
def __init__(self, helper): def __init__(self, helper):
self.helper: Helpers = helper self.helper: Helpers = helper
@staticmethod
def ssl_get_file(
url, out_path, out_file, max_retries=3, backoff_factor=2, headers=None
):
"""
Downloads a file from a given URL using HTTPS with SSL context verification,
retries with exponential backoff and providing download progress feedback.
Parameters:
- url (str): The URL of the file to download. Must start with "https".
- out_path (str): The local path where the file will be saved.
- out_file (str): The name of the file to save the downloaded content as.
- max_retries (int, optional): The maximum number of retry attempts
in case of download failure. Defaults to 3.
- backoff_factor (int, optional): The factor by which the wait time
increases after each failed attempt. Defaults to 2.
- headers (dict, optional):
A dictionary of HTTP headers to send with the request.
Returns:
- bool: True if the download was successful, False otherwise.
Raises:
- urllib.error.URLError: If a URL error occurs during the download.
- ssl.SSLError: If an SSL error occurs during the download.
Exception: If an unexpected error occurs during the download.
Note:
This method logs critical errors and download progress information.
Ensure that the logger is properly configured to capture this information.
"""
if not url.lower().startswith("https"):
logger.error("SSL File Get - Error: URL must start with https.")
return False
ssl_context = ssl.create_default_context(cafile=certifi.where())
if not headers:
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/58.0.3029.110 Safari/537.3"
)
}
req = urllib.request.Request(url, headers=headers)
write_path = os.path.join(out_path, out_file)
attempt = 0
logger.info(f"SSL File Get - Requesting remote: {url}")
file_path_full = os.path.join(out_path, out_file)
logger.info(f"SSL File Get - Download Destination: {file_path_full}")
while attempt < max_retries:
try:
with urllib.request.urlopen(req, context=ssl_context) as response:
total_size = response.getheader("Content-Length")
if total_size:
total_size = int(total_size)
downloaded = 0
with open(write_path, "wb") as file:
while True:
chunk = response.read(1024 * 1024) # 1 MB
if not chunk:
break
file.write(chunk)
downloaded += len(chunk)
if total_size:
progress = (downloaded / total_size) * 100
logger.info(
f"SSL File Get - Download progress: {progress:.2f}%"
)
return True
except (urllib.error.URLError, ssl.SSLError) as e:
logger.warning(f"SSL File Get - Attempt {attempt+1} failed: {e}")
time.sleep(backoff_factor**attempt)
except Exception as e:
logger.critical(f"SSL File Get - Unexpected error: {e}")
return False
finally:
attempt += 1
logger.error("SSL File Get - Maximum retries reached. Download failed.")
return False
@staticmethod @staticmethod
def del_dirs(path): def del_dirs(path):
path = pathlib.Path(path) path = pathlib.Path(path)

View File

@ -1112,7 +1112,7 @@ class Helpers:
return os.path.normpath(path) return os.path.normpath(path)
def find_default_password(self): def find_default_password(self):
default_file = os.path.join(self.root_dir, "default.json") default_file = os.path.join(self.root_dir, "app", "config", "default.json")
data = {} data = {}
if Helpers.check_file_exists(default_file): if Helpers.check_file_exists(default_file):
@ -1180,25 +1180,6 @@ class Helpers:
return temp_dir return temp_dir
return False return False
@staticmethod
def download_file(executable_url, jar_path):
try:
response = requests.get(executable_url, timeout=5)
except Exception as ex:
logger.error("Could not download executable: %s", ex)
return False
if response.status_code != 200:
logger.error("Unable to download file from %s", executable_url)
return False
try:
with open(jar_path, "wb") as jar_file:
jar_file.write(response.content)
except Exception as e:
logger.error("Unable to finish executable download. Error: %s", e)
return False
return True
@staticmethod @staticmethod
def remove_prefix(text, prefix): def remove_prefix(text, prefix):
if text.startswith(prefix): if text.startswith(prefix):

View File

@ -3,7 +3,6 @@ import time
import shutil import shutil
import logging import logging
import threading import threading
import urllib
from app.classes.controllers.server_perms_controller import PermissionsServers from app.classes.controllers.server_perms_controller import PermissionsServers
from app.classes.controllers.servers_controller import ServersController from app.classes.controllers.servers_controller import ServersController
@ -227,25 +226,39 @@ 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 the latest Bedrock server, unzips it, sets necessary permissions.
Parameters:
path (str): The directory path to download and unzip the Bedrock server.
new_id (str): The identifier for the new server import operation.
This method handles exceptions and logs errors for each step of the process.
"""
try: try:
bedrock_url = Helpers.get_latest_bedrock_url() bedrock_url = Helpers.get_latest_bedrock_url()
if bedrock_url.lower().startswith("https"): if bedrock_url:
urllib.request.urlretrieve( file_path = os.path.join(path, "bedrock_server.zip")
bedrock_url,
os.path.join(path, "bedrock_server.zip"), success = FileHelpers.ssl_get_file(
bedrock_url, path, "bedrock_server.zip"
) )
if not success:
logger.error("Failed to download the Bedrock server zip.")
return
unzip_path = os.path.join(path, "bedrock_server.zip") unzip_path = self.helper.wtol_path(file_path)
unzip_path = self.helper.wtol_path(unzip_path) # unzips archive that was downloaded.
# unzips archive that was downloaded. FileHelpers.unzip_file(unzip_path)
FileHelpers.unzip_file(unzip_path) # adjusts permissions for execution if os is not windows
# adjusts permissions for execution if os is not windows
if not self.helper.is_os_windows():
os.chmod(os.path.join(path, "bedrock_server"), 0o0744)
# we'll delete the zip we downloaded now if not self.helper.is_os_windows():
os.remove(os.path.join(path, "bedrock_server.zip")) os.chmod(os.path.join(path, "bedrock_server"), 0o0744)
# we'll delete the zip we downloaded now
os.remove(file_path)
else:
logger.error("Bedrock download URL issue!")
except Exception as e: except Exception as e:
logger.critical( logger.critical(
f"Failed to download bedrock executable during server creation! \n{e}" f"Failed to download bedrock executable during server creation! \n{e}"

View File

@ -10,7 +10,6 @@ import threading
import logging.config import logging.config
import subprocess import subprocess
import html import html
import urllib.request
import glob import glob
import json import json
@ -1454,33 +1453,45 @@ class ServerInstance:
# lets download the files # lets download the files
if HelperServers.get_server_type_by_id(self.server_id) != "minecraft-bedrock": if HelperServers.get_server_type_by_id(self.server_id) != "minecraft-bedrock":
# boolean returns true for false for success
downloaded = Helpers.download_file( jar_dir = os.path.dirname(current_executable)
self.settings["executable_update_url"], current_executable jar_file_name = os.path.basename(current_executable)
downloaded = FileHelpers.ssl_get_file(
self.settings["executable_update_url"], jar_dir, jar_file_name
) )
else: else:
# 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()
if bedrock_url.lower().startswith("https"): if bedrock_url:
urllib.request.urlretrieve( # Use the new method for secure download
bedrock_url, download_path = os.path.join(
os.path.join(self.settings["path"], "bedrock_server.zip"), self.settings["path"], "bedrock_server.zip"
)
downloaded = FileHelpers.ssl_get_file(
bedrock_url, self.settings["path"], "bedrock_server.zip"
) )
unzip_path = os.path.join(self.settings["path"], "bedrock_server.zip") if downloaded:
unzip_path = self.helper.wtol_path(unzip_path) unzip_path = download_path
# unzips archive that was downloaded. unzip_path = self.helper.wtol_path(unzip_path)
FileHelpers.unzip_file(unzip_path, server_update=True)
# adjusts permissions for execution if os is not windows
if not self.helper.is_os_windows():
os.chmod(
os.path.join(self.settings["path"], "bedrock_server"), 0o0744
)
# we'll delete the zip we downloaded now # unzips archive that was downloaded.
os.remove(os.path.join(self.settings["path"], "bedrock_server.zip")) FileHelpers.unzip_file(unzip_path, server_update=True)
downloaded = True
# adjusts permissions for execution if os is not windows
if not self.helper.is_os_windows():
os.chmod(
os.path.join(self.settings["path"], "bedrock_server"),
0o0744,
)
# we'll delete the zip we downloaded now
os.remove(download_path)
else:
logger.error("Failed to download the Bedrock server zip.")
downloaded = False
except Exception as e: except Exception as e:
logger.critical( logger.critical(
f"Failed to download bedrock executable for update \n{e}" f"Failed to download bedrock executable for update \n{e}"

View File

@ -345,15 +345,17 @@ class PanelHandler(BaseHandler):
self.controller.users.get_user_lang_by_id(exec_user["user_id"]) self.controller.users.get_user_lang_by_id(exec_user["user_id"])
), ),
"super_user": superuser, "super_user": superuser,
"api_key": { "api_key": (
"name": api_key.name, {
"created": api_key.created, "name": api_key.name,
"server_permissions": api_key.server_permissions, "created": api_key.created,
"crafty_permissions": api_key.crafty_permissions, "server_permissions": api_key.server_permissions,
"superuser": api_key.superuser, "crafty_permissions": api_key.crafty_permissions,
} "superuser": api_key.superuser,
if api_key is not None }
else None, if api_key is not None
else None
),
"superuser": superuser, "superuser": superuser,
} }
try: try:
@ -417,14 +419,14 @@ class PanelHandler(BaseHandler):
self.controller.first_login = False self.controller.first_login = False
if superuser: # TODO: Figure out a better solution if superuser: # TODO: Figure out a better solution
try: try:
page_data[ page_data["servers"] = (
"servers" self.controller.servers.get_all_servers_stats()
] = self.controller.servers.get_all_servers_stats() )
except IndexError: except IndexError:
self.controller.servers.stats.record_stats() self.controller.servers.stats.record_stats()
page_data[ page_data["servers"] = (
"servers" self.controller.servers.get_all_servers_stats()
] = self.controller.servers.get_all_servers_stats() )
else: else:
try: try:
user_auth = self.controller.servers.get_authorized_servers_stats( user_auth = self.controller.servers.get_authorized_servers_stats(
@ -454,19 +456,19 @@ class PanelHandler(BaseHandler):
for server_id in user_order[:]: for server_id in user_order[:]:
for server in un_used_servers[:]: for server in un_used_servers[:]:
if flag == 0: if flag == 0:
server["stats"][ server["stats"]["importing"] = (
"importing" self.controller.servers.get_import_status(
] = self.controller.servers.get_import_status( str(server["stats"]["server_id"]["server_id"])
str(server["stats"]["server_id"]["server_id"]) )
) )
server["stats"]["crashed"] = self.controller.servers.is_crashed( server["stats"]["crashed"] = self.controller.servers.is_crashed(
str(server["stats"]["server_id"]["server_id"]) str(server["stats"]["server_id"]["server_id"])
) )
try: try:
server["stats"][ server["stats"]["waiting_start"] = (
"waiting_start" self.controller.servers.get_waiting_start(
] = self.controller.servers.get_waiting_start( str(server["stats"]["server_id"]["server_id"])
str(server["stats"]["server_id"]["server_id"]) )
) )
except Exception as e: except Exception as e:
logger.error(f"Failed to get server waiting to start: {e}") logger.error(f"Failed to get server waiting to start: {e}")
@ -543,9 +545,9 @@ class PanelHandler(BaseHandler):
server_id server_id
) )
if not self.failed_server: if not self.failed_server:
page_data[ page_data["server_stats"] = (
"server_stats" self.controller.servers.get_server_stats_by_id(server_id)
] = self.controller.servers.get_server_stats_by_id(server_id) )
else: else:
server_temp_obj = self.controller.servers.get_server_data_by_id( server_temp_obj = self.controller.servers.get_server_data_by_id(
server_id server_id
@ -572,6 +574,7 @@ class PanelHandler(BaseHandler):
"crash_detection": server_temp_obj["crash_detection"], "crash_detection": server_temp_obj["crash_detection"],
"show_status": server_temp_obj["show_status"], "show_status": server_temp_obj["show_status"],
"ignored_exits": server_temp_obj["ignored_exits"], "ignored_exits": server_temp_obj["ignored_exits"],
"count_players": server_temp_obj["count_players"],
}, },
"running": False, "running": False,
"crashed": False, "crashed": False,
@ -611,19 +614,19 @@ class PanelHandler(BaseHandler):
"Config": EnumPermissionsServer.CONFIG, "Config": EnumPermissionsServer.CONFIG,
"Players": EnumPermissionsServer.PLAYERS, "Players": EnumPermissionsServer.PLAYERS,
} }
page_data[ page_data["user_permissions"] = (
"user_permissions" self.controller.server_perms.get_user_id_permissions_list(
] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id
exec_user["user_id"], server_id )
) )
if not self.failed_server: if not self.failed_server:
page_data["server_stats"][ page_data["server_stats"]["crashed"] = (
"crashed" self.controller.servers.is_crashed(server_id)
] = self.controller.servers.is_crashed(server_id) )
if not self.failed_server: if not self.failed_server:
page_data["server_stats"][ page_data["server_stats"]["server_type"] = (
"server_type" self.controller.servers.get_server_type_by_id(server_id)
] = self.controller.servers.get_server_type_by_id(server_id) )
if not subpage: if not subpage:
for spage, perm in SUBPAGE_PERMS.items(): for spage, perm in SUBPAGE_PERMS.items():
@ -674,23 +677,23 @@ class PanelHandler(BaseHandler):
page_data["java_versions"] = page_java page_data["java_versions"] = page_java
if subpage == "backup": if subpage == "backup":
server_info = self.controller.servers.get_server_data_by_id(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id)
page_data[ page_data["backup_config"] = (
"backup_config" self.controller.management.get_backup_config(server_id)
] = self.controller.management.get_backup_config(server_id) )
exclusions = [] exclusions = []
page_data[ page_data["exclusions"] = (
"exclusions" self.controller.management.get_excluded_backup_dirs(server_id)
] = self.controller.management.get_excluded_backup_dirs(server_id) )
page_data[ page_data["backing_up"] = (
"backing_up" self.controller.servers.get_server_instance_by_id(
] = self.controller.servers.get_server_instance_by_id( server_id
server_id ).is_backingup
).is_backingup )
page_data[ page_data["backup_stats"] = (
"backup_stats" self.controller.servers.get_server_instance_by_id(
] = self.controller.servers.get_server_instance_by_id( server_id
server_id ).send_backup_status()
).send_backup_status() )
# makes it so relative path is the only thing shown # makes it so relative path is the only thing shown
for file in page_data["exclusions"]: for file in page_data["exclusions"]:
if Helpers.is_os_windows(): if Helpers.is_os_windows():
@ -723,10 +726,10 @@ class PanelHandler(BaseHandler):
server_id, hours=(days * 24) server_id, hours=(days * 24)
) )
if subpage == "webhooks": if subpage == "webhooks":
page_data[ page_data["webhooks"] = (
"webhooks" self.controller.management.get_webhooks_by_server(
] = self.controller.management.get_webhooks_by_server( server_id, model=True
server_id, model=True )
) )
page_data["triggers"] = WebhookFactory.get_monitored_events() page_data["triggers"] = WebhookFactory.get_monitored_events()
@ -758,9 +761,9 @@ class PanelHandler(BaseHandler):
if not superuser: if not superuser:
self.redirect("/panel/error?error=Unauthorized access") self.redirect("/panel/error?error=Unauthorized access")
page_data["banned_players_html"] = get_banned_players_html() page_data["banned_players_html"] = get_banned_players_html()
page_data[ page_data["banned_players"] = (
"banned_players" self.controller.servers.get_banned_players(server_id)
] = self.controller.servers.get_banned_players(server_id) )
server_instance = self.controller.servers.get_server_instance_by_id( server_instance = self.controller.servers.get_server_instance_by_id(
server_id server_id
) )
@ -925,9 +928,9 @@ class PanelHandler(BaseHandler):
if item not in page_data["backgrounds"]: if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item) page_data["backgrounds"].append(item)
page_data["background"] = self.controller.cached_login page_data["background"] = self.controller.cached_login
page_data[ page_data["login_opacity"] = (
"login_opacity" self.controller.management.get_login_opacity()
] = self.controller.management.get_login_opacity() )
page_data["active_link"] = "custom_login" page_data["active_link"] = "custom_login"
template = "panel/custom_login.html" template = "panel/custom_login.html"
@ -959,13 +962,11 @@ class PanelHandler(BaseHandler):
page_data["servers"] = [] page_data["servers"] = []
page_data["servers_all"] = self.controller.servers.get_all_defined_servers() page_data["servers_all"] = self.controller.servers.get_all_defined_servers()
page_data["role-servers"] = [] page_data["role-servers"] = []
page_data[ page_data["permissions_all"] = (
"permissions_all" self.controller.crafty_perms.list_defined_crafty_permissions()
] = self.controller.crafty_perms.list_defined_crafty_permissions() )
page_data["permissions_list"] = set() page_data["permissions_list"] = set()
page_data[ page_data["quantity_server"] = (
"quantity_server"
] = (
self.controller.crafty_perms.list_all_crafty_permissions_quantity_limits() # pylint: disable=line-too-long self.controller.crafty_perms.list_all_crafty_permissions_quantity_limits() # pylint: disable=line-too-long
) )
page_data["languages"] = [] page_data["languages"] = []
@ -1007,10 +1008,10 @@ class PanelHandler(BaseHandler):
page_data["server_data"] = self.controller.servers.get_server_data_by_id( page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id server_id
) )
page_data[ page_data["user_permissions"] = (
"user_permissions" self.controller.server_perms.get_user_id_permissions_list(
] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id
exec_user["user_id"], server_id )
) )
page_data["permissions"] = { page_data["permissions"] = {
"Commands": EnumPermissionsServer.COMMANDS, "Commands": EnumPermissionsServer.COMMANDS,
@ -1025,9 +1026,9 @@ class PanelHandler(BaseHandler):
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id( page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id server_id
) )
page_data["server_stats"][ page_data["server_stats"]["server_type"] = (
"server_type" self.controller.servers.get_server_type_by_id(server_id)
] = self.controller.servers.get_server_type_by_id(server_id) )
page_data["new_webhook"] = True page_data["new_webhook"] = True
page_data["webhook"] = {} page_data["webhook"] = {}
page_data["webhook"]["webhook_type"] = "Custom" page_data["webhook"]["webhook_type"] = "Custom"
@ -1061,10 +1062,10 @@ class PanelHandler(BaseHandler):
page_data["server_data"] = self.controller.servers.get_server_data_by_id( page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id server_id
) )
page_data[ page_data["user_permissions"] = (
"user_permissions" self.controller.server_perms.get_user_id_permissions_list(
] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id
exec_user["user_id"], server_id )
) )
page_data["permissions"] = { page_data["permissions"] = {
"Commands": EnumPermissionsServer.COMMANDS, "Commands": EnumPermissionsServer.COMMANDS,
@ -1079,9 +1080,9 @@ class PanelHandler(BaseHandler):
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id( page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id server_id
) )
page_data["server_stats"][ page_data["server_stats"]["server_type"] = (
"server_type" self.controller.servers.get_server_type_by_id(server_id)
] = self.controller.servers.get_server_type_by_id(server_id) )
page_data["new_webhook"] = False page_data["new_webhook"] = False
page_data["webhook"] = self.controller.management.get_webhook_by_id( page_data["webhook"] = self.controller.management.get_webhook_by_id(
webhook_id webhook_id
@ -1121,10 +1122,10 @@ class PanelHandler(BaseHandler):
"Config": EnumPermissionsServer.CONFIG, "Config": EnumPermissionsServer.CONFIG,
"Players": EnumPermissionsServer.PLAYERS, "Players": EnumPermissionsServer.PLAYERS,
} }
page_data[ page_data["user_permissions"] = (
"user_permissions" self.controller.server_perms.get_user_id_permissions_list(
] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id
exec_user["user_id"], server_id )
) )
page_data["server_data"] = self.controller.servers.get_server_data_by_id( page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id server_id
@ -1132,9 +1133,9 @@ class PanelHandler(BaseHandler):
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id( page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id server_id
) )
page_data["server_stats"][ page_data["server_stats"]["server_type"] = (
"server_type" self.controller.servers.get_server_type_by_id(server_id)
] = self.controller.servers.get_server_type_by_id(server_id) )
page_data["new_schedule"] = True page_data["new_schedule"] = True
page_data["schedule"] = {} page_data["schedule"] = {}
page_data["schedule"]["children"] = [] page_data["schedule"]["children"] = []
@ -1189,10 +1190,10 @@ class PanelHandler(BaseHandler):
"Config": EnumPermissionsServer.CONFIG, "Config": EnumPermissionsServer.CONFIG,
"Players": EnumPermissionsServer.PLAYERS, "Players": EnumPermissionsServer.PLAYERS,
} }
page_data[ page_data["user_permissions"] = (
"user_permissions" self.controller.server_perms.get_user_id_permissions_list(
] = self.controller.server_perms.get_user_id_permissions_list( exec_user["user_id"], server_id
exec_user["user_id"], server_id )
) )
page_data["server_data"] = self.controller.servers.get_server_data_by_id( page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id server_id
@ -1200,9 +1201,9 @@ class PanelHandler(BaseHandler):
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id( page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id server_id
) )
page_data["server_stats"][ page_data["server_stats"]["server_type"] = (
"server_type" self.controller.servers.get_server_type_by_id(server_id)
] = self.controller.servers.get_server_type_by_id(server_id) )
page_data["new_schedule"] = False page_data["new_schedule"] = False
page_data["schedule"] = {} page_data["schedule"] = {}
page_data["schedule"]["server_id"] = server_id page_data["schedule"]["server_id"] = server_id
@ -1212,9 +1213,9 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["name"] = schedule.name page_data["schedule"]["name"] = schedule.name
else: else:
page_data["schedule"]["name"] = "" page_data["schedule"]["name"] = ""
page_data["schedule"][ page_data["schedule"]["children"] = (
"children" self.controller.management.get_child_schedules(sch_id)
] = self.controller.management.get_child_schedules(sch_id) )
# We check here to see if the command is any of the default ones. # We check here to see if the command is any of the default ones.
# We do not want a user changing to a custom command # We do not want a user changing to a custom command
# and seeing our command there. # and seeing our command there.
@ -1280,16 +1281,16 @@ class PanelHandler(BaseHandler):
} }
if exec_user["superuser"]: if exec_user["superuser"]:
page_data["users"] = self.controller.users.get_all_users() page_data["users"] = self.controller.users.get_all_users()
page_data[ page_data["permissions_all"] = (
"permissions_all" self.controller.crafty_perms.list_defined_crafty_permissions()
] = self.controller.crafty_perms.list_defined_crafty_permissions() )
page_data[ page_data["permissions_list"] = (
"permissions_list" self.controller.crafty_perms.get_crafty_permissions_list(user_id)
] = self.controller.crafty_perms.get_crafty_permissions_list(user_id) )
page_data[ page_data["quantity_server"] = (
"quantity_server" self.controller.crafty_perms.list_crafty_permissions_quantity_limits(
] = self.controller.crafty_perms.list_crafty_permissions_quantity_limits( user_id
user_id )
) )
page_data["languages"] = [] page_data["languages"] = []
page_data["languages"].append( page_data["languages"].append(
@ -1349,12 +1350,12 @@ class PanelHandler(BaseHandler):
page_data["user"] = self.controller.users.get_user_by_id(user_id) page_data["user"] = self.controller.users.get_user_by_id(user_id)
page_data["api_keys"] = self.controller.users.get_user_api_keys(user_id) page_data["api_keys"] = self.controller.users.get_user_api_keys(user_id)
# self.controller.crafty_perms.list_defined_crafty_permissions() # self.controller.crafty_perms.list_defined_crafty_permissions()
page_data[ page_data["server_permissions_all"] = (
"server_permissions_all" self.controller.server_perms.list_defined_permissions()
] = self.controller.server_perms.list_defined_permissions() )
page_data[ page_data["crafty_permissions_all"] = (
"crafty_permissions_all" self.controller.crafty_perms.list_defined_crafty_permissions()
] = self.controller.crafty_perms.list_defined_crafty_permissions() )
if user_id is None: if user_id is None:
self.redirect("/panel/error?error=Invalid User ID") self.redirect("/panel/error?error=Invalid User ID")
@ -1442,9 +1443,9 @@ class PanelHandler(BaseHandler):
DatabaseShortcuts.get_data_obj(server.server_object) DatabaseShortcuts.get_data_obj(server.server_object)
) )
page_data["servers_all"] = page_servers page_data["servers_all"] = page_servers
page_data[ page_data["permissions_all"] = (
"permissions_all" self.controller.server_perms.list_defined_permissions()
] = self.controller.server_perms.list_defined_permissions() )
page_data["permissions_dict"] = {} page_data["permissions_dict"] = {}
template = "panel/panel_edit_role.html" template = "panel/panel_edit_role.html"
@ -1467,12 +1468,12 @@ class PanelHandler(BaseHandler):
DatabaseShortcuts.get_data_obj(server.server_object) DatabaseShortcuts.get_data_obj(server.server_object)
) )
page_data["servers_all"] = page_servers page_data["servers_all"] = page_servers
page_data[ page_data["permissions_all"] = (
"permissions_all" self.controller.server_perms.list_defined_permissions()
] = self.controller.server_perms.list_defined_permissions() )
page_data[ page_data["permissions_dict"] = (
"permissions_dict" self.controller.server_perms.get_role_permissions_dict(role_id)
] = self.controller.server_perms.get_role_permissions_dict(role_id) )
page_data["user-roles"] = user_roles page_data["user-roles"] = user_roles
page_data["users"] = self.controller.users.get_all_users() page_data["users"] = self.controller.users.get_all_users()

View File

@ -80,9 +80,13 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
200, 200,
{ {
"status": "ok", "status": "ok",
"data": self.controller.roles.get_all_role_ids() "data": (
if get_only_ids self.controller.roles.get_all_role_ids()
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()], if get_only_ids
else [
model_to_dict(r) for r in self.controller.roles.get_all_roles()
]
),
}, },
) )
@ -158,9 +162,13 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
200, 200,
{ {
"status": "ok", "status": "ok",
"data": self.controller.roles.get_all_role_ids() "data": (
if get_only_ids self.controller.roles.get_all_role_ids()
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()], if get_only_ids
else [
model_to_dict(r) for r in self.controller.roles.get_all_roles()
]
),
}, },
) )

View File

@ -36,9 +36,13 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
200, 200,
{ {
"status": "ok", "status": "ok",
"data": self.controller.roles.get_all_role_ids() "data": (
if get_only_ids self.controller.roles.get_all_role_ids()
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()], if get_only_ids
else [
model_to_dict(r) for r in self.controller.roles.get_all_roles()
]
),
}, },
) )

View File

@ -87,9 +87,13 @@ class ApiRolesIndexHandler(BaseApiHandler):
200, 200,
{ {
"status": "ok", "status": "ok",
"data": self.controller.roles.get_all_role_ids() "data": (
if get_only_ids self.controller.roles.get_all_role_ids()
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()], if get_only_ids
else [
model_to_dict(r) for r in self.controller.roles.get_all_roles()
]
),
}, },
) )

View File

@ -25,8 +25,12 @@ class ApiRolesRoleServersHandler(BaseApiHandler):
200, 200,
{ {
"status": "ok", "status": "ok",
"data": PermissionsServers.get_server_ids_from_role(role_id) "data": (
if get_only_ids PermissionsServers.get_server_ids_from_role(role_id)
else self.controller.roles.get_server_ids_and_perms_from_role(role_id), if get_only_ids
else self.controller.roles.get_server_ids_and_perms_from_role(
role_id
)
),
}, },
) )

View File

@ -30,7 +30,15 @@ class ApiServersServerActionHandler(BaseApiHandler):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if action == "clone_server": if action == "clone_server":
return self._clone_server(server_id, auth_data[4]["user_id"]) if (
self.controller.crafty_perms.can_create_server(auth_data[4]["user_id"])
or auth_data[4]["superuser"]
):
self._clone_server(server_id, auth_data[4]["user_id"])
return self.finish_json(200, {"status": "ok"})
return self.finish_json(
200, {"status": "error", "error": "SERVER_LIMIT_REACHED"}
)
if action == "eula": if action == "eula":
return self._agree_eula(server_id, auth_data[4]["user_id"]) return self._agree_eula(server_id, auth_data[4]["user_id"])
@ -94,6 +102,13 @@ class ApiServersServerActionHandler(BaseApiHandler):
user_id, user_id,
server_data.get("server_port"), server_data.get("server_port"),
) )
for role in self.controller.server_perms.get_server_roles(server_id):
mask = self.controller.server_perms.get_permissions_mask(
role.role_id, server_id
)
self.controller.server_perms.add_role_server(
new_server_id, role.role_id, mask
)
self.controller.servers.init_all_servers() self.controller.servers.init_all_servers()

View File

@ -118,15 +118,17 @@ class ServerHandler(BaseHandler):
"lang_page": Helpers.get_lang_page( "lang_page": Helpers.get_lang_page(
self.controller.users.get_user_lang_by_id(exec_user["user_id"]) self.controller.users.get_user_lang_by_id(exec_user["user_id"])
), ),
"api_key": { "api_key": (
"name": api_key.name, {
"created": api_key.created, "name": api_key.name,
"server_permissions": api_key.server_permissions, "created": api_key.created,
"crafty_permissions": api_key.crafty_permissions, "server_permissions": api_key.server_permissions,
"superuser": api_key.superuser, "crafty_permissions": api_key.crafty_permissions,
} "superuser": api_key.superuser,
if api_key is not None }
else None, if api_key is not None
else None
),
"superuser": superuser, "superuser": superuser,
} }

View File

@ -598,26 +598,30 @@
</script> </script>
<script> <script>
function send_command(server_id, command) { async function send_command(server_id, command) {
/* this getCookie function is in base.html */ /* this getCookie function is in base.html */
const token = getCookie("_xsrf"); const token = getCookie("_xsrf");
$.ajax({ let res = await fetch(`/api/v2/servers/${server_id}/action/${command}`, {
type: "POST", method: 'POST',
headers: { 'X-XSRFToken': token }, headers: {
url: `/api/v2/servers/${server_id}/action/${command}`, 'token': token,
success: function (data) { },
console.log("got response:");
console.log(data);
if (command === "clone_server" && data.status === "ok") {
window.location.reload();
}
/*setTimeout(function () {
if (command != 'start_server') {
location.reload();
}
}, 10000);*/
}
}); });
let responseData = await res.json();
if (responseData.status === "ok") {
if (command === "clone_server"){
window.location.reload()
}
console.log("Command received successfully")
} else {
setTimeout(function(){
$('.modal').modal('hide');
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}, 2000)
}
} }

View File

@ -4,13 +4,13 @@ argon2-cffi==23.1.0
cached_property==1.5.2 cached_property==1.5.2
colorama==0.4.6 colorama==0.4.6
croniter==1.4.1 croniter==1.4.1
cryptography==41.0.7 cryptography==42.0.2
libgravatar==1.0.4 libgravatar==1.0.4
nh3==0.2.14 nh3==0.2.14
packaging==23.2 packaging==23.2
peewee==3.13 peewee==3.13
psutil==5.9.5 psutil==5.9.5
pyOpenSSL==23.3.0 pyOpenSSL==24.0.0
pyjwt==2.8.0 pyjwt==2.8.0
PyYAML==6.0.1 PyYAML==6.0.1
requests==2.31.0 requests==2.31.0