Merge branch 'dev' into 'master'

v4.3.0

See merge request crafty-controller/crafty-4!728
This commit is contained in:
Iain Powrie 2024-03-08 21:29:20 +00:00
commit 0a296a0095
65 changed files with 1207 additions and 548 deletions

View File

@ -0,0 +1,15 @@
#!/bin/bash
# Prompt the user for the directory path
read -p "Enter the directory path to set permissions (/var/opt/minecraft/crafty): " directory_path
# Check if the script is running within a Docker container
if [ -f "/.dockerenv" ]; then
echo "Script is running within a Docker container. Exiting with error."
exit 1 # Exit with an error code if running in Docker
else
echo "Script is not running within a Docker container. Executing permissions changes..."
# Run the commands to set permissions
sudo chmod 700 $(find "$directory_path" -type d)
sudo chmod 644 $(find "$directory_path" -type f)
fi

View File

@ -1,4 +1,28 @@
# Changelog # Changelog
## --- [4.3.0] - 2023/03/09
### Breaking Changes
- This release includes database migrations that are not revertable. Once you update to this version you will not be able to rollback to a previous version.
- In this release, we've implemented a breaking change to enhance server identification within Crafty: instead of relying on numerical integers (1, 2, 3, etc.), Servers are now uniquely identified by their UUIDs. Please adapt your API clients accordingly.
### Refactor
- Refactor remote file downloads ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/719))
### Bug fixes
- 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))
- Fix forge install v1.20, 1.20.1 and 1.20.2 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/710))
- Fix Sanitisation on Passwords ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/715) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/725))
- Fix `Upload Imports` on unix systems, that have a space in the root dir name ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/722))
- Fix Bedrock downloads, add `www` to download URL ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/723))
- Fire backup webhook 'after' backup has finished ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/727))
### Tweaks
- Bump pyOpenSSL & cryptography for CVE-2024-0727, CVE-2023-50782 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/716))
- Bump cryptography for CVE-2024-26130 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/724))
### Lang
- Update `de_DE, en_EN, es_ES, fr_FR, he_IL, lol_EN, lv_LV, nl_BE pl_PL, th_TH, tr_TR, uk_UA, zh_CN` translations for `4.3.0` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/715))
<br><br>
## --- [4.2.3] - 2023/02/02 ## --- [4.2.3] - 2023/02/02
### New features ### New features
- Use Papermc Group's API for `paper` & `folia` builds in server builder ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/688)) - Use Papermc Group's API for `paper` & `folia` builds in server builder ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/688))

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.2.3 # Crafty Controller 4.3.0
> Python based Control Panel for your Minecraft Server > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?

View File

@ -80,8 +80,8 @@ class ServersController(metaclass=Singleton):
PeeweeException: If the server already exists PeeweeException: If the server already exists
""" """
return HelperServers.create_server( return HelperServers.create_server(
name,
server_uuid, server_uuid,
name,
server_dir, server_dir,
backup_path, backup_path,
server_command, server_command,
@ -161,9 +161,9 @@ class ServersController(metaclass=Singleton):
# Servers Methods # Servers Methods
# ********************************************************************************** # **********************************************************************************
def get_server_instance_by_id(self, server_id: t.Union[str, int]) -> ServerInstance: def get_server_instance_by_id(self, server_id: t.Union[str, str]) -> ServerInstance:
for server in self.servers_list: for server in self.servers_list:
if int(server["server_id"]) == int(server_id): if server["server_id"] == server_id:
return server["server_obj"] return server["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}") logger.warning(f"Unable to find server object for server id {server_id}")

View File

@ -52,7 +52,7 @@ class UsersController:
}, },
"password": { "password": {
"type": "string", "type": "string",
"minLength": 8, "minLength": self.helper.minimum_password_length,
"examples": ["crafty"], "examples": ["crafty"],
"title": "Password", "title": "Password",
}, },

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

