Merge branch 'dev' into lang/pirate

This commit is contained in:
Zedifus 2024-03-07 23:52:32 +00:00
commit 06775b796b
54 changed files with 660 additions and 250 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,5 +1,5 @@
# Changelog
## --- [4.2.4] - 2023/TBD
## --- [4.3.0] - 2023/TBD
### New features
TBD
### Refactor
@ -9,10 +9,16 @@ TBD
- 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))
### 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
- New `en_PT`
- 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

View File

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

View File

@ -80,8 +80,8 @@ class ServersController(metaclass=Singleton):
PeeweeException: If the server already exists
"""
return HelperServers.create_server(
name,
server_uuid,
name,
server_dir,
backup_path,
server_command,
@ -161,9 +161,9 @@ class ServersController(metaclass=Singleton):
# 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:
if int(server["server_id"]) == int(server_id):
if server["server_id"] == server_id:
return server["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -81,6 +81,7 @@ class Helpers:
self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"]
self.crafty_starting = False
self.minimum_password_length = 8
@staticmethod
def auto_installer_fix(ex):
@ -117,7 +118,7 @@ class Helpers:
Get latest bedrock executable url \n\n
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 = {
"Accept-Encoding": "identity",
"Accept-Language": "en",

View File

@ -239,7 +239,7 @@ class Controller:
try:
os.mkdir(final_path)
except FileExistsError:
final_path += "_" + server["server_uuid"]
final_path += "_" + server["server_id"]
os.mkdir(final_path)
try:
FileHelpers.copy_file(
@ -632,11 +632,11 @@ class Controller:
# and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not exec_user["superuser"]:
new_server_uuid = self.servers.get_server_data_by_id(new_server_id).get(
"server_uuid"
new_server_id = self.servers.get_server_data_by_id(new_server_id).get(
"server_id"
)
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"],
)
self.server_perms.add_role_server(new_server_id, role_id, "11111111")
@ -647,7 +647,7 @@ class Controller:
role_id = role
self.server_perms.add_role_server(new_server_id, role_id, "11111111")
return new_server_id, server_fs_uuid
return new_server_id
@staticmethod
def verify_jar_server(server_path: str, server_jar: str):
@ -1095,7 +1095,7 @@ class Controller:
for server in servers:
server_path = server.get("path")
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):
WebSocketManager().broadcast_page(

View File

@ -18,13 +18,22 @@ class DatabaseBuilder:
logger.info("Fresh Install Detected - Creating Default Settings")
Console.info("Fresh Install Detected - Creating Default Settings")
default_data = self.helper.find_default_password()
if password not in default_data:
if "password" not in default_data:
Console.help(
"No default password found. Using password created "
"by Crafty. Find it in app/config/default-creds.txt"
)
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(
username=username,

View File

@ -200,6 +200,21 @@ class Migrator(object):
)
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
def rename_table(self, model: peewee.Model, new_name: str) -> peewee.Model:
"""

View File

@ -696,6 +696,10 @@ class ServerInstance:
version_param = version[0][0].split(".")
version_major = int(version_param[0])
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
if version_major <= 1 and version_minor < 17:
@ -729,8 +733,8 @@ class ServerInstance:
server_obj.execution_command = execution_command
Console.debug(SUCCESSMSG)
elif version_major <= 1 and version_minor < 20:
# NEW VERSION >= 1.17 and <= 1.20
elif version_major <= 1 and version_minor <= 20 and version_sub < 3:
# NEW VERSION >= 1.17 and <= 1.20.2
# (no jar file in server dir, only run.bat and run.sh)
run_file_path = ""
@ -777,7 +781,7 @@ class ServerInstance:
server_obj.execution_command = execution_command
Console.debug(SUCCESSMSG)
else:
# NEW VERSION >= 1.20
# NEW VERSION >= 1.20.3
# (executable jar is back in server dir)
# Retrieving the executable jar filename

View File

@ -174,7 +174,7 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID")
return None
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
return server_id
# Does this server exist?
@ -556,7 +556,7 @@ class PanelHandler(BaseHandler):
"server_id": {
"server_id": server_id,
"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"],
"log_path": server_temp_obj["log_path"],
"executable": server_temp_obj["executable"],

View File

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

View File

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

View File

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

View File

