Merge branch 'refactor/server_ids' of gitlab.com:crafty-controller/crafty-4 into refactor/server_ids

This commit is contained in:
Silversthorn 2024-03-03 10:00:25 +01:00
commit bdefa69e32
29 changed files with 286 additions and 144 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 # Changelog
## --- [4.2.4] - 2023/TBD ## --- [4.3.0] - 2023/TBD
### New features ### New features
TBD TBD
### Refactor ### Refactor
@ -9,10 +9,15 @@ TBD
- Make sure default.json is read from correct location ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/714)) - 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)) - 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 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))
- 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 ### Tweaks
- Bump pyOpenSSL & cryptography for CVE-2024-0727, CVE-2023-50782 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/716)) - 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 ### Lang
TBD - 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> <br><br>
## --- [4.2.3] - 2023/02/02 ## --- [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 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 > Python based Control Panel for your Minecraft Server
## What is Crafty Controller? ## What is Crafty Controller?

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

@ -82,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

@ -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",

View File

@ -18,13 +18,20 @@ 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 > 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

@ -696,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:
@ -729,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 = ""
@ -777,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

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

@ -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

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

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

@ -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,7 +4,7 @@ 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==42.0.2 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

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