@ -31,9 +31,9 @@ class AuditLog(BaseModel):
user_name = CharField(default="") user_name = CharField(default="")
user_id = IntegerField(default=0, index=True) user_id = IntegerField(default=0, index=True)
source_ip = CharField(default="127.0.0.1") source_ip = CharField(default="127.0.0.1")
server_id = IntegerField( server_id = ForeignKeyField(
default=None, index=True Servers, backref="audit_server", null=True
) # When auditing global events, use server ID 0 ) # When auditing global events, use server ID null
log_msg = TextField(default="") log_msg = TextField(default="")
class Meta: class Meta:
@ -79,7 +79,7 @@ class HostStats(BaseModel):
# ********************************************************************************** # **********************************************************************************
class Webhooks(BaseModel): class Webhooks(BaseModel):
id = AutoField() id = AutoField()
server_id = IntegerField(null=True) server_id = ForeignKeyField(Servers, backref="webhook_server", null=True)
name = CharField(default="Custom Webhook", max_length=64) name = CharField(default="Custom Webhook", max_length=64)
url = CharField(default="") url = CharField(default="")
webhook_type = CharField(default="Custom") webhook_type = CharField(default="Custom")
@ -337,7 +337,7 @@ class HelpersManagement:
@staticmethod @staticmethod
def delete_scheduled_task_by_server(server_id): def delete_scheduled_task_by_server(server_id):
Schedules.delete().where(Schedules.server_id == int(server_id)).execute() Schedules.delete().where(Schedules.server_id == server_id).execute()
@staticmethod @staticmethod
def get_scheduled_task(schedule_id): def get_scheduled_task(schedule_id):

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

@ -71,7 +71,7 @@ class HelperServerStats:
database = None database = None
def __init__(self, server_id): def __init__(self, server_id):
self.server_id = int(server_id) self.server_id = server_id
self.init_database(self.server_id) self.init_database(self.server_id)
def init_database(self, server_id): def init_database(self, server_id):

View File

@ -3,7 +3,6 @@ import datetime
import typing as t import typing as t
from peewee import ( from peewee import (
CharField, CharField,
AutoField,
DateTimeField, DateTimeField,
BooleanField, BooleanField,
IntegerField, IntegerField,
@ -13,6 +12,9 @@ from playhouse.shortcuts import model_to_dict
from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.models.base_model import BaseModel from app.classes.models.base_model import BaseModel
# from app.classes.models.users import Users
from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -20,9 +22,8 @@ logger = logging.getLogger(__name__)
# Servers Model # Servers Model
# ********************************************************************************** # **********************************************************************************
class Servers(BaseModel): class Servers(BaseModel):
server_id = AutoField() server_id = CharField(primary_key=True, default=Helpers.create_uuid())
created = DateTimeField(default=datetime.datetime.now) created = DateTimeField(default=datetime.datetime.now)
server_uuid = CharField(default="", index=True)
server_name = CharField(default="Server", index=True) server_name = CharField(default="Server", index=True)
path = CharField(default="") path = CharField(default="")
backup_path = CharField(default="") backup_path = CharField(default="")
@ -40,6 +41,7 @@ class Servers(BaseModel):
type = CharField(default="minecraft-java") type = CharField(default="minecraft-java")
show_status = BooleanField(default=1) show_status = BooleanField(default=1)
created_by = IntegerField(default=-100) created_by = IntegerField(default=-100)
# created_by = ForeignKeyField(Users, backref="creator_server", null=True)
shutdown_timeout = IntegerField(default=60) shutdown_timeout = IntegerField(default=60)
ignored_exits = CharField(default="0") ignored_exits = CharField(default="0")
count_players = BooleanField(default=True) count_players = BooleanField(default=True)
@ -60,8 +62,8 @@ class HelperServers:
# ********************************************************************************** # **********************************************************************************
@staticmethod @staticmethod
def create_server( def create_server(
server_id: str,
name: str, name: str,
server_uuid: str,
server_dir: str, server_dir: str,
backup_path: str, backup_path: str,
server_command: str, server_command: str,
@ -95,25 +97,24 @@ class HelperServers:
Raises: Raises:
PeeweeException: If the server already exists PeeweeException: If the server already exists
""" """
return Servers.insert( return Servers.create(
{ server_id=server_id,
Servers.server_name: name, server_uuid=server_id,
Servers.server_uuid: server_uuid, server_name=name,
Servers.path: server_dir, path=server_dir,
Servers.executable: server_file, executable=server_file,
Servers.execution_command: server_command, execution_command=server_command,
Servers.auto_start: False, auto_start=False,
Servers.auto_start_delay: 10, auto_start_delay=10,
Servers.crash_detection: False, crash_detection=False,
Servers.log_path: server_log_file, log_path=server_log_file,
Servers.server_port: server_port, server_port=server_port,
Servers.server_ip: server_host, server_ip=server_host,
Servers.stop_command: server_stop, stop_command=server_stop,
Servers.backup_path: backup_path, backup_path=backup_path,
Servers.type: server_type, type=server_type,
Servers.created_by: created_by, created_by=created_by,
} ).server_id
).execute()
@staticmethod @staticmethod
def get_server_obj(server_id): def get_server_obj(server_id):

View File

@ -18,7 +18,12 @@ logger = logging.getLogger(__name__)
class MainPrompt(cmd.Cmd): class MainPrompt(cmd.Cmd):
def __init__( def __init__(
self, helper, tasks_manager, migration_manager, main_controller, import3 self,
helper,
tasks_manager,
migration_manager,
main_controller,
import3,
): ):
super().__init__() super().__init__()
self.helper: Helpers = helper self.helper: Helpers = helper
@ -77,11 +82,11 @@ class MainPrompt(cmd.Cmd):
# get new password from user # get new password from user
new_pass = getpass.getpass(prompt=f"NEW password for: {username} > ") new_pass = getpass.getpass(prompt=f"NEW password for: {username} > ")
# check to make sure it fits our requirements. # check to make sure it fits our requirements.
if len(new_pass) > 512: if len(new_pass) < self.helper.minimum_password_length:
Console.warning("Passwords must be greater than 6char long and under 512") Console.warning(
return False "Passwords must be greater than"
if len(new_pass) < 6: f" {self.helper.minimum_password_length} char long"
Console.warning("Passwords must be greater than 6char long and under 512") )
return False return False
# grab repeated password input # grab repeated password input
new_pass_conf = getpass.getpass(prompt="Re-enter your password: > ") new_pass_conf = getpass.getpass(prompt="Re-enter your password: > ")

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

@ -81,6 +81,7 @@ class Helpers:
self.update_available = False self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"] self.ignored_names = ["crafty_managed.txt", "db_stats"]
self.crafty_starting = False self.crafty_starting = False
self.minimum_password_length = 8
@staticmethod @staticmethod
def auto_installer_fix(ex): def auto_installer_fix(ex):
@ -117,7 +118,7 @@ class Helpers:
Get latest bedrock executable url \n\n Get latest bedrock executable url \n\n
returns url if successful, False if not returns url if successful, False if not
""" """
url = "https://minecraft.net/en-us/download/server/bedrock/" url = "https://www.minecraft.net/en-us/download/server/bedrock/"
headers = { headers = {
"Accept-Encoding": "identity", "Accept-Encoding": "identity",
"Accept-Language": "en", "Accept-Language": "en",
@ -1112,7 +1113,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 +1181,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

@ -239,7 +239,7 @@ class Controller:
try: try:
os.mkdir(final_path) os.mkdir(final_path)
except FileExistsError: except FileExistsError:
final_path += "_" + server["server_uuid"] final_path += "_" + server["server_id"]
os.mkdir(final_path) os.mkdir(final_path)
try: try:
FileHelpers.copy_file( FileHelpers.copy_file(
@ -632,11 +632,11 @@ class Controller:
# and add the user to it if he's not a superuser # and add the user to it if he's not a superuser
if len(captured_roles) == 0: if len(captured_roles) == 0:
if not exec_user["superuser"]: if not exec_user["superuser"]:
new_server_uuid = self.servers.get_server_data_by_id(new_server_id).get( new_server_id = self.servers.get_server_data_by_id(new_server_id).get(
"server_uuid" "server_id"
) )
role_id = self.roles.add_role( role_id = self.roles.add_role(
f"Creator of Server with uuid={new_server_uuid}", f"Creator of Server with id={new_server_id}",
exec_user["user_id"], exec_user["user_id"],
) )
self.server_perms.add_role_server(new_server_id, role_id, "11111111") self.server_perms.add_role_server(new_server_id, role_id, "11111111")
@ -647,7 +647,7 @@ class Controller:
role_id = role role_id = role
self.server_perms.add_role_server(new_server_id, role_id, "11111111") self.server_perms.add_role_server(new_server_id, role_id, "11111111")
return new_server_id, server_fs_uuid return new_server_id
@staticmethod @staticmethod
def verify_jar_server(server_path: str, server_jar: str): def verify_jar_server(server_path: str, server_jar: str):
@ -1095,7 +1095,7 @@ class Controller:
for server in servers: for server in servers:
server_path = server.get("path") server_path = server.get("path")
new_local_server_path = os.path.join( new_local_server_path = os.path.join(
new_server_path, server.get("server_uuid") new_server_path, server.get("server_id")
) )
if os.path.isdir(server_path): if os.path.isdir(server_path):
WebSocketManager().broadcast_page( WebSocketManager().broadcast_page(

View File

@ -18,13 +18,22 @@ class DatabaseBuilder:
logger.info("Fresh Install Detected - Creating Default Settings") logger.info("Fresh Install Detected - Creating Default Settings")
Console.info("Fresh Install Detected - Creating Default Settings") Console.info("Fresh Install Detected - Creating Default Settings")
default_data = self.helper.find_default_password() default_data = self.helper.find_default_password()
if password not in default_data: if "password" not in default_data:
Console.help( Console.help(
"No default password found. Using password created " "No default password found. Using password created "
"by Crafty. Find it in app/config/default-creds.txt" "by Crafty. Find it in app/config/default-creds.txt"
) )
username = default_data.get("username", "admin") username = default_data.get("username", "admin")
password = default_data.get("password", password) if self.helper.minimum_password_length > len(
default_data.get("password", password)
):
Console.critical(
"Default password too short"
" using Crafty's created default."
" Find it in app/config/default-creds.txt"
)
else:
password = default_data.get("password", password)
self.users_helper.add_user( self.users_helper.add_user(
username=username, username=username,

View File

@ -200,6 +200,21 @@ class Migrator(object):
) )
return model return model
@get_model
def alter_column_type(
self,
model: peewee.Model,
column_name: str,
field: peewee.Field,
) -> peewee.Model:
"""
Alter field data type in database.
"""
self.operations.append(
self.migrator.alter_column_type(model._meta.table_name, column_name, field)
)
return model
@get_model @get_model
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model: def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
""" """

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
@ -209,7 +208,7 @@ class ServerInstance:
self.dir_scheduler.start() self.dir_scheduler.start()
self.start_dir_calc_task() self.start_dir_calc_task()
self.backup_thread = threading.Thread( self.backup_thread = threading.Thread(
target=self.a_backup_server, daemon=True, name=f"backup_{self.name}" target=self.backup_server, daemon=True, name=f"backup_{self.name}"
) )
self.is_backingup = False self.is_backingup = False
# Reset crash and update at initialization # Reset crash and update at initialization
@ -697,6 +696,10 @@ class ServerInstance:
version_param = version[0][0].split(".") version_param = version[0][0].split(".")
version_major = int(version_param[0]) version_major = int(version_param[0])
version_minor = int(version_param[1]) version_minor = int(version_param[1])
if len(version_param) > 2:
version_sub = int(version_param[2])
else:
version_sub = 0
# Checking which version we are with # Checking which version we are with
if version_major <= 1 and version_minor < 17: if version_major <= 1 and version_minor < 17:
@ -730,8 +733,8 @@ class ServerInstance:
server_obj.execution_command = execution_command server_obj.execution_command = execution_command
Console.debug(SUCCESSMSG) Console.debug(SUCCESSMSG)
elif version_major <= 1 and version_minor < 20: elif version_major <= 1 and version_minor <= 20 and version_sub < 3:
# NEW VERSION >= 1.17 and <= 1.20 # NEW VERSION >= 1.17 and <= 1.20.2
# (no jar file in server dir, only run.bat and run.sh) # (no jar file in server dir, only run.bat and run.sh)
run_file_path = "" run_file_path = ""
@ -778,7 +781,7 @@ class ServerInstance:
server_obj.execution_command = execution_command server_obj.execution_command = execution_command
Console.debug(SUCCESSMSG) Console.debug(SUCCESSMSG)
else: else:
# NEW VERSION >= 1.20 # NEW VERSION >= 1.20.3
# (executable jar is back in server dir) # (executable jar is back in server dir)
# Retrieving the executable jar filename # Retrieving the executable jar filename
@ -1107,13 +1110,12 @@ class ServerInstance:
f.write("eula=true") f.write("eula=true")
self.run_threaded_server(user_id) self.run_threaded_server(user_id)
@callback def a_backup_server(self):
def backup_server(self):
if self.settings["backup_path"] == "": if self.settings["backup_path"] == "":
logger.critical("Backup path is None. Canceling Backup!") logger.critical("Backup path is None. Canceling Backup!")
return return
backup_thread = threading.Thread( backup_thread = threading.Thread(
target=self.a_backup_server, daemon=True, name=f"backup_{self.name}" target=self.backup_server, daemon=True, name=f"backup_{self.name}"
) )
logger.info( logger.info(
f"Starting Backup Thread for server {self.settings['server_name']}." f"Starting Backup Thread for server {self.settings['server_name']}."
@ -1140,7 +1142,8 @@ class ServerInstance:
return False return False
logger.info(f"Backup Thread started for server {self.settings['server_name']}.") logger.info(f"Backup Thread started for server {self.settings['server_name']}.")
def a_backup_server(self): @callback
def backup_server(self):
was_server_running = None was_server_running = None
logger.info(f"Starting server {self.name} (ID {self.server_id}) backup") logger.info(f"Starting server {self.name} (ID {self.server_id}) backup")
server_users = PermissionsServers.get_server_user_list(self.server_id) server_users = PermissionsServers.get_server_user_list(self.server_id)
@ -1370,7 +1373,7 @@ class ServerInstance:
def a_jar_update(self): def a_jar_update(self):
server_users = PermissionsServers.get_server_user_list(self.server_id) server_users = PermissionsServers.get_server_user_list(self.server_id)
was_started = "-1" was_started = "-1"
self.backup_server() self.a_backup_server()
# checks if server is running. Calls shutdown if it is running. # checks if server is running. Calls shutdown if it is running.
if self.check_running(): if self.check_running():
was_started = True was_started = True
@ -1450,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

@ -140,7 +140,7 @@ class TasksManager:
) )
elif command == "backup_server": elif command == "backup_server":
svr.backup_server() svr.a_backup_server()
elif command == "update_executable": elif command == "update_executable":
svr.jar_update() svr.jar_update()

View File

@ -174,7 +174,7 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID") self.redirect("/panel/error?error=Invalid Server ID")
return None return None
for server in self.controller.servers.failed_servers: for server in self.controller.servers.failed_servers:
if int(server_id) == server["server_id"]: if server_id == server["server_id"]:
self.failed_server = True self.failed_server = True
return server_id return server_id
# Does this server exist? # Does this server exist?
@ -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
@ -554,7 +556,7 @@ class PanelHandler(BaseHandler):
"server_id": { "server_id": {
"server_id": server_id, "server_id": server_id,
"server_name": server_temp_obj["server_name"], "server_name": server_temp_obj["server_name"],
"server_uuid": server_temp_obj["server_uuid"], "server_uuid": server_temp_obj["server_id"],
"path": server_temp_obj["path"], "path": server_temp_obj["path"],
"log_path": server_temp_obj["log_path"], "log_path": server_temp_obj["log_path"],
"executable": server_temp_obj["executable"], "executable": server_temp_obj["executable"],
@ -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

@ -1,5 +1,8 @@
import logging import logging
import json
import nh3 import nh3
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from app.classes.shared.helpers import Helpers from app.classes.shared.helpers import Helpers
from app.classes.models.users import HelperUsers from app.classes.models.users import HelperUsers
@ -45,7 +48,7 @@ class PublicHandler(BaseHandler):
} }
if self.request.query: if self.request.query:
page_data["query"] = self.request.query page_data["query"] = self.request.query_arguments.get("next")[0].decode()
# sensible defaults # sensible defaults
template = "public/404.html" template = "public/404.html"
@ -75,11 +78,7 @@ class PublicHandler(BaseHandler):
# if we have no page, let's go to login # if we have no page, let's go to login
else: else:
if self.request.query: return self.redirect("/login")
self.redirect("/login?" + self.request.query)
else:
self.redirect("/login")
return
self.render( self.render(
template, template,
@ -89,33 +88,61 @@ class PublicHandler(BaseHandler):
) )
def post(self, page=None): def post(self, page=None):
# pylint: disable=no-member login_schema = {
error = nh3.clean(self.get_argument("error", "Invalid Login!")) "type": "object",
error_msg = nh3.clean(self.get_argument("error_msg", "")) "properties": {
# pylint: enable=no-member "username": {
"type": "string",
},
"password": {"type": "string"},
},
"required": ["username", "password"],
"additionalProperties": False,
}
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
logger.error(
"Invalid JSON schema for API"
f" login attempt from {self.get_remote_ip()}"
)
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, login_schema)
except ValidationError as e:
logger.error(
"Invalid JSON schema for API"
f" login attempt from {self.get_remote_ip()}"
)
return self.finish_json(
400,
{
"status": "error",
"error": "VWggb2ghIFN0aW5reS 🪠",
"error_data": str(e),
},
)
page_data = { page_data = {
"version": self.helper.get_version_string(), "version": self.helper.get_version_string(),
"error": error,
"lang": self.helper.get_setting("language"), "lang": self.helper.get_setting("language"),
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")), "lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"query": "", "query": "",
} }
if self.request.query: if self.request.query:
page_data["query"] = self.request.query page_data["query"] = self.request.query_arguments.get("next")[0].decode()
if page == "login": if page == "login":
data = json.loads(self.request.body)
auth_log.info( auth_log.info(
f"User attempting to authenticate from {self.get_remote_ip()}" f"User attempting to authenticate from {self.get_remote_ip()}"
) )
next_page = "/login" entered_username = nh3.clean(data["username"]) # pylint: disable=no-member
if self.request.query: entered_password = data["password"]
next_page = "/login?" + self.request.query
# pylint: disable=no-member
entered_username = nh3.clean(self.get_argument("username"))
entered_password = self.get_argument("password")
# pylint: enable=no-member
try: try:
user_id = HelperUsers.get_user_id_by_name(entered_username.lower()) user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
@ -127,16 +154,18 @@ class PublicHandler(BaseHandler):
f" Authentication failed from remote IP {self.get_remote_ip()}" f" Authentication failed from remote IP {self.get_remote_ip()}"
" Users does not exist." " Users does not exist."
) )
error_msg = "Incorrect username or password. Please try again." self.finish_json(
403,
{
"status": "error",
"error": self.helper.translation.translate(
"login", "incorrect", self.helper.get_setting("language")
),
},
)
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") return self.clear_cookie("token")
if self.request.query:
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/login?error_msg={error_msg}")
return
# if we don't have a user # if we don't have a user
if not user_data: if not user_data:
auth_log.error( auth_log.error(
@ -145,15 +174,18 @@ class PublicHandler(BaseHandler):
" User does not exist." " User does not exist."
) )
self.controller.log_attempt(self.get_remote_ip(), entered_username) self.controller.log_attempt(self.get_remote_ip(), entered_username)
error_msg = "Incorrect username or password. Please try again." self.finish_json(
403,
{
"status": "error",
"error": self.helper.translation.translate(
"login", "incorrect", self.helper.get_setting("language")
),
},
)
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") return self.clear_cookie("token")
if self.request.query:
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/login?error_msg={error_msg}")
return
# if they are disabled # if they are disabled
if not user_data.enabled: if not user_data.enabled:
@ -163,19 +195,18 @@ class PublicHandler(BaseHandler):
" User account disabled" " User account disabled"
) )
self.controller.log_attempt(self.get_remote_ip(), entered_username) self.controller.log_attempt(self.get_remote_ip(), entered_username)
error_msg = ( self.finish_json(
"User account disabled. Please contact " 403,
"your system administrator for more info." {
"status": "error",
"error": self.helper.translation.translate(
"login", "disabled", self.helper.get_setting("language")
),
},
) )
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") return self.clear_cookie("token")
if self.request.query:
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/login?error_msg={error_msg}")
return
login_result = self.helper.verify_pass(entered_password, user_data.password) login_result = self.helper.verify_pass(entered_password, user_data.password)
# Valid Login # Valid Login
@ -200,32 +231,34 @@ class PublicHandler(BaseHandler):
user_data.user_id, "Logged in", 0, self.get_remote_ip() user_data.user_id, "Logged in", 0, self.get_remote_ip()
) )
if self.request.query_arguments.get("next"): return self.finish_json(
next_page = self.request.query_arguments.get("next")[0].decode() 200, {"status": "ok", "data": {"message": "login successful!"}}
else: )
next_page = "/panel/dashboard"
self.redirect(next_page) # We'll continue on and handle unsuccessful logins
else: auth_log.error(
auth_log.error( f"User attempted to log into {entered_username}."
f"User attempted to log into {entered_username}." f" Authentication failed from remote IP {self.get_remote_ip()}"
f" Authentication failed from remote IP {self.get_remote_ip()}" )
self.controller.log_attempt(self.get_remote_ip(), entered_username)
# self.clear_cookie("user")
# self.clear_cookie("user_data")
self.clear_cookie("token")
error_msg = self.helper.translation.translate(
"login", "incorrect", self.helper.get_setting("language")
)
if entered_password == "app/config/default-creds.txt":
error_msg += ". "
error_msg += self.helper.translation.translate(
"login", "defaultPath", self.helper.get_setting("language")
) )
self.controller.log_attempt(self.get_remote_ip(), entered_username) # log this failed login attempt
# self.clear_cookie("user") self.controller.management.add_to_audit_log(
# self.clear_cookie("user_data") user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
self.clear_cookie("token") )
error_msg = "Incorrect username or password. Please try again." return self.finish_json(
# log this failed login attempt 403,
self.controller.management.add_to_audit_log( {"status": "error", "error": error_msg},
user_data.user_id, "Tried to log in", 0, self.get_remote_ip() )
)
if self.request.query:
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/login?error_msg={error_msg}")
else: else:
if self.request.query: self.redirect("/login?")
self.redirect("/login?" + self.request.query)
else:
self.redirect("/login")

View File

@ -208,92 +208,92 @@ def api_handlers(handler_args):
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/?", r"/api/v2/servers/([a-z0-9-]+)/?",
ApiServersServerIndexHandler, ApiServersServerIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/backups/?", r"/api/v2/servers/([a-z0-9-]+)/backups/?",
ApiServersServerBackupsIndexHandler, ApiServersServerBackupsIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/backups/backup/?", r"/api/v2/servers/([a-z0-9-]+)/backups/backup/?",
ApiServersServerBackupsBackupIndexHandler, ApiServersServerBackupsBackupIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/files/?", r"/api/v2/servers/([a-z0-9-]+)/files/?",
ApiServersServerFilesIndexHandler, ApiServersServerFilesIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/files/create/?", r"/api/v2/servers/([a-z0-9-]+)/files/create/?",
ApiServersServerFilesCreateHandler, ApiServersServerFilesCreateHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/files/zip/?", r"/api/v2/servers/([a-z0-9-]+)/files/zip/?",
ApiServersServerFilesZipHandler, ApiServersServerFilesZipHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/tasks/?", r"/api/v2/servers/([a-z0-9-]+)/tasks/?",
ApiServersServerTasksIndexHandler, ApiServersServerTasksIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/tasks/([0-9]+)/?", r"/api/v2/servers/([a-z0-9-]+)/tasks/([0-9]+)/?",
ApiServersServerTasksTaskIndexHandler, ApiServersServerTasksTaskIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/tasks/([0-9]+)/children/?", r"/api/v2/servers/([a-z0-9-]+)/tasks/([0-9]+)/children/?",
ApiServersServerTasksTaskChildrenHandler, ApiServersServerTasksTaskChildrenHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/stats/?", r"/api/v2/servers/([a-z0-9-]+)/stats/?",
ApiServersServerStatsHandler, ApiServersServerStatsHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/history/?", r"/api/v2/servers/([a-z0-9-]+)/history/?",
ApiServersServerHistoryHandler, ApiServersServerHistoryHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/webhook/([0-9]+)/?", r"/api/v2/servers/([a-z0-9-]+)/webhook/([0-9]+)/?",
ApiServersServerWebhooksManagementIndexHandler, ApiServersServerWebhooksManagementIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/webhook/?", r"/api/v2/servers/([a-z0-9-]+)/webhook/?",
ApiServersServerWebhooksIndexHandler, ApiServersServerWebhooksIndexHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/action/([a-z_]+)/?", r"/api/v2/servers/([a-z0-9-]+)/action/([a-z_]+)/?",
ApiServersServerActionHandler, ApiServersServerActionHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/logs/?", r"/api/v2/servers/([a-z0-9-]+)/logs/?",
ApiServersServerLogsHandler, ApiServersServerLogsHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/users/?", r"/api/v2/servers/([a-z0-9-]+)/users/?",
ApiServersServerUsersHandler, ApiServersServerUsersHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/public/?", r"/api/v2/servers/([a-z0-9-]+)/public/?",
ApiServersServerPublicHandler, ApiServersServerPublicHandler,
handler_args, handler_args,
), ),
( (
r"/api/v2/servers/([0-9]+)/stdin/?", r"/api/v2/servers/([a-z0-9-]+)/stdin/?",
ApiServersServerStdinHandler, ApiServersServerStdinHandler,
handler_args, handler_args,
), ),