@ -723,9 +723,7 @@ class ApiServersIndexHandler(BaseApiHandler):
405, {"status": "error", "error": "DATA CONSTRAINT FAILED"}
)
return
new_server_id, new_server_uuid = self.controller.create_api_server(
data, user["user_id"]
)
new_server_id = self.controller.create_api_server(data, user["user_id"])
self.controller.servers.stats.record_stats()
@ -734,7 +732,7 @@ class ApiServersIndexHandler(BaseApiHandler):
(
f"created server {data['name']}"
f" (ID: {new_server_id})"
f" (UUID: {new_server_uuid})"
f" (UUID: {new_server_id})"
),
server_id=new_server_id,
source_ip=self.get_remote_ip(),
@ -746,7 +744,7 @@ class ApiServersIndexHandler(BaseApiHandler):
"status": "ok",
"data": {
"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.servers import Servers
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler
@ -68,10 +67,20 @@ class ApiServersServerActionHandler(BaseApiHandler):
name_counter += 1
new_server_name = server_data.get("server_name") + f" (Copy {name_counter})"
new_server_uuid = Helpers.create_uuid()
while os.path.exists(os.path.join(self.helper.servers_dir, new_server_uuid)):
new_server_uuid = Helpers.create_uuid()
new_server_path = os.path.join(self.helper.servers_dir, new_server_uuid)
new_server_id = self.controller.servers.create_server(
new_server_name,
None,
"",
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(
user_id,
@ -89,19 +98,12 @@ class ApiServersServerActionHandler(BaseApiHandler):
self.helper.get_os_understandable_path(server_data.get("log_path"))
)
new_server_id = self.controller.servers.create_server(
new_server_name,
new_server_uuid,
new_server_path,
"",
new_server_command,
server_data.get("executable"),
new_server_log_file,
server_data.get("stop_command"),
server_data.get("type"),
user_id,
server_data.get("server_port"),
)
server: Servers = self.controller.servers.get_server_obj(new_server_id)
server.path = new_server_path
server.log_path = new_server_log_file
server.execution_command = new_server_command
self.controller.servers.update_server(server)
for role in self.controller.server_perms.get_server_roles(server_id):
mask = self.controller.server_perms.get_permissions_mask(
role.role_id, server_id

View File

@ -145,7 +145,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server)
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
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)
failed = False
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)
failed = True

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4>
</div>
</div>
@ -454,7 +454,7 @@
});
}
});
try {
if ($('#backup_path').val() == '') {
console.log('true')

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4>
</div>
</div>
@ -235,7 +235,7 @@
if (key != "start_time" && key != "cron_string" && key != "interval_type") {
if (typeof value == "boolean") {
return value
}
}
console.log(key)
if (key === "interval" && value === ""){
return 0;
@ -298,7 +298,7 @@
title: responseData.status,
message: responseData.error
});
}
}
});
$("#schedule_form").on("submit", async function (e) {
@ -332,7 +332,7 @@
method: 'PATCH',
headers: {
'X-XSRFToken': token,
"Content-Type": "application/json",
"Content-Type": "application/json",
},
body: formDataJsonString,
});
@ -345,7 +345,7 @@
title: responseData.error,
message: responseData.error_data
});
}
}
});
});

View File

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

View File

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

View File

@ -17,7 +17,7 @@
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
<small>UUID: {{ data['server_stats']['server_id']['server_id'] }}</small>
</h4>
</div>
</div>
@ -161,7 +161,7 @@
if (key != "start_time" && key != "cron_string" && key != "interval_type") {
if (typeof value == "boolean") {
return value
}
}
console.log(key)
if (key === "interval" && value === ""){
return 0;
@ -221,7 +221,7 @@
title: responseData.status,
message: responseData.error
});
}
}
});
$("#webhook_form").on("submit", async function (e) {
@ -249,7 +249,7 @@
method: 'PATCH',
headers: {
'X-XSRFToken': token,
"Content-Type": "application/json",
"Content-Type": "application/json",
},
body: formDataJsonString,
});
@ -262,15 +262,15 @@
title: responseData.status,
message: responseData.error
});
}
}
});
});
function hexToDiscordInt(hexColor) {
// Remove the hash at the start if it's there
const sanitizedHex = hexColor.startsWith('#') ? hexColor.slice(1) : hexColor;
// Convert the hex to an integer
return parseInt(sanitizedHex, 16);
}

View File

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

View File