View File

@ -17,7 +17,7 @@ login_schema = {
"minLength": 4, "minLength": 4,
"pattern": "^[a-z0-9_]+$", "pattern": "^[a-z0-9_]+$",
}, },
"password": {"type": "string", "maxLength": 20, "minLength": 4}, "password": {"type": "string", "minLength": 4},
}, },
"required": ["username", "password"], "required": ["username", "password"],
"additionalProperties": False, "additionalProperties": False,

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

@ -723,9 +723,7 @@ class ApiServersIndexHandler(BaseApiHandler):
405, {"status": "error", "error": "DATA CONSTRAINT FAILED"} 405, {"status": "error", "error": "DATA CONSTRAINT FAILED"}
) )
return return
new_server_id, new_server_uuid = self.controller.create_api_server( new_server_id = self.controller.create_api_server(data, user["user_id"])
data, user["user_id"]
)
self.controller.servers.stats.record_stats() self.controller.servers.stats.record_stats()
@ -734,7 +732,7 @@ class ApiServersIndexHandler(BaseApiHandler):
( (
f"created server {data['name']}" f"created server {data['name']}"
f" (ID: {new_server_id})" f" (ID: {new_server_id})"
f" (UUID: {new_server_uuid})" f" (UUID: {new_server_id})"
), ),
server_id=new_server_id, server_id=new_server_id,
source_ip=self.get_remote_ip(), source_ip=self.get_remote_ip(),
@ -746,7 +744,7 @@ class ApiServersIndexHandler(BaseApiHandler):
"status": "ok", "status": "ok",
"data": { "data": {
"new_server_id": str(new_server_id), "new_server_id": str(new_server_id),
"new_server_uuid": new_server_uuid, "new_server_uuid": new_server_id,
}, },
}, },
) )

View File

@ -3,7 +3,6 @@ import os
from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.models.servers import Servers from app.classes.models.servers import Servers
from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
@ -30,7 +29,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"])
@ -60,10 +67,20 @@ class ApiServersServerActionHandler(BaseApiHandler):
name_counter += 1 name_counter += 1
new_server_name = server_data.get("server_name") + f" (Copy {name_counter})" new_server_name = server_data.get("server_name") + f" (Copy {name_counter})"
new_server_uuid = Helpers.create_uuid() new_server_id = self.controller.servers.create_server(
while os.path.exists(os.path.join(self.helper.servers_dir, new_server_uuid)): new_server_name,
new_server_uuid = Helpers.create_uuid() None,
new_server_path = os.path.join(self.helper.servers_dir, new_server_uuid) "",
None,
server_data.get("executable"),
None,
server_data.get("stop_command"),
server_data.get("type"),
user_id,
server_data.get("server_port"),
)
new_server_path = os.path.join(self.helper.servers_dir, new_server_id)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
user_id, user_id,
@ -81,19 +98,19 @@ class ApiServersServerActionHandler(BaseApiHandler):
self.helper.get_os_understandable_path(server_data.get("log_path")) self.helper.get_os_understandable_path(server_data.get("log_path"))
) )
new_server_id = self.controller.servers.create_server( server: Servers = self.controller.servers.get_server_obj(new_server_id)
new_server_name, server.path = new_server_path
new_server_uuid, server.log_path = new_server_log_file
new_server_path, server.execution_command = new_server_command
"", self.controller.servers.update_server(server)
new_server_command,
server_data.get("executable"), for role in self.controller.server_perms.get_server_roles(server_id):
new_server_log_file, mask = self.controller.server_perms.get_permissions_mask(
server_data.get("stop_command"), role.role_id, server_id
server_data.get("type"), )
user_id, self.controller.server_perms.add_role_server(
server_data.get("server_port"), new_server_id, role.role_id, mask
) )
self.controller.servers.init_all_servers() self.controller.servers.init_all_servers()