@ -77,55 +77,49 @@
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
</style>
{% if data['query'] %}
<form action="/login?{{ data['query'] }}" method="post">
{% else %}
<form action="/login" method="post">
{% end %}
{% raw xsrf_form_html() %}
<div class="form-group">
<label class="label">{{ translate('login', 'username', data['lang']) }}</label>
<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>
<form id="login-form" data-query="{{ data.get('query', None) }}">
{% raw xsrf_form_html() %}
<div class="form-group">
<label class="label">{{ translate('login', 'username', data['lang']) }}</label>
<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 class="form-group">
<label class="label">{{ translate('login', 'password', data['lang']) }}</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password"
required="true">
</div>
</div>
<div class="form-group">
<label class="label">{{ translate('login', 'password', data['lang']) }}</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password"
required="true">
</div>
<div class="form-group">
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login',
data['lang']) }}</button>
</div>
{% if error_msg is not None %}
<fieldset style="color: red; text-align: center;">
<span>{{error_msg}}</span>
</fieldset>
{% end %}
<div class="form-group d-flex justify-content-between">
<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 class="form-group">
<button class="login-input btn btn-primary submit-btn btn-block">{{ translate('login', 'login',
data['lang']) }}</button>
</div>
<fieldset id="error-field" style="color: red; text-align: center;">
</fieldset>
<div class="form-group d-flex justify-content-between">
<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 class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
{{data['version'] }}</a> </span>
</div>
<div class="text-block text-center my-3">
<a href="/status" class="text-small forgot-password">{{ translate('login', 'viewStatus',
data['lang']) }}</a>
</div>
</form>
<div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control
{{data['version'] }}</a> </span>
</div>
<div class="text-block text-center my-3">
<a href="/status" class="text-small forgot-password">{{ translate('login', 'viewStatus',
data['lang']) }}</a>
</div>
</form>
</div>
@ -155,13 +149,13 @@
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
//Register Service worker for mobile app
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) {
console.log('Service Worker Registered');
});
}
});
async function resetPass(){
async function resetPass() {
let res = await fetch(`/api/v2/crafty/resetPass/`, {
method: 'GET',
});
@ -170,7 +164,38 @@
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>
<style>
.modal-content {

View File

@ -556,7 +556,7 @@
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
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";
}
else {

View File

@ -881,7 +881,7 @@
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
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").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"
},
"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",
"incorrect": "Benutzername oder Passwort falsch",
"login": "Einloggen",
"password": "Passwort",
"username": "Nutzername",

View File

@ -215,7 +215,10 @@
"version": "Version"
},
"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",
"incorrect": "Incorrect username or password",
"login": "Log In",
"password": "Password",
"username": "Username",

View File

@ -111,6 +111,7 @@
"starting": "Inicio-retrasado",
"status": "Estado",
"stop": "Detener",
"storage": "Almacenamiento",
"version": "Versión",
"welcome": "Bienvenido a Crafty Controller"
},
@ -214,7 +215,10 @@
"version": "Versión"
},
"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",
"incorrect": "El nombre de usuario o contraseña es incorrecto",
"login": "Iniciar Sesión",
"password": "Contraseña",
"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.",
"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",
"countPlayers": "Incluir el servidor en la cuenta total de jugadores",
"crashTime": "Tiempo de espera por crasheo",
"crashTimeDesc": "¿Cuanto tiempo esperar para considerar el servidor como crasheado?",
"deleteFilesQuestion": "¿Eliminar archivos del servidor del host?",
@ -510,6 +515,7 @@
"cpuUsage": "Uso de CPU",
"description": "Descripción",
"errorCalculatingUptime": "Error calculando tiempo de actividad",
"loadingMotd": "Cargando MOTD",
"memUsage": "Uso de memoria",
"offline": "Desconectado",
"online": "En línea",
@ -577,6 +583,7 @@
"serverUpload": "Subir servidor comprimido",
"serverVersion": "Versión del servidor",
"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",
"uploadZip": "Subir archivo Zip para importar servidor",
"zipPath": "Ruta del servidor"
@ -591,6 +598,15 @@
"newServer": "Crear nuevo Servidor",
"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": {
"apiKey": "Claves API",
"auth": "¿Autorizado? ",

View File

@ -215,7 +215,10 @@
"version": "Version"
},
"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é",
"incorrect": "Identifiant et/ou mot de passe incorrect.",
"login": "Connexion",
"password": "Mot de Passe",
"username": "Nom d'Utilisateur",

View File

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

View File

@ -215,7 +215,10 @@
"version": "VERSHUN"
},
"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",
"incorrect": "U gotz wrong name or passwurd",
"login": "WOG INZ",
"password": "SEEKRET",
"username": "USERNAEM",

View File

@ -216,7 +216,10 @@
"version": "Versija"
},
"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",
"incorrect": "Nepareizs lietotājvārds vai parole",
"login": "Ieiet",
"password": "Parole",
"username": "Lietotājvārds",

View File

@ -215,7 +215,10 @@
"version": "Versie"
},
"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",
"incorrect": "Verkeerde gebruikersnaam of wachtwoord",
"login": "Log In",
"password": "Wachtwoord",
"username": "gebruikersnaam",

View File

@ -215,7 +215,10 @@
"version": "Wersja"
},
"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",
"incorrect": "Niepoprawny login lub hasło/Niepoprawna nazwa użytkownika lub hasło",
"login": "Zaloguj się",
"password": "Hasło",
"username": "Nazwa użytkownika",

View File

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

View File

@ -215,7 +215,10 @@
"version": "Sürüm"
},
"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",
"incorrect": "Kullanıcı adınız veya şifreniz yanlış.",
"login": "Oturum Aç",
"password": "Şifre",
"username": "Kullanıcı Adı",

View File

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

View File

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

View File

@ -4,7 +4,7 @@ argon2-cffi==23.1.0
cached_property==1.5.2
colorama==0.4.6
croniter==1.4.1
cryptography==42.0.2
cryptography==42.0.4
libgravatar==1.0.4
nh3==0.2.14
packaging==23.2

View File

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