View File

@ -145,7 +145,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
new_server_id = new_server new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server) new_server = self.controller.servers.get_server_data(new_server)
self.controller.rename_backup_dir( self.controller.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"] server_id, new_server_id, new_server["server_id"]
) )
# preserve current schedules # preserve current schedules
for schedule in self.controller.management.get_schedules_by_server( for schedule in self.controller.management.get_schedules_by_server(

View File

@ -176,7 +176,7 @@ class ApiServersServerIndexHandler(BaseApiHandler):
self.tasks_manager.remove_all_server_tasks(server_id) self.tasks_manager.remove_all_server_tasks(server_id)
failed = False failed = False
for item in self.controller.servers.failed_servers[:]: for item in self.controller.servers.failed_servers[:]:
if item["server_id"] == int(server_id): if item["server_id"] == server_id:
self.controller.servers.failed_servers.remove(item) self.controller.servers.failed_servers.remove(item)
failed = True failed = True

View File

@ -17,7 +17,7 @@ def metrics_handlers(handler_args):
handler_args, handler_args,
), ),
( (
r"/metrics/servers/([0-9]+)/?", r"/metrics/servers/([a-z0-9-]+)/?",
ApiOpenMetricsServersHandler, ApiOpenMetricsServersHandler,
handler_args, handler_args,
), ),

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

@ -1,5 +1,5 @@
{ {
"major": 4, "major": 4,
"minor": 2, "minor": 3,
"sub": 3 "sub": 0
} }

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

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }} data['server_stats']['server_id']['server_name'] }}
<br /> <br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> <small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4> </h4>
</div> </div>
</div> </div>

View File

@ -77,55 +77,49 @@
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4); box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
} }
</style> </style>
{% if data['query'] %} <form id="login-form" data-query="{{ data.get('query', None) }}">
<form action="/login?{{ data['query'] }}" method="post"> {% raw xsrf_form_html() %}
{% else %} <div class="form-group">
<form action="/login" method="post"> <label class="label">{{ translate('login', 'username', data['lang']) }}</label>
{% end %} <div class="input-group">
{% raw xsrf_form_html() %} <input type="text" class="form-control login-text-input login-input"
<div class="form-group"> placeholder="{{ translate('login', 'username', data['lang']) }}" name="username" id="username"
<label class="label">{{ translate('login', 'username', data['lang']) }}</label> required="true">
<div class="input-group">
<input type="text" class="form-control login-text-input login-input"
placeholder="{{ translate('login', 'username', data['lang']) }}" name="username" id="username"
required="true">
</div>
</div> </div>
<div class="form-group"> </div>
<label class="label">{{ translate('login', 'password', data['lang']) }}</label> <div class="form-group">
<div class="input-group"> <label class="label">{{ translate('login', 'password', data['lang']) }}</label>
<input type="password" class="form-control login-text-input login-input" <div class="input-group">
placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password" <input type="password" class="form-control login-text-input login-input"
required="true"> placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password"
</div> required="true">
</div> </div>
<div class="form-group"> </div>
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login', <div class="form-group">
data['lang']) }}</button> <button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login',
</div> data['lang']) }}</button>
{% if error_msg is not None %} </div>
<fieldset style="color: red; text-align: center;"> <fieldset id="error-field" style="color: red; text-align: center;">
<span>{{error_msg}}</span> </fieldset>
</fieldset> <div class="form-group d-flex justify-content-between">
{% end %} <div class="form-check form-check-flat mt-0">
<div class="form-group d-flex justify-content-between"> &nbsp;
<div class="form-check form-check-flat mt-0">
&nbsp;
</div>
<button onclick="resetPass()" id="#resetPass" form="" class="btn btn-outline-primary btn-sm forgot-password ">{{ translate('login', 'forgotPassword',
data['lang']) }}</button>
</div> </div>
<button onclick="resetPass()" id="#resetPass" form=""
class="btn btn-outline-primary btn-sm forgot-password ">{{ translate('login', 'forgotPassword',
data['lang']) }}</button>
</div>
<div class="text-block text-center my-3"> <div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control <span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
{{data['version'] }}</a> </span> {{data['version'] }}</a> </span>
</div> </div>
<div class="text-block text-center my-3"> <div class="text-block text-center my-3">
<a href="/status" class="text-small forgot-password">{{ translate('login', 'viewStatus', <a href="/status" class="text-small forgot-password">{{ translate('login', 'viewStatus',
data['lang']) }}</a> data['lang']) }}</a>
</div> </div>
</form> </form>
</div> </div>
@ -155,13 +149,13 @@
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')'; document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app //Register Service worker for mobile app
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'}) navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', { scope: '/' })
.then(function (registration) { .then(function (registration) {
console.log('Service Worker Registered'); console.log('Service Worker Registered');
}); });
} }
}); });
async function resetPass(){ async function resetPass() {
let res = await fetch(`/api/v2/crafty/resetPass/`, { let res = await fetch(`/api/v2/crafty/resetPass/`, {
method: 'GET', method: 'GET',
}); });
@ -170,7 +164,38 @@
bootbox.alert(responseData.data) bootbox.alert(responseData.data)
} }
$("#login-form").on("submit", async function (e) {
e.preventDefault();
let loginForm = document.getElementById("login-form");
let formData = new FormData(loginForm);
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
console.log(formDataObject)
let res = await fetch(`/login`, {
method: 'POST',
headers: {
'X-XSRFToken': formDataObject._xsrf,
"Content-Type": "application/json"
},
body: JSON.stringify({
"username": formDataObject.username,
"password": formDataObject.password
}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
console.log("OK")
if ($("#login-form").data("query")) {
location.href = `${$("#login-form").data("query")}`;
} else {
location.href = `/panel/dashboard`
}
} else {
$("#error-field").html(responseData.error);
}
});
</script> </script>
<style> <style>
.modal-content { .modal-content {

View File

@ -556,7 +556,7 @@
xmlHttpRequest.addEventListener('load', (event) => { xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!') console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = `<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value=${fileName} type="text" id="file-uploaded" disabled></input> 🔒</div>`; $("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${decodeURIComponent(fileName)}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
} }
else { else {

View File

@ -881,7 +881,7 @@
xmlHttpRequest.addEventListener('load', (event) => { xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!') console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = `<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value=${fileName} type="text" id="file-uploaded" disabled></input> 🔒</div>`; $("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${fileName}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
document.getElementById("lower_half").hidden = false; document.getElementById("lower_half").hidden = false;
} }

View File

@ -0,0 +1,244 @@
import datetime
import uuid
import peewee
import logging
from app.classes.shared.console import Console
from app.classes.shared.migration import Migrator, MigrateHistory
from app.classes.models.management import (
AuditLog,
Webhooks,
Schedules,
Backups,
)
from app.classes.models.server_permissions import RoleServers
logger = logging.getLogger(__name__)
def migrate(migrator: Migrator, database, **kwargs):
"""
Write your migrations here.
"""
db = database
# **********************************************************************************
# Servers New Model from Old (easier to migrate without dunmping Database)
# **********************************************************************************
class Servers(peewee.Model):
server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4()))
created = peewee.DateTimeField(default=datetime.datetime.now)
server_uuid = peewee.CharField(default="", index=True)
server_name = peewee.CharField(default="Server", index=True)
path = peewee.CharField(default="")
backup_path = peewee.CharField(default="")
executable = peewee.CharField(default="")
log_path = peewee.CharField(default="")
execution_command = peewee.CharField(default="")
auto_start = peewee.BooleanField(default=0)
auto_start_delay = peewee.IntegerField(default=10)
crash_detection = peewee.BooleanField(default=0)
stop_command = peewee.CharField(default="stop")
executable_update_url = peewee.CharField(default="")
server_ip = peewee.CharField(default="127.0.0.1")
server_port = peewee.IntegerField(default=25565)
logs_delete_after = peewee.IntegerField(default=0)
type = peewee.CharField(default="minecraft-java")
show_status = peewee.BooleanField(default=1)
created_by = peewee.IntegerField(default=-100)
shutdown_timeout = peewee.IntegerField(default=60)
ignored_exits = peewee.CharField(default="0")
class Meta:
table_name = "servers"
database = db
try:
logger.info("Migrating Data from Int to UUID (Type Change)")
Console.info("Migrating Data from Int to UUID (Type Change)")
# Changes on Server Table
migrator.alter_column_type(
Servers,
"server_id",
peewee.CharField(primary_key=True, default=str(uuid.uuid4())),
)
# Changes on Audit Log Table
migrator.alter_column_type(
AuditLog,
"server_id",
peewee.ForeignKeyField(
Servers,
backref="audit_server",
null=True,
field=peewee.CharField(primary_key=True, default=str(uuid.uuid4())),
),
)
# Changes on Webhook Table
migrator.alter_column_type(
Webhooks,
"server_id",
peewee.ForeignKeyField(
Servers,
backref="webhook_server",
null=True,
field=peewee.CharField(primary_key=True, default=str(uuid.uuid4())),
),
)
migrator.run()
logger.info("Migrating Data from Int to UUID (Type Change) : SUCCESS")
Console.info("Migrating Data from Int to UUID (Type Change) : SUCCESS")
except Exception as ex:
logger.error("Error while migrating Data from Int to UUID (Type Change)")
logger.error(ex)
Console.error("Error while migrating Data from Int to UUID (Type Change)")
Console.error(ex)
last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count())
last_migration.delete()
return
try:
logger.info("Migrating Data from Int to UUID (Foreign Keys)")
Console.info("Migrating Data from Int to UUID (Foreign Keys)")
# Changes on Audit Log Table
for audit_log in AuditLog.select():
old_server_id = audit_log.server_id_id
if old_server_id == "0" or old_server_id is None:
server_uuid = None
else:
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
AuditLog.update(server_id=server_uuid).where(
AuditLog.audit_id == audit_log.audit_id
).execute()
# Changes on Webhooks Log Table
for webhook in Webhooks.select():
old_server_id = webhook.server_id_id
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
Webhooks.update(server_id=server_uuid).where(
Webhooks.id == webhook.id
).execute()
# Changes on Schedules Log Table
for schedule in Schedules.select():
old_server_id = schedule.server_id_id
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
Schedules.update(server_id=server_uuid).where(
Schedules.schedule_id == schedule.schedule_id
).execute()
# Changes on Backups Log Table
for backup in Backups.select():
old_server_id = backup.server_id_id
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
Backups.update(server_id=server_uuid).where(
Backups.server_id == old_server_id
).execute()
# Changes on RoleServers Log Table
for role_servers in RoleServers.select():
old_server_id = role_servers.server_id_id
try:
server = Servers.get_by_id(old_server_id)
server_uuid = server.server_uuid
except:
server_uuid = old_server_id
RoleServers.update(server_id=server_uuid).where(
RoleServers.role_id == role_servers.id
and RoleServers.server_id == old_server_id
).execute()
logger.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS")
Console.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS")
except Exception as ex:
logger.error("Error while migrating Data from Int to UUID (Foreign Keys)")
logger.error(ex)
Console.error("Error while migrating Data from Int to UUID (Foreign Keys)")
Console.error(ex)
last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count())
last_migration.delete()
return
try:
logger.info("Migrating Data from Int to UUID (Primary Keys)")
Console.info("Migrating Data from Int to UUID (Primary Keys)")
# Migrating servers from the old id type to the new one
for server in Servers.select():
Servers.update(server_id=server.server_uuid).where(
Servers.server_id == server.server_id
).execute()
logger.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS")
Console.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS")
except Exception as ex:
logger.error("Error while migrating Data from Int to UUID (Primary Keys)")
logger.error(ex)
Console.error("Error while migrating Data from Int to UUID (Primary Keys)")
Console.error(ex)
last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count())
last_migration.delete()
return
# Changes on Server Table
logger.info("Migrating Data from Int to UUID (Removing UUID Field from Servers)")
Console.info("Migrating Data from Int to UUID (Removing UUID Field from Servers)")
migrator.drop_columns("servers", ["server_uuid"])
migrator.run()
logger.info(
"Migrating Data from Int to UUID (Removing UUID Field from Servers) : SUCCESS"
)
Console.info(
"Migrating Data from Int to UUID (Removing UUID Field from Servers) : SUCCESS"
)
return
def rollback(migrator: Migrator, database, **kwargs):
"""
Write your rollback migrations here.
"""
db = database
# Changes on Server Table
migrator.alter_column_type(
"servers",
"server_id",
peewee.AutoField(),
)
# Changes on Audit Log Table
migrator.alter_column_type(
AuditLog,
"server_id",
peewee.IntegerField(default=None, index=True),
)
# Changes on Webhook Table
migrator.alter_column_type(
Webhooks,
"server_id",
peewee.IntegerField(null=True),
)

View File

@ -215,7 +215,10 @@
"version": "Version" "version": "Version"
}, },
"login": { "login": {
"defaultPath": "Der eingegebene Text ist der Pfad zum Passwort, nicht das Passwort selbst. Das Standartpasswort kann unter diesen Pfad eingesehen werden.",
"disabled": "Account gesperrt. Für weitere Informationen den Serveradministrator kontaktieren",
"forgotPassword": "Passwort vergessen", "forgotPassword": "Passwort vergessen",
"incorrect": "Benutzername oder Passwort falsch",
"login": "Einloggen", "login": "Einloggen",
"password": "Passwort", "password": "Passwort",
"username": "Nutzername", "username": "Nutzername",

View File

@ -215,7 +215,10 @@
"version": "Version" "version": "Version"
}, },
"login": { "login": {
"defaultPath": "The password you entered is the default credential path, not the password. Please find the default password in that location.",
"disabled": "User account disabled. Please contact your system administrator for more info.",
"forgotPassword": "Forgot Password", "forgotPassword": "Forgot Password",
"incorrect": "Incorrect username or password",
"login": "Log In", "login": "Log In",
"password": "Password", "password": "Password",
"username": "Username", "username": "Username",

View File

@ -111,6 +111,7 @@
"starting": "Inicio-retrasado", "starting": "Inicio-retrasado",
"status": "Estado", "status": "Estado",
"stop": "Detener", "stop": "Detener",
"storage": "Almacenamiento",
"version": "Versión", "version": "Versión",
"welcome": "Bienvenido a Crafty Controller" "welcome": "Bienvenido a Crafty Controller"
}, },
@ -214,7 +215,10 @@
"version": "Versión" "version": "Versión"
}, },
"login": { "login": {
"defaultPath": "La contraseña introducida es la ruta default de las credenciales, no la contraseña. Busca la contraseña accediendo a la carpeta de la ruta",
"disabled": "Cuenta del usuario desactivada. Porfavor contacta al administrador para mas informacion.",
"forgotPassword": "Olvidé mi contraseña", "forgotPassword": "Olvidé mi contraseña",
"incorrect": "El nombre de usuario o contraseña es incorrecto",
"login": "Iniciar Sesión", "login": "Iniciar Sesión",
"password": "Contraseña", "password": "Contraseña",
"username": "Usuario", "username": "Usuario",
@ -326,6 +330,7 @@
"bePatientDeleteFiles": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty y eliminamos todos los archivos. Esta pantalla se cerrará en unos momentos.", "bePatientDeleteFiles": "Tenga paciencia mientras eliminamos su servidor del panel de Crafty y eliminamos todos los archivos. Esta pantalla se cerrará en unos momentos.",
"bePatientUpdate": "Tenga paciencia mientras actualizamos el servidor. El tiempo de descarga puede variar según la velocidad del Internet...<br /> Esta pantalla se actualizará en unos momentos.", "bePatientUpdate": "Tenga paciencia mientras actualizamos el servidor. El tiempo de descarga puede variar según la velocidad del Internet...<br /> Esta pantalla se actualizará en unos momentos.",
"cancel": "Cancelar", "cancel": "Cancelar",
"countPlayers": "Incluir el servidor en la cuenta total de jugadores",
"crashTime": "Tiempo de espera por crasheo", "crashTime": "Tiempo de espera por crasheo",
"crashTimeDesc": "¿Cuanto tiempo esperar para considerar el servidor como crasheado?", "crashTimeDesc": "¿Cuanto tiempo esperar para considerar el servidor como crasheado?",
"deleteFilesQuestion": "¿Eliminar archivos del servidor del host?", "deleteFilesQuestion": "¿Eliminar archivos del servidor del host?",
@ -510,6 +515,7 @@
"cpuUsage": "Uso de CPU", "cpuUsage": "Uso de CPU",
"description": "Descripción", "description": "Descripción",
"errorCalculatingUptime": "Error calculando tiempo de actividad", "errorCalculatingUptime": "Error calculando tiempo de actividad",
"loadingMotd": "Cargando MOTD",
"memUsage": "Uso de memoria", "memUsage": "Uso de memoria",
"offline": "Desconectado", "offline": "Desconectado",
"online": "En línea", "online": "En línea",
@ -577,6 +583,7 @@
"serverUpload": "Subir servidor comprimido", "serverUpload": "Subir servidor comprimido",
"serverVersion": "Versión del servidor", "serverVersion": "Versión del servidor",
"sizeInGB": "Tamaño en GB", "sizeInGB": "Tamaño en GB",
"unsupported": "Versiones de Minecraft inferiores a la 1.8 no estan soportadas por Crafty. Es posible instalarlas. Resultados pueden variar.",
"uploadButton": "Subir", "uploadButton": "Subir",
"uploadZip": "Subir archivo Zip para importar servidor", "uploadZip": "Subir archivo Zip para importar servidor",
"zipPath": "Ruta del servidor" "zipPath": "Ruta del servidor"
@ -591,6 +598,15 @@
"newServer": "Crear nuevo Servidor", "newServer": "Crear nuevo Servidor",
"servers": "Servidores" "servers": "Servidores"
}, },
"startup": {
"almost": "Terminando. Espera un momento...",
"internals": "Configurando e inicializando los componentes internos de Crafty",
"internet": "Verificando conexion a internet",
"server": "Inicializando ",
"serverInit": "Inicializando Servidores",
"starting": "Crafty esta iniciando...",
"tasks": "Iniciando el programador de tareas"
},
"userConfig": { "userConfig": {
"apiKey": "Claves API", "apiKey": "Claves API",
"auth": "¿Autorizado? ", "auth": "¿Autorizado? ",

View File

@ -215,7 +215,10 @@
"version": "Version" "version": "Version"
}, },
"login": { "login": {
"defaultPath": "Ce que tu as renseigné n'est pas le mot de passe, mais le chemin du fichier où le trouver.",
"disabled": "Ce compte est désactivé. Merci de contacter l'administrateur de ton serveur pour plus d'informations.",
"forgotPassword": "Mot de Passe Oublié", "forgotPassword": "Mot de Passe Oublié",
"incorrect": "Identifiant et/ou mot de passe incorrect.",
"login": "Connexion", "login": "Connexion",
"password": "Mot de Passe", "password": "Mot de Passe",
"username": "Nom d'Utilisateur", "username": "Nom d'Utilisateur",

View File

@ -215,7 +215,10 @@
"version": "גרסה" "version": "גרסה"
}, },
"login": { "login": {
"defaultPath": "הסיסמה שהזנת היא נתיב האישורים המוגדר כברירת מחדל, ולא הסיסמה עצמה. אנא מצא את הסיסמה המוגדרת כברירת מחדל במיקום זה.",
"disabled": "חשבון המשתמש מושבת. אנא פנה למנהל המערכת שלך לקבלת מידע נוסף.",
"forgotPassword": "שכחתי סיסמה", "forgotPassword": "שכחתי סיסמה",
"incorrect": "שם משתמש או סיסמה שגויים",
"login": "התחברות", "login": "התחברות",
"password": "סיסמה", "password": "סיסמה",
"username": "שם משתמש", "username": "שם משתמש",

View File

@ -215,7 +215,10 @@
"version": "VERSHUN" "version": "VERSHUN"
}, },
"login": { "login": {
"defaultPath": "Silleh hooman, dat iz da dafault secret path, not da passwurd. Plz find da default passwurd in dat spot.",
"disabled": "User account no play. Plz boop ur system hooman for moar infoz.",
"forgotPassword": "FORGWOTS YOUR SEEKRET", "forgotPassword": "FORGWOTS YOUR SEEKRET",
"incorrect": "U gotz wrong name or passwurd",
"login": "WOG INZ", "login": "WOG INZ",
"password": "SEEKRET", "password": "SEEKRET",
"username": "USERNAEM", "username": "USERNAEM",

View File

@ -216,7 +216,10 @@
"version": "Versija" "version": "Versija"
}, },
"login": { "login": {
"defaultPath": "Parole ko ievadijāt ir celš uz noklusētās paroles vietu, nevis noklusētā parole. Lūdzu apskatiet noklusēto paroli šajā vietā.",
"disabled": "Lietotāja konts atspējots. Lūdzu sazinieties ar savu sistēmas administratoru priekš papildus informācijas.",
"forgotPassword": "Aizmirsu Paroli", "forgotPassword": "Aizmirsu Paroli",
"incorrect": "Nepareizs lietotājvārds vai parole",
"login": "Ieiet", "login": "Ieiet",
"password": "Parole", "password": "Parole",
"username": "Lietotājvārds", "username": "Lietotājvārds",

View File

@ -215,7 +215,10 @@
"version": "Versie" "version": "Versie"
}, },
"login": { "login": {
"defaultPath": "Het ingevoerde wachtwoord is het pad naar de standaardreferentie, niet het wachtwoord zelf. Raadpleeg de standaardwachtwoord op de aangegeven locatie.",
"disabled": "Gebruikersaccount uitgeschakeld. Neem voor meer informatie contact op met uw systeembeheerder.",
"forgotPassword": "Wachtwoord vergeten", "forgotPassword": "Wachtwoord vergeten",
"incorrect": "Verkeerde gebruikersnaam of wachtwoord",
"login": "Log In", "login": "Log In",
"password": "Wachtwoord", "password": "Wachtwoord",
"username": "gebruikersnaam", "username": "gebruikersnaam",

View File

@ -215,7 +215,10 @@
"version": "Wersja" "version": "Wersja"
}, },
"login": { "login": {
"defaultPath": "Hasło które wprowadziłeś jest podstawową ścieżką w której przechowywane są dane logowania. Znajdź podstawowe hasło w tej lokalizacji.",
"disabled": "Konto tego użytkownika jest wyłączone. Skontaktuj się z administratorem by uzyskać więcej informacji.",
"forgotPassword": "Zapomniałem hasła", "forgotPassword": "Zapomniałem hasła",
"incorrect": "Niepoprawny login lub hasło/Niepoprawna nazwa użytkownika lub hasło",
"login": "Zaloguj się", "login": "Zaloguj się",
"password": "Hasło", "password": "Hasło",
"username": "Nazwa użytkownika", "username": "Nazwa użytkownika",

View File

@ -215,7 +215,10 @@
"version": "เวอร์ชั่น" "version": "เวอร์ชั่น"
}, },
"login": { "login": {
"defaultPath": "รหัสผ่านที่คุณกรอกคือเส้นทางข้อมูลเริ่มต้น ไม่ใช่รหัสผ่าน กรุณาค้นหารหัสผ่านเริ่มต้นในตำแหน่งนั้น",
"disabled": "บัญชีผู้ใช้ถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบของคุณสำหรับข้อมูลเพิ่มเติม",
"forgotPassword": "ลืมรหัสผ่าน", "forgotPassword": "ลืมรหัสผ่าน",
"incorrect": "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง",
"login": "เข้าสู่ระบบ", "login": "เข้าสู่ระบบ",
"password": "รหัสผ่าน", "password": "รหัสผ่าน",
"username": "ชื่อผู้ใช้", "username": "ชื่อผู้ใช้",

View File

@ -215,7 +215,10 @@
"version": "Sürüm" "version": "Sürüm"
}, },
"login": { "login": {
"defaultPath": "Girdiğiniz şifre varsayılan şifrenin konumudur, varsayılan şifre değil. Lütfen o konumda bulunan varsayılan şifreyi bulunuz.",
"disabled": "Bu kullanıcı hesabı engellenmiştir. Daha fazla bilgi için lütfen sunucu yöneticiniz ile konuşunuz.",
"forgotPassword": "Şifremi Unuttum", "forgotPassword": "Şifremi Unuttum",
"incorrect": "Kullanıcı adınız veya şifreniz yanlış.",
"login": "Oturum Aç", "login": "Oturum Aç",
"password": "Şifre", "password": "Şifre",
"username": "Kullanıcı Adı", "username": "Kullanıcı Adı",

View File

@ -85,7 +85,7 @@
"cpuCurFreq": "Швидкість CPU", "cpuCurFreq": "Швидкість CPU",
"cpuMaxFreq": "Максимальна швидкість CPU", "cpuMaxFreq": "Максимальна швидкість CPU",
"cpuUsage": "Використання CPU", "cpuUsage": "Використання CPU",
"crashed": "Аварійне завершення", "crashed": "Краш",
"dashboard": "Панель", "dashboard": "Панель",
"delay-explained": "Служба/агент нещодавно запущено та затримує запуск серверів minecraft", "delay-explained": "Служба/агент нещодавно запущено та затримує запуск серверів minecraft",
"host": "Хост", "host": "Хост",
@ -215,7 +215,10 @@
"version": "Версія" "version": "Версія"
}, },
"login": { "login": {
"defaultPath": "Пароль, який ви ввели, є шляхом до облікових даних за умовчанням, а не паролем. Будь ласка, знайдіть стандартний пароль у цьому місці.",
"disabled": "Користувача вимкнено. Зверніться до вашого системного адміністратора за допомогою.",
"forgotPassword": "Забули пароль", "forgotPassword": "Забули пароль",
"incorrect": "Неправильний логін або пароль",
"login": "Вхід", "login": "Вхід",
"password": "Пароль", "password": "Пароль",
"username": "Логін", "username": "Логін",
@ -351,7 +354,7 @@
"sendingRequest": "Надсилання вашого запиту...", "sendingRequest": "Надсилання вашого запиту...",
"serverAutoStart": "Сервер Авто-старт", "serverAutoStart": "Сервер Авто-старт",
"serverAutostartDelay": "Сервер Авто-старт затримка", "serverAutostartDelay": "Сервер Авто-старт затримка",
"serverAutostartDelayDesc": "Затримка Авто-старту сервера (Якщо увімкнуто раніше)", "serverAutostartDelayDesc": "Затримка Авто-старту сервера (Після запуску Crafty))",
"serverCrashDetection": "Детектор крашу сервера", "serverCrashDetection": "Детектор крашу сервера",
"serverExecutable": "Виконуваний файл Серверу", "serverExecutable": "Виконуваний файл Серверу",
"serverExecutableDesc": "Це виконуваний файл для запуску сервера", "serverExecutableDesc": "Це виконуваний файл для запуску сервера",
@ -369,7 +372,7 @@
"serverPortDesc": "Цей порт призначений для статистики Crafty", "serverPortDesc": "Цей порт призначений для статистики Crafty",
"serverStopCommand": "Команда зупинки сервера", "serverStopCommand": "Команда зупинки сервера",
"serverStopCommandDesc": "Команда яка буде надсилатись, щоб зупинити сервер", "serverStopCommandDesc": "Команда яка буде надсилатись, щоб зупинити сервер",
"showStatus": "Показувати на публічній сторінці статус", "showStatus": "Показувати статус на публічній сторінці",
"shutdownTimeout": "Час відклику зупинки", "shutdownTimeout": "Час відклику зупинки",
"statsHint1": "Цей порт на якому працює сервер. Це потрібно лиш для того щоб Crafty міг виводити статистику для цього сервера.", "statsHint1": "Цей порт на якому працює сервер. Це потрібно лиш для того щоб Crafty міг виводити статистику для цього сервера.",
"statsHint2": "Це не змінює порт вашого сервера. Ви мусите власноруч змінити налаштування в server.properties або іншому конфігураційному файлі.", "statsHint2": "Це не змінює порт вашого сервера. Ви мусите власноруч змінити налаштування в server.properties або іншому конфігураційному файлі.",
@ -406,7 +409,7 @@
"logs": "Логи", "logs": "Логи",
"metrics": "Графік", "metrics": "Графік",
"playerControls": "Керування Гравцями", "playerControls": "Керування Гравцями",
"reset": "Повернутись нагору", "reset": "Вниз",
"schedule": "Розклад", "schedule": "Розклад",
"serverDetails": "Деталі сервера", "serverDetails": "Деталі сервера",
"terminal": "Термінал" "terminal": "Термінал"

View File

@ -215,7 +215,10 @@
"version": "版本" "version": "版本"
}, },
"login": { "login": {
"defaultPath": "您输入的密码是默认凭据的路径,不是其中的密码。请在此路径中找到默认密码。",
"disabled": "用户账号已禁用。请联系您的系统管理员以了解更多信息。",
"forgotPassword": "忘记密码", "forgotPassword": "忘记密码",
"incorrect": "用户名或密码错误",
"login": "登录", "login": "登录",
"password": "密码", "password": "密码",
"username": "用户名", "username": "用户名",

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.4
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

View File

@ -3,7 +3,7 @@ sonar.organization=crafty-controller
# This is the name and version displayed in the SonarCloud UI. # This is the name and version displayed in the SonarCloud UI.
sonar.projectName=Crafty 4 sonar.projectName=Crafty 4
sonar.projectVersion=4.2.3 sonar.projectVersion=4.3.0
sonar.python.version=3.9, 3.10, 3.11 sonar.python.version=3.9, 3.10, 3.11
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/** sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**