Merge branch 'dev' into refactor/server_ids
1
.gitignore
vendored
@ -37,3 +37,4 @@ app/config/
|
||||
docker/*
|
||||
!docker/docker-compose.yml
|
||||
lang_sort_log.txt
|
||||
lang_sort.txt
|
||||
|
72
.gitlab/scripts/sort.py
Normal file
@ -0,0 +1,72 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def get_missing_keys_and_values(obj1, obj2, path=None):
|
||||
if path is None:
|
||||
path = []
|
||||
|
||||
missing_keys_and_values = {}
|
||||
|
||||
if isinstance(obj1, dict) and isinstance(obj2, dict):
|
||||
for key in obj1:
|
||||
if key not in obj2:
|
||||
missing_keys_and_values[key] = obj1[key]
|
||||
elif isinstance(obj1[key], (dict, list)) and isinstance(
|
||||
obj2[key], (dict, list)
|
||||
):
|
||||
sub_missing = get_missing_keys_and_values(
|
||||
obj1[key], obj2[key], path + [key]
|
||||
)
|
||||
if sub_missing:
|
||||
missing_keys_and_values[key] = sub_missing
|
||||
|
||||
return missing_keys_and_values
|
||||
|
||||
|
||||
def main():
|
||||
project_dir = os.getcwd()
|
||||
os.chdir("../../app/translations") # Change the working directory
|
||||
dir_path = os.getcwd() # Get the current working directory
|
||||
|
||||
en_en_path = os.path.join(dir_path, "en_EN.json")
|
||||
|
||||
if not os.path.isfile(en_en_path):
|
||||
print(
|
||||
f"The file en_EN.json does not exist in {dir_path}. Ensure you have the right directory, Exiting."
|
||||
)
|
||||
return
|
||||
|
||||
result = {} # JSON object to store missing keys and values
|
||||
|
||||
for root, _, files in os.walk(dir_path):
|
||||
for file in files:
|
||||
if (
|
||||
"_incomplete" not in file
|
||||
and file != "en_EN.json"
|
||||
and file.endswith(".json")
|
||||
):
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as current_file:
|
||||
current_data = json.load(current_file)
|
||||
|
||||
with open(en_en_path, "r", encoding="utf-8") as en_en_file:
|
||||
en_en_data = json.load(en_en_file)
|
||||
|
||||
missing_keys_and_values = get_missing_keys_and_values(
|
||||
en_en_data, current_data
|
||||
)
|
||||
if missing_keys_and_values:
|
||||
result[file] = missing_keys_and_values
|
||||
|
||||
# Write the JSON object to lang_sort.txt
|
||||
with open(
|
||||
os.path.join(project_dir, "lang_sort.txt"),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as output_file:
|
||||
json.dump(result, output_file, indent=4)
|
||||
|
||||
|
||||
main()
|
49
CHANGELOG.md
@ -1,9 +1,53 @@
|
||||
# Changelog
|
||||
## --- [4.2.1] - 2023/TBD
|
||||
## --- [4.2.3] - 2023/TBD
|
||||
### New features
|
||||
TBD
|
||||
### Bug fixes
|
||||
TBD
|
||||
### Tweaks
|
||||
TBD
|
||||
### Lang
|
||||
TBD
|
||||
<br><br>
|
||||
|
||||
## --- [4.2.2] - 2023/12/13
|
||||
### New features
|
||||
- Loading Screen for Crafty during startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/668))
|
||||
### Refactor
|
||||
- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670))
|
||||
- Tidy up main.py to be more comprehensive ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/668))
|
||||
- Force random password on first run. Stop using common default password ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/672) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/673))
|
||||
### Bug fixes
|
||||
- Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664))
|
||||
- Bump cryptography for CVE-2023-49083 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/680))
|
||||
- Fix bug where su cannot edit general user password ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/676))
|
||||
- Fix bug where no file error on import root dir ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/677))
|
||||
- Fix Unban button failing to pardon users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/671))
|
||||
- Fix stack in API error handling ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/674))
|
||||
- Fix bug where you cannot select "do not monitor mounts" from `config.json` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/678))
|
||||
- Fix support log 'x' button still downloading logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/679))
|
||||
- Fix bug where servers are created without bu dir ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/682))
|
||||
### Tweaks
|
||||
- Homogenize Panel logos/branding ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/666))
|
||||
- Retain previous tab when revisiting server details page (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
|
||||
- Add server name tag in panel header (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
|
||||
- Setup logging for panel authentication attempts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
|
||||
- Update minimum password length from 6 to 8, and unrestrict maximum password length ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
|
||||
- Give better feedback when backup delete fails ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/681))
|
||||
- Add user queue debug logging ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/683))
|
||||
### Lang
|
||||
- Update `de_DE, en_EN, fr_FR, lol_EN, lv_LV, nl_BE, pl_PL, zh_CN` translations for `4.2.2` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/684))
|
||||
- Mark `es_ES` as incomplete ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/684))
|
||||
- Mark `he_IL` as active ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/684))
|
||||
- pl_PL Minor fixes ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/675))
|
||||
<br><br>
|
||||
|
||||
## --- [4.2.1] - 2023/11/01
|
||||
### Bug fixes
|
||||
- Fix logic issue with `get_files` API permissions check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/654))
|
||||
- Fix notifications not showing up/being reset #298 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/660))
|
||||
- Fix users not being able to be deleted since the prompt fails to display ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/661))
|
||||
- Fix duplicate function naming on dashboard ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/662))
|
||||
### Tweaks
|
||||
- Auto refresh Crafty Announcements on 30m interval ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/653))
|
||||
- Improve Crafty toggle buttons and Webhooks page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/656))
|
||||
@ -12,6 +56,9 @@ TBD
|
||||
- Update `es_ES` lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/655))
|
||||
- Clean up wording in `pl_PL` lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/656))
|
||||
- Add `de_DE`, `es_ES` `fr_FR`, `lol_EN`, `lv_LV`, `nl_BE` `pl_PL` & `zh_CN` translations for !656 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/656))
|
||||
### Docs
|
||||
- [(New) Server Webhook Documentation](https://docs.craftycontrol.com/pages/user-guide/webhooks/)
|
||||
- [(Edit) Image Context in Windows Service - Install steps, with slight wording improvement](https://docs.craftycontrol.com/pages/getting-started/installation/windows/#install-steps)
|
||||
<br><br>
|
||||
|
||||
## --- [4.2.0] - 2023/10/18
|
||||
|
@ -1,5 +1,5 @@
|
||||
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
|
||||
# Crafty Controller 4.2.1
|
||||
# Crafty Controller 4.2.3
|
||||
> Python based Control Panel for your Minecraft Server
|
||||
|
||||
## What is Crafty Controller?
|
||||
|
@ -22,6 +22,7 @@ from app.classes.models.server_permissions import (
|
||||
PermissionsServers,
|
||||
EnumPermissionsServer,
|
||||
)
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -36,6 +37,8 @@ class ServersController(metaclass=Singleton):
|
||||
self.management_helper = management_helper
|
||||
self.servers_list = []
|
||||
self.stats = Stats(self.helper, self)
|
||||
self.web_sock = WebSocketManager()
|
||||
self.server_subpage = {}
|
||||
|
||||
# **********************************************************************************
|
||||
# Generic Servers Methods
|
||||
@ -169,8 +172,15 @@ class ServersController(metaclass=Singleton):
|
||||
def init_all_servers(self):
|
||||
servers = self.get_all_defined_servers()
|
||||
self.failed_servers = []
|
||||
|
||||
for server in servers:
|
||||
self.web_sock.broadcast_to_admins(
|
||||
"update",
|
||||
{"section": "server", "server": server["server_name"]},
|
||||
)
|
||||
self.web_sock.broadcast_to_non_admins(
|
||||
"update",
|
||||
{"section": "init"},
|
||||
)
|
||||
server_id = server.get("server_id")
|
||||
|
||||
# if we have already initialized this server, let's skip it.
|
||||
|
@ -45,8 +45,7 @@ class UsersController:
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 6,
|
||||
"minLength": 8,
|
||||
"examples": ["crafty"],
|
||||
"title": "Password",
|
||||
},
|
||||
|
@ -80,6 +80,7 @@ class Helpers:
|
||||
self.translation = Translation(self)
|
||||
self.update_available = False
|
||||
self.ignored_names = ["crafty_managed.txt", "db_stats"]
|
||||
self.crafty_starting = False
|
||||
|
||||
@staticmethod
|
||||
def auto_installer_fix(ex):
|
||||
@ -361,6 +362,42 @@ class Helpers:
|
||||
|
||||
return result_of_check == 0
|
||||
|
||||
def create_pass(self):
|
||||
# Maximum length of password needed
|
||||
max_len = 64
|
||||
|
||||
# Declare string of the character that we need in our password
|
||||
digits = string.digits
|
||||
locase = string.ascii_lowercase
|
||||
upcase = string.ascii_uppercase
|
||||
symbols = "!@#$%^&*" # Reducing to avoid issues with ([]{}<>,'`) etc
|
||||
|
||||
# Combine all the character strings above to form one string
|
||||
combo = digits + upcase + locase + symbols
|
||||
|
||||
# Randomly select at least one character from each character set above
|
||||
rand_digit = secrets.choice(digits)
|
||||
rand_upper = secrets.choice(upcase)
|
||||
rand_lower = secrets.choice(locase)
|
||||
rand_symbol = secrets.choice(symbols)
|
||||
|
||||
# Combine the character randomly selected above
|
||||
temp_pass = rand_digit + rand_upper + rand_lower + rand_symbol
|
||||
|
||||
# Fill the rest of the password length by selecting randomly char list
|
||||
for _ in range(max_len - 4):
|
||||
temp_pass += secrets.choice(combo)
|
||||
|
||||
# Shuffle the temporary password to prevent predictable patterns
|
||||
temp_pass_list = list(temp_pass)
|
||||
secrets.SystemRandom().shuffle(temp_pass_list)
|
||||
|
||||
# Form the password by concatenating the characters
|
||||
password = "".join(temp_pass_list)
|
||||
|
||||
# Return completed password
|
||||
return password
|
||||
|
||||
@staticmethod
|
||||
def cmdparse(cmd_in):
|
||||
# Parse a string into arguments
|
||||
@ -578,16 +615,19 @@ class Helpers:
|
||||
return version_data
|
||||
|
||||
def get_announcements(self):
|
||||
data = []
|
||||
try:
|
||||
data = []
|
||||
response = requests.get("https://craftycontrol.com/notify", timeout=2)
|
||||
data = json.loads(response.content)
|
||||
if self.update_available:
|
||||
data.append(self.update_available)
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch notifications with error: {e}")
|
||||
|
||||
if self.update_available:
|
||||
data.append(self.update_available)
|
||||
return data
|
||||
if self.update_available:
|
||||
data = [self.update_available]
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_version_string(self):
|
||||
version_data = self.get_version()
|
||||
|
@ -78,6 +78,37 @@ class Controller:
|
||||
self.first_login = False
|
||||
self.cached_login = self.management.get_login_image()
|
||||
self.support_scheduler.start()
|
||||
try:
|
||||
with open(
|
||||
os.path.join(os.path.curdir, "logs", "auth_tracker.log"),
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
self.auth_tracker = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
self.auth_tracker = {}
|
||||
|
||||
def log_attempt(self, remote_ip, username):
|
||||
remote = self.auth_tracker.get(str(remote_ip), None)
|
||||
if remote:
|
||||
remote["names"].append(username)
|
||||
remote["attempts"] += 1
|
||||
remote["times"].append(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||
self.auth_tracker[str(remote_ip)] = remote
|
||||
else:
|
||||
self.auth_tracker[str(remote_ip)] = {
|
||||
"names": [username],
|
||||
"attempts": 1,
|
||||
"times": [datetime.now().strftime("%d/%m/%Y %H:%M:%S")],
|
||||
}
|
||||
|
||||
def write_auth_tracker(self):
|
||||
with open(
|
||||
os.path.join(os.path.curdir, "logs", "auth_tracker.log"),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
json.dump(self.auth_tracker, f, indent=4)
|
||||
|
||||
@staticmethod
|
||||
def check_system_user():
|
||||
@ -498,6 +529,10 @@ class Controller:
|
||||
server_host=monitoring_host,
|
||||
server_type=monitoring_type,
|
||||
)
|
||||
self.management.set_backup_config(
|
||||
new_server_id,
|
||||
backup_path,
|
||||
)
|
||||
if data["create_type"] == "minecraft_java":
|
||||
if root_create_data["create_type"] == "download_jar":
|
||||
# modded update urls from server jars will only update the installer
|
||||
|
@ -14,13 +14,17 @@ class DatabaseBuilder:
|
||||
self.management_helper = management_helper
|
||||
self.users_helper = users_helper
|
||||
|
||||
def default_settings(self):
|
||||
def default_settings(self, password="crafty"):
|
||||
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:
|
||||
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", "crafty")
|
||||
password = default_data.get("password", password)
|
||||
|
||||
self.users_helper.add_user(
|
||||
username=username,
|
||||
|
@ -23,6 +23,7 @@ from app.classes.web.tornado_handler import Webserver
|
||||
from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
logger = logging.getLogger("apscheduler")
|
||||
command_log = logging.getLogger("cmd_queue")
|
||||
scheduler_intervals = {
|
||||
"seconds",
|
||||
"minutes",
|
||||
@ -94,7 +95,15 @@ class TasksManager:
|
||||
def command_watcher(self):
|
||||
while True:
|
||||
# select any commands waiting to be processed
|
||||
command_log.debug(
|
||||
"Queue currently has "
|
||||
f"{self.controller.management.command_queue.qsize()} queued commands."
|
||||
)
|
||||
if not self.controller.management.command_queue.empty():
|
||||
command_log.info(
|
||||
"Current queued commands: "
|
||||
f"{list(self.controller.management.command_queue.queue)}"
|
||||
)
|
||||
cmd = self.controller.management.command_queue.get()
|
||||
try:
|
||||
svr = self.controller.servers.get_server_instance_by_id(
|
||||
@ -201,6 +210,13 @@ class TasksManager:
|
||||
id="update_watcher",
|
||||
start_date=datetime.datetime.now(),
|
||||
)
|
||||
self.scheduler.add_job(
|
||||
self.controller.write_auth_tracker,
|
||||
"interval",
|
||||
minutes=5,
|
||||
id="auth_tracker_write",
|
||||
start_date=datetime.datetime.now(),
|
||||
)
|
||||
# self.scheduler.add_job(
|
||||
# self.scheduler.print_jobs, "interval", seconds=10, id="-1"
|
||||
# )
|
||||
|
@ -37,7 +37,15 @@ class WebSocketManager(metaclass=Singleton):
|
||||
|
||||
def broadcast_to_admins(self, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if client.get_user_id in HelperUsers.get_super_user_list():
|
||||
if str(client.get_user_id()) in str(HelperUsers.get_super_user_list()):
|
||||
return True
|
||||
return False
|
||||
|
||||
self.broadcast_with_fn(filter_fn, event_type, data)
|
||||
|
||||
def broadcast_to_non_admins(self, event_type: str, data):
|
||||
def filter_fn(client):
|
||||
if str(client.get_user_id()) not in str(HelperUsers.get_super_user_list()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -1,446 +0,0 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import re
|
||||
|
||||
from app.classes.controllers.crafty_perms_controller import EnumPermissionsCrafty
|
||||
from app.classes.controllers.server_perms_controller import EnumPermissionsServer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
from app.classes.models.management import DatabaseShortcuts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE)
|
||||
|
||||
|
||||
class ApiHandler(BaseHandler):
|
||||
def return_response(self, status: int, data: dict):
|
||||
# Define a standardized response
|
||||
self.set_status(status)
|
||||
self.write(data)
|
||||
|
||||
def check_xsrf_cookie(self):
|
||||
# Disable CSRF protection on API routes
|
||||
pass
|
||||
|
||||
def access_denied(self, user, reason=""):
|
||||
if reason:
|
||||
reason = " because " + reason
|
||||
logger.info(
|
||||
"User %s from IP %s was denied access to the API route "
|
||||
+ self.request.path
|
||||
+ reason,
|
||||
user,
|
||||
self.get_remote_ip(),
|
||||
)
|
||||
self.finish(
|
||||
self.return_response(
|
||||
403,
|
||||
{
|
||||
"error": "ACCESS_DENIED",
|
||||
"info": "You were denied access to the requested resource",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def authenticate_user(self) -> bool:
|
||||
self.permissions = {
|
||||
"Commands": EnumPermissionsServer.COMMANDS,
|
||||
"Terminal": EnumPermissionsServer.TERMINAL,
|
||||
"Logs": EnumPermissionsServer.LOGS,
|
||||
"Schedule": EnumPermissionsServer.SCHEDULE,
|
||||
"Backup": EnumPermissionsServer.BACKUP,
|
||||
"Files": EnumPermissionsServer.FILES,
|
||||
"Config": EnumPermissionsServer.CONFIG,
|
||||
"Players": EnumPermissionsServer.PLAYERS,
|
||||
"Server_Creation": EnumPermissionsCrafty.SERVER_CREATION,
|
||||
"User_Config": EnumPermissionsCrafty.USER_CONFIG,
|
||||
"Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG,
|
||||
}
|
||||
try:
|
||||
logger.debug("Searching for specified token")
|
||||
|
||||
api_token = self.get_argument("token", "")
|
||||
self.api_token = api_token
|
||||
if api_token is None and self.request.headers.get("Authorization"):
|
||||
api_token = bearer_pattern.sub(
|
||||
"", self.request.headers.get("Authorization")
|
||||
)
|
||||
elif api_token is None:
|
||||
api_token = self.get_cookie("token")
|
||||
user_data = self.controller.users.get_user_by_api_token(api_token)
|
||||
|
||||
logger.debug("Checking results")
|
||||
if user_data:
|
||||
# Login successful! Check perms
|
||||
logger.info(f"User {user_data['username']} has authenticated to API")
|
||||
|
||||
return True # This is to set the "authenticated"
|
||||
logging.debug("Auth unsuccessful")
|
||||
self.access_denied("unknown", "the user provided an invalid token")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.warning("An error occured while authenticating an API user: %s", e)
|
||||
self.finish(
|
||||
self.return_response(
|
||||
403,
|
||||
{
|
||||
"error": "ACCESS_DENIED",
|
||||
"info": "An error occured while authenticating the user",
|
||||
},
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class ServersStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get details about all servers"""
|
||||
authenticated = self.authenticate_user()
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
if not authenticated:
|
||||
return
|
||||
if user_obj["superuser"]:
|
||||
raw_stats = self.controller.servers.get_all_servers_stats()
|
||||
else:
|
||||
raw_stats = self.controller.servers.get_authorized_servers_stats(
|
||||
user_obj["user_id"]
|
||||
)
|
||||
stats = []
|
||||
for rs in raw_stats:
|
||||
s = {}
|
||||
for k, v in rs["server_data"].items():
|
||||
if isinstance(v, datetime):
|
||||
s[k] = v.timestamp()
|
||||
else:
|
||||
s[k] = v
|
||||
stats.append(s)
|
||||
|
||||
# Get server stats
|
||||
# TODO Check perms
|
||||
self.finish(self.write({"servers": stats}))
|
||||
|
||||
|
||||
class NodeStats(ApiHandler):
|
||||
def get(self):
|
||||
"""Get stats for particular node"""
|
||||
authenticated = self.authenticate_user()
|
||||
if not authenticated:
|
||||
return
|
||||
|
||||
# Get node stats
|
||||
node_stats = self.controller.servers.stats.get_node_stats()
|
||||
self.return_response(200, {"code": node_stats["node_stats"]})
|
||||
|
||||
|
||||
class SendCommand(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
server_id = self.get_argument("id")
|
||||
|
||||
if (
|
||||
not user_obj["user_id"]
|
||||
in self.controller.server_perms.get_server_user_list(server_id)
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if not self.permissions[
|
||||
"Commands"
|
||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
||||
):
|
||||
self.access_denied(user)
|
||||
return
|
||||
|
||||
command = self.get_argument("command", default=None, strip=True)
|
||||
server_id = self.get_argument("id")
|
||||
if command:
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
if server.check_running:
|
||||
server.send_command(command)
|
||||
self.return_response(200, {"run": True})
|
||||
else:
|
||||
self.return_response(200, {"error": "SER_NOT_RUNNING"})
|
||||
else:
|
||||
self.return_response(200, {"error": "NO_COMMAND"})
|
||||
|
||||
|
||||
class ServerBackup(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
server_id = self.get_argument("id")
|
||||
|
||||
if (
|
||||
not user_obj["user_id"]
|
||||
in self.controller.server_perms.get_server_user_list(server_id)
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if not self.permissions[
|
||||
"Backup"
|
||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
||||
):
|
||||
self.access_denied(user)
|
||||
return
|
||||
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
|
||||
server.backup_server()
|
||||
|
||||
self.return_response(200, {"code": "SER_BAK_CALLED"})
|
||||
|
||||
|
||||
class StartServer(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
remote_ip = self.get_remote_ip()
|
||||
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
server_id = self.get_argument("id")
|
||||
|
||||
if (
|
||||
not user_obj["user_id"]
|
||||
in self.controller.server_perms.get_server_user_list(server_id)
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
if not self.permissions[
|
||||
"Commands"
|
||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
|
||||
if not server.check_running():
|
||||
self.controller.management.send_command(
|
||||
user_obj["user_id"], server_id, remote_ip, "start_server"
|
||||
)
|
||||
self.return_response(200, {"code": "SER_START_CALLED"})
|
||||
else:
|
||||
self.return_response(500, {"error": "SER_RUNNING"})
|
||||
|
||||
|
||||
class StopServer(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
remote_ip = self.get_remote_ip()
|
||||
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
server_id = self.get_argument("id")
|
||||
|
||||
if (
|
||||
not user_obj["user_id"]
|
||||
in self.controller.server_perms.get_server_user_list(server_id)
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
|
||||
if not self.permissions[
|
||||
"Commands"
|
||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
||||
):
|
||||
self.access_denied(user)
|
||||
return
|
||||
|
||||
server = self.controller.servers.get_server_instance_by_id(server_id)
|
||||
|
||||
if server.check_running():
|
||||
self.controller.management.send_command(
|
||||
user, server_id, remote_ip, "stop_server"
|
||||
)
|
||||
|
||||
self.return_response(200, {"code": "SER_STOP_CALLED"})
|
||||
else:
|
||||
self.return_response(500, {"error": "SER_NOT_RUNNING"})
|
||||
|
||||
|
||||
class RestartServer(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
remote_ip = self.get_remote_ip()
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
server_id = self.get_argument("id")
|
||||
|
||||
if not user_obj["user_id"] in self.controller.server_perms.get_server_user_list(
|
||||
server_id
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
|
||||
if not self.permissions[
|
||||
"Commands"
|
||||
] in self.controller.server_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token), server_id
|
||||
):
|
||||
self.access_denied(user)
|
||||
|
||||
self.controller.management.send_command(
|
||||
user, server_id, remote_ip, "restart_server"
|
||||
)
|
||||
self.return_response(200, {"code": "SER_RESTART_CALLED"})
|
||||
|
||||
|
||||
class CreateUser(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
|
||||
user_obj["user_id"]
|
||||
)
|
||||
if (
|
||||
not self.permissions["User_Config"] in user_perms
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if not self.permissions[
|
||||
"User_Config"
|
||||
] in self.controller.crafty_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token)
|
||||
):
|
||||
self.access_denied(user)
|
||||
return
|
||||
|
||||
new_username = self.get_argument("username").lower()
|
||||
new_pass = self.get_argument("password")
|
||||
manager = int(user_obj["user_id"])
|
||||
|
||||
if new_username:
|
||||
self.controller.users.add_user(
|
||||
new_username, manager, new_pass, "default@example.com", True, False
|
||||
)
|
||||
|
||||
self.return_response(
|
||||
200,
|
||||
{
|
||||
"code": "COMPLETE",
|
||||
"username": new_username,
|
||||
"password": new_pass,
|
||||
},
|
||||
)
|
||||
else:
|
||||
self.return_response(
|
||||
500,
|
||||
{
|
||||
"error": "MISSING_PARAMS",
|
||||
"info": "Some paramaters failed validation",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeleteUser(ApiHandler):
|
||||
def post(self):
|
||||
user = self.authenticate_user()
|
||||
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
user_perms = self.controller.crafty_perms.get_crafty_permissions_list(
|
||||
user_obj["user_id"]
|
||||
)
|
||||
|
||||
if (
|
||||
not self.permissions["User_Config"] in user_perms
|
||||
and not user_obj["superuser"]
|
||||
):
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if not self.permissions[
|
||||
"User_Config"
|
||||
] in self.controller.crafty_perms.get_api_key_permissions_list(
|
||||
self.controller.users.get_api_key_by_token(self.api_token)
|
||||
):
|
||||
self.access_denied(user)
|
||||
return
|
||||
|
||||
user_id = self.get_argument("user_id", None, True)
|
||||
user_to_del = self.controller.users.get_user_by_id(user_id)
|
||||
|
||||
if user_to_del["superuser"]:
|
||||
self.return_response(
|
||||
500,
|
||||
{"error": "NOT_ALLOWED", "info": "You cannot delete a super user"},
|
||||
)
|
||||
else:
|
||||
if user_id:
|
||||
self.controller.users.remove_user(user_id)
|
||||
self.return_response(200, {"code": "COMPLETED"})
|
||||
|
||||
|
||||
class ListServers(ApiHandler):
|
||||
def get(self):
|
||||
user = self.authenticate_user()
|
||||
user_obj = self.controller.users.get_user_by_api_token(self.api_token)
|
||||
|
||||
if user is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if self.api_token is None:
|
||||
self.access_denied("unknown")
|
||||
return
|
||||
|
||||
if user_obj["superuser"]:
|
||||
servers = self.controller.servers.get_all_defined_servers()
|
||||
servers = [str(i) for i in servers]
|
||||
else:
|
||||
servers = self.controller.servers.get_authorized_servers(
|
||||
user_obj["user_id"]
|
||||
)
|
||||
page_servers = []
|
||||
for server in servers:
|
||||
if server not in page_servers:
|
||||
page_servers.append(
|
||||
DatabaseShortcuts.get_data_obj(server.server_object)
|
||||
)
|
||||
servers = page_servers
|
||||
servers = [str(i) for i in servers]
|
||||
|
||||
self.return_response(
|
||||
200,
|
||||
{
|
||||
"code": "COMPLETED",
|
||||
"servers": servers,
|
||||
},
|
||||
)
|
@ -14,6 +14,7 @@ from app.classes.shared.translation import Translation
|
||||
from app.classes.shared.main_models import DatabaseShortcuts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
auth_log = logging.getLogger("auth")
|
||||
|
||||
bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE)
|
||||
|
||||
@ -231,9 +232,16 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||
user,
|
||||
)
|
||||
logging.debug("Auth unsuccessful")
|
||||
auth_log.error(
|
||||
f"Authentication attempted from {self.get_remote_ip()}. Invalid token"
|
||||
)
|
||||
self.access_denied(None, "the user provided an invalid token")
|
||||
return None
|
||||
except Exception as auth_exception:
|
||||
auth_log.error(
|
||||
f"Authentication attempted from {self.get_remote_ip()}."
|
||||
f" Error: {auth_exception}"
|
||||
)
|
||||
logger.debug(
|
||||
"An error occured while authenticating an API user:",
|
||||
exc_info=auth_exception,
|
||||
|
@ -210,6 +210,8 @@ class PanelHandler(BaseHandler):
|
||||
error = self.get_argument("error", "WTF Error!")
|
||||
|
||||
template = "panel/denied.html"
|
||||
if self.helper.crafty_starting:
|
||||
page = "loading"
|
||||
|
||||
now = time.time()
|
||||
formatted_time = str(
|
||||
@ -243,9 +245,13 @@ class PanelHandler(BaseHandler):
|
||||
for r in exec_user["roles"]:
|
||||
role = self.controller.roles.get_role(r)
|
||||
exec_user_role.add(role["role_name"])
|
||||
defined_servers = self.controller.servers.get_authorized_servers(
|
||||
exec_user["user_id"]
|
||||
)
|
||||
# get_auth_servers will throw an exception if run while Crafty is starting
|
||||
if not self.helper.crafty_starting:
|
||||
defined_servers = self.controller.servers.get_authorized_servers(
|
||||
exec_user["user_id"]
|
||||
)
|
||||
else:
|
||||
defined_servers = []
|
||||
|
||||
user_order = self.controller.users.get_user_by_id(exec_user["user_id"])
|
||||
user_order = user_order["server_order"].split(",")
|
||||
@ -481,6 +487,12 @@ class PanelHandler(BaseHandler):
|
||||
subpage = nh3.clean(self.get_argument("subpage", ""))
|
||||
|
||||
server_id = self.check_server_id()
|
||||
# load page the user was on last
|
||||
server_subpage = self.controller.servers.server_subpage.get(server_id, "")
|
||||
if subpage == "" and server_subpage != "":
|
||||
subpage = self.controller.servers.server_subpage.get(server_id, "")
|
||||
else:
|
||||
self.controller.servers.server_subpage[server_id] = subpage
|
||||
if server_id is None:
|
||||
return
|
||||
if not self.failed_server:
|
||||
@ -1609,7 +1621,8 @@ class PanelHandler(BaseHandler):
|
||||
logs_thread.start()
|
||||
self.redirect("/panel/dashboard")
|
||||
return
|
||||
|
||||
if self.helper.crafty_starting:
|
||||
template = "panel/loading.html"
|
||||
self.render(
|
||||
template,
|
||||
data=page_data,
|
||||
|
@ -6,6 +6,7 @@ from app.classes.models.users import HelperUsers
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
auth_log = logging.getLogger("auth")
|
||||
|
||||
|
||||
class PublicHandler(BaseHandler):
|
||||
@ -96,18 +97,27 @@ class PublicHandler(BaseHandler):
|
||||
page_data["query"] = self.request.query
|
||||
|
||||
if page == "login":
|
||||
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
|
||||
|
||||
entered_username = nh3.clean(self.get_argument("username"))
|
||||
entered_password = nh3.clean(self.get_argument("password"))
|
||||
entered_password = self.get_argument("password")
|
||||
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
|
||||
user_data = HelperUsers.get_user_model(user_id)
|
||||
except:
|
||||
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||
auth_log.error(
|
||||
f"User attempted to log into {entered_username}."
|
||||
f" Authentication failed from remote IP {self.get_remote_ip()}"
|
||||
" Users does not exist."
|
||||
)
|
||||
error_msg = "Incorrect username or password. Please try again."
|
||||
# self.clear_cookie("user")
|
||||
# self.clear_cookie("user_data")
|
||||
@ -120,6 +130,12 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
# if we don't have a user
|
||||
if not user_data:
|
||||
auth_log.error(
|
||||
f"User attempted to log into {entered_username}. Authentication"
|
||||
f" failed from remote IP {self.get_remote_ip()}"
|
||||
" User does not exist."
|
||||
)
|
||||
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||
error_msg = "Incorrect username or password. Please try again."
|
||||
# self.clear_cookie("user")
|
||||
# self.clear_cookie("user_data")
|
||||
@ -132,6 +148,12 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
# if they are disabled
|
||||
if not user_data.enabled:
|
||||
auth_log.error(
|
||||
f"User attempted to log into {entered_username}. "
|
||||
f"Authentication failed from remote IP {self.get_remote_ip()}."
|
||||
" 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."
|
||||
@ -159,7 +181,11 @@ class PublicHandler(BaseHandler):
|
||||
user_data.last_ip = self.get_remote_ip()
|
||||
user_data.last_login = Helpers.get_time_as_string()
|
||||
user_data.save()
|
||||
|
||||
auth_log.info(
|
||||
f"{entered_username} successfully"
|
||||
" authenticated and logged"
|
||||
f" into panel from remote IP {self.get_remote_ip()}"
|
||||
)
|
||||
# log this login
|
||||
self.controller.management.add_to_audit_log(
|
||||
user_data.user_id, "Logged in", 0, self.get_remote_ip()
|
||||
@ -172,6 +198,11 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
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()}"
|
||||
)
|
||||
self.controller.log_attempt(self.get_remote_ip(), entered_username)
|
||||
# self.clear_cookie("user")
|
||||
# self.clear_cookie("user_data")
|
||||
self.clear_cookie("token")
|
||||
|
@ -7,7 +7,7 @@ from app.classes.shared.helpers import Helpers
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
auth_log = logging.getLogger("auth")
|
||||
login_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -29,6 +29,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
||||
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)}
|
||||
)
|
||||
@ -36,6 +40,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
||||
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,
|
||||
{
|
||||
@ -52,12 +60,23 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
||||
user_data = Users.get_or_none(Users.username == username)
|
||||
|
||||
if user_data is None:
|
||||
self.controller.log_attempt(self.get_remote_ip(), username)
|
||||
auth_log.error(
|
||||
f"User attempted to log into {username}."
|
||||
" Authentication failed from remote IP"
|
||||
f" {self.get_remote_ip()}. User not found"
|
||||
)
|
||||
return self.finish_json(
|
||||
401,
|
||||
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
|
||||
)
|
||||
|
||||
if not user_data.enabled:
|
||||
auth_log.error(
|
||||
f"User attempted to log into {username}."
|
||||
" Authentication failed from remote"
|
||||
f" IP {self.get_remote_ip()} account disabled"
|
||||
)
|
||||
self.finish_json(
|
||||
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
|
||||
)
|
||||
@ -67,6 +86,11 @@ class ApiAuthLoginHandler(BaseApiHandler):
|
||||
|
||||
# Valid Login
|
||||
if login_result:
|
||||
auth_log.info(
|
||||
f"{username} successfully"
|
||||
" authenticated and logged"
|
||||
f" into panel from remote IP {self.get_remote_ip()}"
|
||||
)
|
||||
logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
|
||||
|
||||
# record this login
|
||||
|
@ -29,6 +29,14 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
|
||||
) = auth_data
|
||||
|
||||
data = self.helper.get_announcements()
|
||||
if not data:
|
||||
return self.finish_json(
|
||||
424,
|
||||
{
|
||||
"status": "error",
|
||||
"data": "Failed to get announcements",
|
||||
},
|
||||
)
|
||||
cleared = str(
|
||||
self.controller.users.get_user_by_id(auth_data[4]["user_id"])[
|
||||
"cleared_notifs"
|
||||
@ -84,6 +92,14 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
|
||||
},
|
||||
)
|
||||
announcements = self.helper.get_announcements()
|
||||
if not announcements:
|
||||
return self.finish_json(
|
||||
424,
|
||||
{
|
||||
"status": "error",
|
||||
"data": "Failed to get current announcements",
|
||||
},
|
||||
)
|
||||
res = [d.get("id", None) for d in announcements]
|
||||
cleared_notifs = str(
|
||||
self.controller.users.get_user_by_id(auth_data[4]["user_id"])[
|
||||
|
@ -7,6 +7,7 @@ from jsonschema.exceptions import ValidationError
|
||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
||||
from app.classes.shared.helpers import Helpers
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
from app.classes.web.websocket_handler import WebSocketManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
files_get_schema = {
|
||||
@ -73,7 +74,7 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
|
||||
else:
|
||||
if user_id:
|
||||
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
WebSocketManager().broadcast_user(
|
||||
user_id,
|
||||
"send_start_error",
|
||||
{
|
||||
@ -85,7 +86,7 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
|
||||
else:
|
||||
if not self.helper.check_path_exists(folder) and user_id:
|
||||
user_lang = self.controller.users.get_user_lang_by_id(user_id)
|
||||
self.helper.websocket_helper.broadcast_user(
|
||||
WebSocketManager().broadcast_user(
|
||||
user_id,
|
||||
"send_start_error",
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
|
||||
|
||||
try:
|
||||
data = orjson.loads(self.request.body)
|
||||
except orjson.decoder.JSONDecodeError as e:
|
||||
except orjson.JSONDecodeError as e:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||
)
|
||||
|
@ -72,9 +72,9 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
|
||||
FileHelpers.del_file(
|
||||
os.path.join(backup_conf["backup_path"], data["filename"])
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "NO BACKUP FOUND"}
|
||||
400, {"status": "error", "error": f"DELETE FAILED with error {e}"}
|
||||
)
|
||||
self.controller.management.add_to_audit_log(
|
||||
auth_data[4]["user_id"],
|
||||
|
@ -215,7 +215,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
|
||||
|
||||
user_obj = HelperUsers.get_user_model(user_id)
|
||||
if "password" in data and str(user["user_id"]) != str(user_id):
|
||||
if str(user["user_id"]) != str(user_obj.manager):
|
||||
if str(user["user_id"]) != str(user_obj.manager) and not user["superuser"]:
|
||||
# TODO: edit your own password
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"}
|
||||
|
@ -22,18 +22,6 @@ from app.classes.web.default_handler import DefaultHandler
|
||||
from app.classes.web.routes.api.api_handlers import api_handlers
|
||||
from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers
|
||||
from app.classes.web.server_handler import ServerHandler
|
||||
from app.classes.web.api_handler import (
|
||||
ServersStats,
|
||||
NodeStats,
|
||||
ServerBackup,
|
||||
StartServer,
|
||||
StopServer,
|
||||
RestartServer,
|
||||
CreateUser,
|
||||
DeleteUser,
|
||||
ListServers,
|
||||
SendCommand,
|
||||
)
|
||||
from app.classes.web.websocket_handler import WebSocketHandler
|
||||
from app.classes.web.static_handler import CustomStaticHandler
|
||||
from app.classes.web.upload_handler import UploadHandler
|
||||
@ -162,17 +150,6 @@ class Webserver:
|
||||
(r"/ws", WebSocketHandler, handler_args),
|
||||
(r"/upload", UploadHandler, handler_args),
|
||||
(r"/status", StatusHandler, handler_args),
|
||||
# API Routes V1
|
||||
(r"/api/v1/stats/servers", ServersStats, handler_args),
|
||||
(r"/api/v1/stats/node", NodeStats, handler_args),
|
||||
(r"/api/v1/server/send_command", SendCommand, handler_args),
|
||||
(r"/api/v1/server/backup", ServerBackup, handler_args),
|
||||
(r"/api/v1/server/start", StartServer, handler_args),
|
||||
(r"/api/v1/server/stop", StopServer, handler_args),
|
||||
(r"/api/v1/server/restart", RestartServer, handler_args),
|
||||
(r"/api/v1/list_servers", ListServers, handler_args),
|
||||
(r"/api/v1/users/create_user", CreateUser, handler_args),
|
||||
(r"/api/v1/users/delete_user", DeleteUser, handler_args),
|
||||
# API Routes V2
|
||||
*api_handlers(handler_args),
|
||||
# API Routes OpenMetrics
|
||||
|
@ -10,16 +10,20 @@
|
||||
},
|
||||
"schedule": {
|
||||
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
|
||||
},
|
||||
"auth": {
|
||||
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
|
||||
},
|
||||
"cmd_queue": {
|
||||
"format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s"
|
||||
}
|
||||
},
|
||||
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "commander",
|
||||
"stream": "ext://sys.stdout"
|
||||
},
|
||||
|
||||
"main_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "commander",
|
||||
@ -50,24 +54,60 @@
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"auth_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "auth",
|
||||
"filename": "logs/auth.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"cmd_queue_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "auth",
|
||||
"filename": "logs/cmd_queue.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8"
|
||||
}
|
||||
},
|
||||
|
||||
"loggers": {
|
||||
"": {
|
||||
"level": "INFO",
|
||||
"handlers": ["main_file_handler", "session_file_handler"],
|
||||
"handlers": [
|
||||
"main_file_handler",
|
||||
"session_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
},
|
||||
"tornado.access": {
|
||||
"level": "INFO",
|
||||
"handlers": ["tornado_access_file_handler"],
|
||||
"handlers": [
|
||||
"tornado_access_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
},
|
||||
"apscheduler": {
|
||||
"level": "INFO",
|
||||
"handlers": ["schedule_file_handler"],
|
||||
"handlers": [
|
||||
"schedule_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
},
|
||||
"auth": {
|
||||
"level": "INFO",
|
||||
"handlers": [
|
||||
"auth_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
},
|
||||
"cmd_queue": {
|
||||
"level": "INFO",
|
||||
"handlers": [
|
||||
"cmd_queue_file_handler"
|
||||
],
|
||||
"propagate": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 2,
|
||||
"sub": 1
|
||||
"sub": 3
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.6 KiB |
10
app/frontend/static/assets/images/crafty-logo-square.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 425 425">
|
||||
<!-- Background -->
|
||||
<rect x="0" y="0" rx="105" ry="105" width="425" height="425" style="fill: #22283A"/>
|
||||
<!-- green C -->
|
||||
<path fill="hsl(160, 59%, 45%)" d="M 162.5 182.5 l 38 -15 q 0 -40 -40 -40 l -95 0 q -40 0 -40 40 l 0 90 q 0 40 40 40 l 95 0 q 40 0 40 -40 l -38 -20 l 0 28 l -100 0 l 0 -108 l 100 0 z"/>
|
||||
<!-- blue C -->
|
||||
<path fill="hsl(192, 99%, 45%)" d="M 262.5 182.5 l -38 -15 q 0 -40 40 -40 l 95 0 q 40 0 40 40 l 0 90 q 0 40 -40 40 l -95 0 q -40 0 -40 -40 l 38 -20 l 0 28 l 100 0 l 0 -108 l -100 0 z"/>
|
||||
<!-- line thing in middle of Cs -->
|
||||
<path fill="hsl(266, 71%, 57%)" d="M 142.5 191.5 q -10 0 -10 10 l 0 19 q 0 10 10 10 l 140 0 q 10 0 10 -10 l 0 -19 q 0 -10 -10 -10 z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 750 B |
@ -82,6 +82,9 @@
|
||||
<span class="mdi mdi-chevron-double-left"></span>
|
||||
<span class="mdi mdi-chevron-double-right"></span>
|
||||
</button>
|
||||
|
||||
<span class="badge-pill badge-outline-primary" id="server-name-nav" style="display: none;"></span>
|
||||
|
||||
|
||||
{% include notify.html %}
|
||||
|
||||
@ -381,18 +384,21 @@
|
||||
if (x) {
|
||||
x.remove()
|
||||
}
|
||||
bootbox.alert({
|
||||
bootbox.confirm({
|
||||
title: "{{ translate('notify', 'downloadLogs', data['lang']) }}",
|
||||
message: "{{ translate('notify', 'finishedPreparing', data['lang']) }}",
|
||||
buttons: {
|
||||
ok: {
|
||||
confirm: {
|
||||
label: 'Download',
|
||||
className: 'btn-info'
|
||||
}
|
||||
},
|
||||
callback: function () {
|
||||
console.log("in callback")
|
||||
location.href = "/panel/download_support_package";
|
||||
callback: function (result) {
|
||||
if (result){
|
||||
location.href = "/panel/download_support_package";
|
||||
} else {
|
||||
bootbox.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -602,4 +608,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -93,7 +93,7 @@
|
||||
return true;
|
||||
}
|
||||
function updateAnnouncements(data) {
|
||||
console.log(data)
|
||||
console.log(data);
|
||||
let text = "";
|
||||
for (let value of data) {
|
||||
text += `<li class="card-header header-sm justify-content-between align-items-center" id="${value.id}"><p style="float: right;"><i data-id="${value.id}"class="clear-button fa-regular fa-x"></i></p><a style="color: var(--purple);" href=${value.link} target="_blank"><h6>${value.title}</h6><small><p>${value.date}</p></small><p>${value.desc}</p></li></a>`
|
||||
@ -138,12 +138,12 @@
|
||||
console.log(responseData);
|
||||
setTimeout(function() {
|
||||
getAnnouncements();
|
||||
}, 1800);
|
||||
}, 1800000); //Wait 30 minutes in miliseconds
|
||||
console.log("Registered annoucement fetch event in 30 minutes.")
|
||||
if (responseData.status === "ok") {
|
||||
updateAnnouncements(responseData.data)
|
||||
} else {
|
||||
updateAnnouncements("<li><p>Trouble Getting Annoucements</p></li>")
|
||||
updateAnnouncements([])
|
||||
}
|
||||
}
|
||||
async function send_clear(uuid) {
|
||||
|
@ -170,7 +170,7 @@
|
||||
{% block js %}
|
||||
<script>
|
||||
function replacer(key, value) {
|
||||
if (key == "disabled_language_files") {
|
||||
if (key == "disabled_language_files" || key == "monitored_mounts") {
|
||||
if (value == 0) {
|
||||
return []
|
||||
} else {
|
||||
|
@ -102,8 +102,11 @@
|
||||
<div class="col-12 mt-4">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper" style="width: 100%;">
|
||||
<h5 class="mb-1 font-weight-medium text-primary">Storage
|
||||
{% if len(data["monitored"]) > 0 %}
|
||||
<h5 class="mb-1 font-weight-medium text-primary">{{ translate('dashboard', 'storage',
|
||||
data['lang']) }}
|
||||
</h5>
|
||||
{% end %}
|
||||
<div id="storage_data">
|
||||
<div class="row">
|
||||
{% for item in data['hosts_data']['disk_json'] %}
|
||||
@ -615,7 +618,7 @@
|
||||
}
|
||||
|
||||
|
||||
function warn(message, link = null, className = null) {
|
||||
function warnServer(message, link = null, className = null) {
|
||||
var closeEl = document.createElement('span');
|
||||
var strongEL = document.createElement('strong');
|
||||
var msgEl = document.createElement('div');
|
||||
@ -799,7 +802,7 @@
|
||||
setTimeout(finishTimeout, 60000);
|
||||
});
|
||||
function finishTimeout() {
|
||||
warn("It seems this is taking a while...it's possible you're using UBlock or a similar ad blocker and it's causing some of our connections to not make it to the server. Try disabling your ad blocker.",
|
||||
warnServer("It seems this is taking a while...it's possible you're using UBlock or a similar ad blocker and it's causing some of our connections to not make it to the server. Try disabling your ad blocker.",
|
||||
null, 'wssError');
|
||||
}
|
||||
$(".stop_button").click(function () {
|
||||
|
73
app/frontend/templates/panel/loading.html
Normal file
@ -0,0 +1,73 @@
|
||||
{% extends ../base.html %}
|
||||
|
||||
{% block meta %}
|
||||
{% end %}
|
||||
|
||||
{% block title %}Crafty Controller Starting{% end %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content-wrapper">
|
||||
<div class="card-header justify-content-between align-items-center" style="border: none;">
|
||||
<div id="image-div" style="width: 100%;">
|
||||
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
|
||||
alt="Crafty Logo, Crafty is loading" width="20%" style="clear: both;">
|
||||
</div>
|
||||
<br>
|
||||
</br>
|
||||
<div id="text-div" style="width: 100%; text-align: center;">
|
||||
<h2 id="status" style="display: block;" data-init="{{ translate('startup', 'serverInit', data['lang']) }}"
|
||||
data-server="{{ translate('startup', 'server', data['lang']) }}"
|
||||
data-internet="{{ translate('startup', 'internet', data['lang']) }}"
|
||||
data-tasks="{{ translate('startup', 'tasks', data['lang']) }}"
|
||||
data-internals="{{ translate('startup', 'internals', data['lang']) }}"
|
||||
data-almost="{{ translate('startup', 'almost', data['lang']) }}">
|
||||
{{ translate('startup', 'starting', data['lang']) }}</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.img-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function rotateImage(degree) {
|
||||
$('#logo-animate').animate({ transform: degree }, {
|
||||
step: function (now, fx) {
|
||||
$(this).css({
|
||||
'-webkit-transform': 'rotate(' + now + 'deg)',
|
||||
'-moz-transform': 'rotate(' + now + 'deg)',
|
||||
'transform': 'rotate(' + now + 'deg)'
|
||||
});
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
rotateImage(360);
|
||||
}, 2000);
|
||||
}
|
||||
$(document).ready(function () {
|
||||
setTimeout(function () {
|
||||
rotateImage(360);
|
||||
}, 2000);
|
||||
if (webSocket) {
|
||||
webSocket.on('update', function (data) {
|
||||
if ("server" in data) {
|
||||
$("#status").html(`${$("#status").data(data.section)} ${data.server}...`);
|
||||
} else {
|
||||
$("#status").html($("#status").data(data.section));
|
||||
}
|
||||
});
|
||||
webSocket.on('send_start_reload', function () {
|
||||
setTimeout(function () {
|
||||
location.href = '/panel/dashboard'
|
||||
}, 5000);
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% end %}
|
@ -363,6 +363,7 @@ data['lang']) }}{% end %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
const userId = new URLSearchParams(document.location.search).get('id');
|
||||
function submit_user(event) {
|
||||
if (!validateForm()) {
|
||||
event.preventDefault();
|
||||
@ -399,8 +400,6 @@ data['lang']) }}{% end %}
|
||||
}
|
||||
}
|
||||
$("#user_form").on("submit", async function (e) {
|
||||
const userId = new URLSearchParams(document.location.search).get('id');
|
||||
console.log(userId)
|
||||
e.preventDefault();
|
||||
let password = null;
|
||||
if(!userId){
|
||||
|
@ -248,12 +248,38 @@
|
||||
$("#player-body").html(text);
|
||||
|
||||
}
|
||||
|
||||
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
||||
function getCookie(name) {
|
||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||
return r ? r[1] : undefined;
|
||||
}
|
||||
const token = getCookie("_xsrf")
|
||||
$(window).ready(function () {
|
||||
console.log("ready!");
|
||||
|
||||
//if (webSocket) {
|
||||
webSocket.on('update_server_details', update_server_details);
|
||||
add_server_name();
|
||||
//}
|
||||
});
|
||||
async function add_server_name(){
|
||||
let res = await fetch(`/api/v2/servers/${serverId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
console.log(responseData)
|
||||
$("#server-name-nav").html(`${responseData.data['server_name']}`)
|
||||
$("#server-name-nav").show();
|
||||
} else {
|
||||
|
||||
bootbox.alert({
|
||||
title: responseData.error,
|
||||
message: responseData.error_data
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
@ -69,7 +69,7 @@
|
||||
<td>Banned on {{ player['banned_on'] }}</td>
|
||||
<td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td>
|
||||
<td class="buttons">
|
||||
<button onclick="send_command_to_server(`pardon {{ player['name'] }} `)" type="button" class="btn btn-danger">Unban</button>
|
||||
<button onclick="send_command_to_server(`pardon {{ player['name'] }}`)" type="button" class="btn btn-danger">Unban</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
|
@ -428,6 +428,7 @@
|
||||
if ($("#root_files_button").hasClass("clicked")){
|
||||
formDataObject.exclusions = excluded;
|
||||
}
|
||||
delete formDataObject.root_path
|
||||
console.log(excluded);
|
||||
console.log(formDataObject);
|
||||
// Format the plain form data as JSON
|
||||
@ -650,10 +651,10 @@
|
||||
let checked = ""
|
||||
let dpath = value.path;
|
||||
let filename = key;
|
||||
if (value.excluded){
|
||||
checked = "checked"
|
||||
}
|
||||
if (value.dir){
|
||||
if (value.excluded){
|
||||
checked = "checked"
|
||||
}
|
||||
text += `<li class="tree-item" data-path="${dpath}">
|
||||
\n<div id="${dpath}" data-path="${dpath}" data-name="${filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" class="checkBoxClass excluded" value="${dpath}" ${checked}>
|
||||
|
@ -43,8 +43,10 @@
|
||||
<form class="forms-sample" method="post" id="webhook_form"
|
||||
action="/panel/edit_webhook?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['webhook']['id'] }}">
|
||||
{% end %}
|
||||
<select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type">
|
||||
<select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type">
|
||||
{% if data['new_webhook'] == False %}
|
||||
<option value="{{data['webhook']['webhook_type']}}">{{data['webhook']['webhook_type']}}</option>
|
||||
{% end %}
|
||||
{% for type in data['providers'] %}
|
||||
{% if type != data['webhook']['webhook_type'] %}
|
||||
<option value="{{type}}">{{type}}</option>
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "Verzögerter Start",
|
||||
"status": "Status",
|
||||
"stop": "Stoppen",
|
||||
"storage": "Speicher",
|
||||
"version": "Version",
|
||||
"welcome": "Willkommen bei Crafty Controller"
|
||||
},
|
||||
@ -591,6 +592,15 @@
|
||||
"newServer": "Neuen Server erstellen",
|
||||
"servers": "Server"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Nur noch einen Moment, fast geschafft",
|
||||
"internals": "Crafty's interne Komponneten initialisieren und starten",
|
||||
"internet": "Verbindung zum Internet überprüfen",
|
||||
"server": "initialisieren ",
|
||||
"serverInit": "Server initialisieren",
|
||||
"starting": "Crafty startet...",
|
||||
"tasks": "Zeitplan-Aufgaben werden geladen"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API Schlüssel",
|
||||
"auth": "Autorisiert? ",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "Delayed-Start",
|
||||
"status": "Status",
|
||||
"stop": "Stop",
|
||||
"storage": "Storage",
|
||||
"version": "Version",
|
||||
"welcome": "Welcome to Crafty Controller"
|
||||
},
|
||||
@ -590,6 +591,15 @@
|
||||
"newServer": "Create New Server",
|
||||
"servers": "Servers"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Finishing up. Hang on tight...",
|
||||
"internals": "Configuring and starting Crafty's internal components",
|
||||
"internet": "Checking for internet connection",
|
||||
"server": "Initializing ",
|
||||
"serverInit": "Initializing Servers",
|
||||
"starting": "Crafty Is Starting...",
|
||||
"tasks": "Starting Tasks Scheduler"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API Keys",
|
||||
"auth": "Authorized? ",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "Démarrage retardé",
|
||||
"status": "Statut",
|
||||
"stop": "Arrêter",
|
||||
"storage": "Stockage",
|
||||
"version": "Version",
|
||||
"welcome": "Bienvenue sur Crafty Controller"
|
||||
},
|
||||
@ -591,6 +592,15 @@
|
||||
"newServer": "Créer un Nouveau Serveur",
|
||||
"servers": "Serveurs"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Finalisation. Patienter ...",
|
||||
"internals": "Configuration et Démarrage des composants internes de Crafty",
|
||||
"internet": "Vérification de la connexion à Internet",
|
||||
"server": "Initialisation ",
|
||||
"serverInit": "Initialisation des Serveurs",
|
||||
"starting": "Crafty Démarre ...",
|
||||
"tasks": "Démarrage du planificateur de tâches"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "Clés API",
|
||||
"auth": "Authorisé ? ",
|
||||
|
@ -53,6 +53,20 @@
|
||||
"translationTitle": "שפת התרגום",
|
||||
"translator": "מתרגמים"
|
||||
},
|
||||
"customLogin": {
|
||||
"apply": "החל",
|
||||
"backgroundUpload": "העלאת רקע",
|
||||
"customLoginPage": "התאמת דף הכניסה",
|
||||
"delete": "מחק",
|
||||
"labelLoginImage": "בחר את רקע כניסתך",
|
||||
"loginBackground": "תמונת רקע לכניסה",
|
||||
"loginImage": "העלה תמונת רקע למסך הכניסה.",
|
||||
"loginOpacity": "בחר את שקיפות חלון הכניסה",
|
||||
"pageTitle": "דף כניסה מותאם אישית",
|
||||
"preview": "תצוגה מקדימה",
|
||||
"select": "בחר",
|
||||
"selectImage": "בחר תמונה"
|
||||
},
|
||||
"dashboard": {
|
||||
"actions": "פעולות",
|
||||
"allServers": "כל השרתים",
|
||||
@ -75,6 +89,7 @@
|
||||
"dashboard": "פאנל",
|
||||
"delay-explained": "השירות/סוכן התחיל לאחרונה והוא מעכב את הדלקת שרת המיינקראפט",
|
||||
"host": "אחסון",
|
||||
"installing": "מתקין...",
|
||||
"kill": "כיבוי מידי",
|
||||
"killing": "...מכבה מידית",
|
||||
"lastBackup": "אחרון:",
|
||||
@ -96,6 +111,7 @@
|
||||
"starting": "התחלה בעיכוב",
|
||||
"status": "סטאטוס",
|
||||
"stop": "עצור",
|
||||
"storage": "שטח אחסון",
|
||||
"version": "גרסה",
|
||||
"welcome": "ברוכים הבאים ל-פאנל קראפטי"
|
||||
},
|
||||
@ -164,20 +180,33 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"agree": "מסכים",
|
||||
"bedrockError": "הורדות Bedrock אינן זמינות. אנא בדוק",
|
||||
"cancel": "בטל",
|
||||
"contact": "בבקשה צרו קשר עם תמיכת פאנל קראפטי באמצעות דיסקורד",
|
||||
"craftyStatus": "דף המצב של Crafty",
|
||||
"cronFormat": "זוהה פורמט Cron לא תקין",
|
||||
"embarassing": "אוי, טוב, זה מביך.",
|
||||
"error": "שגיאה!",
|
||||
"eulaAgree": "אתם מסכימים?",
|
||||
"eulaMsg": "עליכם להסכים להסכם הרישיון למשתמש הקצה. עותק של הסכם הרישיון למשתמש הקצה של מוג'אנג מקושר תחת הודעה זו.",
|
||||
"eulaTitle": "להסכים להסכם רישיון משתמש קצה של מוג'אנג",
|
||||
"fileError": "סוג הקובץ חייב להיות תמונה.",
|
||||
"fileTooLarge": "העלאה נכשלה. העלאת הקובץ גדולה מדי. פנה למנהל המערכת לקבלת סיוע.",
|
||||
"hereIsTheError": "הנה השגיאה",
|
||||
"installerJava": "נכשל בהתקנת {} : התקנות של שרת Forge דורשות Java. זוהה ש-Java אינו מותקן. אנא התקן את Java ואז התקן את השרת.",
|
||||
"internet": "גילינו שלמכונה(מחשב) שמריצה את קראפטי אין חיבור לאינטרנט. חיבורי לקוחות לשרת עשויים להיות מוגבלים.",
|
||||
"migration": "אחסון השרת הראשי של Crafty מועבר למיקום חדש. כל הפעלות השרתים נעצרו במהלך זמן זה. אנא המתן בזמן שאנו מסיימים את המעבר הזה",
|
||||
"no-file": "נראה שאיננו מצליחים לאתר את הקובץ המבוקש. בדוק שוב את הנתיב. האם ל-קראפטי יש הרשאות מתאימות?",
|
||||
"noInternet": "Crafty נתקל בבעיות גישה לאינטרנט. יצירת שרתים הושבתה. אנא בדוק את חיבור האינטרנט שלך ורענן את הדף הזה.",
|
||||
"noJava": "השרת {} לא הצליח להתחיל עם קוד השגיאה: גילינו ש-Java אינו מותקן. אנא התקינו את Java ואז הפעילו את השרת.",
|
||||
"not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?",
|
||||
"portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.",
|
||||
"privMsg": "וה",
|
||||
"serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק",
|
||||
"serverJars2": "למידע מעודכן ביותר.",
|
||||
"start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}",
|
||||
"superError": "חובה להיות משתמש על כדי לבצע פעולה זו.",
|
||||
"terribleFailure": "איזה כישלון נורא!"
|
||||
},
|
||||
"footer": {
|
||||
@ -189,7 +218,8 @@
|
||||
"forgotPassword": "שכחתי סיסמה",
|
||||
"login": "התחברות",
|
||||
"password": "סיסמה",
|
||||
"username": "שם משתמש"
|
||||
"username": "שם משתמש",
|
||||
"viewStatus": "צפה בדף המצב הציבורי"
|
||||
},
|
||||
"notify": {
|
||||
"activityLog": "יומני פעילות",
|
||||
@ -201,24 +231,38 @@
|
||||
"preparingLogs": "אנא המתינו בזמן שאנו מכינים את היומנים שלכם... נשלח הודעה כשהם יהיו מוכנים. זה עשוי להימשך זמן מה לפריסות שרתים גדולות.",
|
||||
"supportLogs": "יומני תמיכה"
|
||||
},
|
||||
"offline": {
|
||||
"offline": "מנותק",
|
||||
"pleaseConnect": "אנא חבר לאינטרנט כדי להשתמש ב-Crafty."
|
||||
},
|
||||
"panelConfig": {
|
||||
"adminControls": "בקרות מנהל",
|
||||
"allowedServers": "שרתים מורשים",
|
||||
"apply": "החל",
|
||||
"assignedRoles": "תפקידים שהוקצו",
|
||||
"cancel": "ביטול",
|
||||
"clearComms": "ניקוי פקודות שלא בוצעו",
|
||||
"custom": "התאמת Crafty",
|
||||
"delete": "מחיקה",
|
||||
"edit": "עריכה",
|
||||
"enableLang": "אפשר כל השפות",
|
||||
"enabled": "מופעל",
|
||||
"globalExplain": "היכן Crafty שומר את כל קבצי השרת שלך. (נוסיף את הנתיב עם /servers/[uuid של השרת])",
|
||||
"globalServer": "תיקיית שרתים גלובלית",
|
||||
"json": "Config.json",
|
||||
"match": "הסיסמאות חייבות להתאים",
|
||||
"newRole": "הוספת תפקיד חדש",
|
||||
"newUser": "הוספת משתמש חדש",
|
||||
"noMounts": "אל תציג נקודות עגינה בלוח המחוונים",
|
||||
"pageTitle": "הגדרת פאנל",
|
||||
"role": "תפקיד",
|
||||
"roleUsers": "תפקידי משתמשים",
|
||||
"roles": "תפקידים",
|
||||
"save": "שמירה",
|
||||
"select": "בחר",
|
||||
"superConfirm": "המשיכו רק אם אתם רוצים שלמשתמש זה תהיה גישה להכל (כל חשבונות המשתמש, השרתים, הגדרות הפאנל וכו'). הם יכולים אפילו למחוק את זכויות משתמש העל שלך.",
|
||||
"superConfirmTitle": "להפעיל משתמש-על? האם אתם בטוחים?",
|
||||
"title": "תצורת Crafty",
|
||||
"user": "משתמש",
|
||||
"users": "משתמשים"
|
||||
},
|
||||
@ -242,14 +286,17 @@
|
||||
"roleTitle": "הגדרות תפקידים",
|
||||
"roleUserName": "שם משתמש",
|
||||
"roleUsers": "תפקידי המשתמשים: ",
|
||||
"selectManager": "בחר מנהל לתפקיד זה",
|
||||
"serverAccess": "?גישה",
|
||||
"serverName": "שם שרת",
|
||||
"serversDesc": "לשרתים מותר לגשת לתפקיד זה"
|
||||
},
|
||||
"serverBackups": {
|
||||
"after": "הרץ פקודה לאחר הגיבוי",
|
||||
"backupAtMidnight": "גיבוי אוטומטי בחצות?",
|
||||
"backupNow": "!גיבוי עכשיו",
|
||||
"backupTask": "החלה משימת גיבוי.",
|
||||
"before": "הרץ פקודה לפני הגיבוי",
|
||||
"cancel": "לבטל",
|
||||
"clickExclude": "לחצו כדי לבחור מה לא יהיה בגיבוי",
|
||||
"compress": "דחוס גיבוי",
|
||||
@ -289,6 +336,8 @@
|
||||
"deleteServerQuestionMessage": "האם אתם בטוחים שברצונכם למחוק את השרת הזה? אחרי זה אין דרך חזרה...",
|
||||
"exeUpdateURL": "כתובת ה-URL של עדכון השרת הניתן להפעלה",
|
||||
"exeUpdateURLDesc": "כתובת אתר להורדה ישירה לקבלת עדכונים.",
|
||||
"ignoredExits": "קודי יציאה של קריסה שלא ישמעו",
|
||||
"ignoredExitsExplain": "קודי יציאה שגילוי הקריסות של Crafty צריך להתעלם מהם כמו 'עצירה' רגילה (מופרדים בפסיקים)",
|
||||
"javaNoChange": "לא למחוק",
|
||||
"javaVersion": "כן למחוק את גרסאת ה-Java המותקנת כרגע",
|
||||
"javaVersionDesc": "אם אתה מתכוון לעקוף את Java, ודא שנתיב ה-Java הנוכחי שלך ב'פקודה לביצוע' עטוף במרכאות (משתנה ברירת המחדל 'java' לא נכלל)",
|
||||
@ -319,7 +368,13 @@
|
||||
"serverPortDesc": "קראפטי צריך פורט בשביל להתחבר לנתונים סטטיסטיים",
|
||||
"serverStopCommand": "פקודת עצירת שרת",
|
||||
"serverStopCommandDesc": "פקודה לשלוח את התוכנית כדי לעצור אותה",
|
||||
"showStatus": "הצג בדף המצב הציבורי",
|
||||
"shutdownTimeout": "זמן קצוב לכיבוי",
|
||||
"statsHint1": "הפורט שבו השרת שלך פועל צריך להיות כאן. זה רק איך Crafty פותח חיבור לשרת שלך לצורך סטטיסטיקות.",
|
||||
"statsHint2": "זה לא משנה את פורט השרת שלך. עדיין צריך לשנות את הפורט בקובץ התצורה של השרת שלך.",
|
||||
"stopBeforeDeleting": "בבקשה לעצור את השרת לפני מחיקתו",
|
||||
"timeoutExplain1": "כמה זמן Crafty יחכה לשרת שלך להיכבות לאחר ביצוע ה",
|
||||
"timeoutExplain2": "פקודה לפני שהוא יכריח את התהליך לרדת.",
|
||||
"update": "עדכנו את קובץ ההפעלה",
|
||||
"yesDelete": "כן, למחוק",
|
||||
"yesDeleteFiles": "כן, מחק קבצים"
|
||||
@ -345,8 +400,12 @@
|
||||
"backup": "גיבוי",
|
||||
"config": "הגדרות",
|
||||
"files": "קבצים",
|
||||
"filter": "סינון יומנים",
|
||||
"filterList": "מילים מסוננות",
|
||||
"logs": "לוג",
|
||||
"metrics": "מדדים",
|
||||
"playerControls": "ניהול שחקנים",
|
||||
"reset": "אפס גלילה",
|
||||
"schedule": "לוח זמנים",
|
||||
"serverDetails": "פרטי שרת",
|
||||
"terminal": "מסוף פקודות"
|
||||
@ -383,6 +442,11 @@
|
||||
"waitUpload": "אנא המתן בזמן שאנו מעלים את הקבצים שלך... זה עשוי לקחת זמן מה.",
|
||||
"yesDelete": "כן, אני מבין.ה את ההשלכות"
|
||||
},
|
||||
"serverMetrics": {
|
||||
"resetZoom": "אפס זום",
|
||||
"zoomHint1": "כדי להגדיל על הגרף החזק את מקש ה-shift והשתמש בגלגלת הגלילה שלך.",
|
||||
"zoomHint2": "חלופית, החזק את מקש ה-shift ולחץ וגרור את האזור שברצונך להגדיל עליו."
|
||||
},
|
||||
"serverPlayerManagement": {
|
||||
"bannedPlayers": "שחקנים בבאן",
|
||||
"loadingBannedPlayers": "טוען שחקנים שהם בבאן",
|
||||
@ -410,18 +474,36 @@
|
||||
"parent-explain": "איזה לוח זמנים צריך להפעיל את זה?",
|
||||
"reaction": "תגובה",
|
||||
"restart": "הפעלה מחדש",
|
||||
"select": "בחר בסיסי / Cron / תגובת שרשרת",
|
||||
"start": "הדלקת שרת",
|
||||
"stop": "כיבוי שרת",
|
||||
"time": "זמן",
|
||||
"time-explain": "באיזו שעה אתה רוצה שהלוח שלך יתבצע?"
|
||||
},
|
||||
"serverSchedules": {
|
||||
"action": "פעולה",
|
||||
"areYouSure": "למחוק משימה מתוזמנת?",
|
||||
"cancel": "לבטל",
|
||||
"cannotSee": "לא רואים הכל?",
|
||||
"cannotSeeOnMobile": "נסה ללחוץ על משימה מתוזמנת לפרטים מלאים.",
|
||||
"child": "ילד של לוח זמנים עם מזהה ",
|
||||
"close": "סגור",
|
||||
"command": "פקודה",
|
||||
"confirm": "אישור",
|
||||
"confirmDelete": "האם ברצונך למחוק את המשימה המתוזמנת הזו? אי אפשר לבטל את זה."
|
||||
"confirmDelete": "האם ברצונך למחוק את המשימה המתוזמנת הזו? אי אפשר לבטל את זה.",
|
||||
"create": "צור לוח זמנים חדש",
|
||||
"cron": "מחרוזת Cron",
|
||||
"delete": "מחק",
|
||||
"details": "פרטי לוח זמנים",
|
||||
"edit": "ערוך",
|
||||
"enabled": "מופעל",
|
||||
"every": "כל",
|
||||
"interval": "מרווח",
|
||||
"name": "שם",
|
||||
"nextRun": "הריצה הבאה",
|
||||
"no": "לא",
|
||||
"scheduledTasks": "משימות מתוזמנות",
|
||||
"yes": "כן"
|
||||
},
|
||||
"serverStats": {
|
||||
"cpuUsage": "שימוש במעבד",
|
||||
@ -444,6 +526,8 @@
|
||||
"commandInput": "הקלידו את הפקודה שלכם",
|
||||
"delay-explained": "השירות/סוכן התחיל לאחרונה והוא מעכב את הדלקת שרת המיינקראפט",
|
||||
"downloading": "...מוריד",
|
||||
"importing": "מייבא...",
|
||||
"installing": "מתקין...",
|
||||
"restart": "הפעלה מחדש",
|
||||
"sendCommand": "שליחת פקודה",
|
||||
"start": "התחלה",
|
||||
@ -468,6 +552,7 @@
|
||||
"importServerButton": "ייבוא שרת!",
|
||||
"importZip": "ייבוא מקובץ Zip",
|
||||
"importing": "מייבא שרת...",
|
||||
"labelZipFile": "בחר את קובץ ה-Zip שלך",
|
||||
"maxMem": "מקסימום זיכרון",
|
||||
"minMem": "מינימום זיכרון",
|
||||
"myNewServer": "השרת החדש שלי",
|
||||
@ -478,6 +563,7 @@
|
||||
"save": "שמור",
|
||||
"selectRole": "בחר תפקידים",
|
||||
"selectRoot": "בחר ארכיון שורש Dir",
|
||||
"selectServer": "בחר שרת",
|
||||
"selectType": "בחר סוג",
|
||||
"selectVersion": "בחר גרסה",
|
||||
"selectZipDir": "בחר את הספרייה בארכיון שממנו אתה רוצה שנפתח קבצים",
|
||||
@ -485,9 +571,13 @@
|
||||
"serverName": "שם השרת",
|
||||
"serverPath": "נתיב שרת",
|
||||
"serverPort": "פורט שרת",
|
||||
"serverSelect": "בחירת שרת",
|
||||
"serverType": "סוג השרת",
|
||||
"serverUpload": "העלה שרת מכווץ",
|
||||
"serverVersion": "גרסת השרת",
|
||||
"sizeInGB": "גודל ב-GB",
|
||||
"uploadButton": "העלה",
|
||||
"uploadZip": "העלה קובץ Zip לייבוא שרת",
|
||||
"zipPath": "נתיב שרת"
|
||||
},
|
||||
"sidebar": {
|
||||
@ -495,10 +585,20 @@
|
||||
"credits": "קרדיט",
|
||||
"dashboard": "פאנל",
|
||||
"documentation": "ויקיפדייה",
|
||||
"inApp": "מסמכים באפליקציה",
|
||||
"navigation": "ניווט",
|
||||
"newServer": "צור שרת חדש",
|
||||
"servers": "שרתים"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "מסיימים. תחזיקו חזק...",
|
||||
"internals": "הגדרה והפעלה של הרכיבים הפנימיים של Crafty",
|
||||
"internet": "בודק את חיבור האינטרנט",
|
||||
"server": "אתחול ",
|
||||
"serverInit": "מפעיל שרתים",
|
||||
"starting": "מתחילים בהפעלת מערכת Crafty...",
|
||||
"tasks": "מתזמן את מחולל המשימות"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API מפתחות",
|
||||
"auth": "מורשה? ",
|
||||
@ -519,6 +619,7 @@
|
||||
"lastLogin": "כניסה אחרונה: ",
|
||||
"lastUpdate": "עדכון אחרון: ",
|
||||
"leaveBlank": "כדי לערוך משתמש מבלי לשנות סיסמה השאר אותו ריק.",
|
||||
"manager": "מנהל",
|
||||
"member": "חבר?",
|
||||
"notExist": "אתה לא יכול למחוק משהו שלא קיים!",
|
||||
"pageTitle": "ערוך משתמש",
|
||||
@ -527,6 +628,7 @@
|
||||
"permName": "שם הרשאה",
|
||||
"repeat": "חזור על הסיסמה",
|
||||
"roleName": "שם התפקיד",
|
||||
"selectManager": "בחר מנהל למשתמש",
|
||||
"super": "משתמש על",
|
||||
"userLang": "שפת משתמש",
|
||||
"userName": "שם משתמש",
|
||||
@ -534,6 +636,30 @@
|
||||
"userRoles": "תפקידי משתמש",
|
||||
"userRolesDesc": "תפקידים שמשתמש זה חבר בהם",
|
||||
"userSettings": "הגדרות משתמש",
|
||||
"userTheme": "ערכת נושא UI",
|
||||
"uses": "מספר השימושים המותרים (-1==ללא הגבלה)"
|
||||
},
|
||||
"webhooks": {
|
||||
"areYouSureDel": "האם אתה בטוח שברצונך למחוק את ה-Webhook הזה?",
|
||||
"areYouSureRun": "האם אתה בטוח שברצונך לבדוק את ה-Webhook הזה?",
|
||||
"backup_server": "גיבוי השרת הושלם",
|
||||
"bot_name": "שם הבוט",
|
||||
"color": "בחר גוון צבע",
|
||||
"crash_detected": "השרת קרס",
|
||||
"edit": "ערוך",
|
||||
"enabled": "מופעל",
|
||||
"jar_update": "השרת הביצועי עודכן",
|
||||
"kill": "השרת נסגר",
|
||||
"name": "שם",
|
||||
"new": "Webhook חדש",
|
||||
"run": "הרץ Webhook לבדיקה",
|
||||
"send_command": "פקודת שרת התקבלה",
|
||||
"start_server": "השרת הופעל",
|
||||
"stop_server": "השרת נעצר",
|
||||
"trigger": "גרם",
|
||||
"type": "סוג ה-Webhook",
|
||||
"url": "URL של ה-Webhook",
|
||||
"webhook_body": "גוף ה-Webhook",
|
||||
"webhooks": "Webhooks"
|
||||
}
|
||||
}
|
@ -111,6 +111,7 @@
|
||||
"starting": "I WAITZ B4 I START",
|
||||
"status": "STATUZ",
|
||||
"stop": "STAHP PLZ",
|
||||
"storage": "STASH BOX",
|
||||
"version": "VERZHUN",
|
||||
"welcome": "WELCOM 2 CWAFTY CONTROLLR"
|
||||
},
|
||||
@ -591,6 +592,15 @@
|
||||
"newServer": "CONSTWUCT A SERVR",
|
||||
"servers": "SERVRS"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "ALMOST DUN. HOLD ON TO YER WHISKERS...",
|
||||
"internals": "SETTIN' UP AN' STARTIN' CWAFTY'S INSIDE BITZ",
|
||||
"internet": "LOOKIN' FOR OUTER SPACE TALKY",
|
||||
"server": "WAKIN' UPZ ",
|
||||
"serverInit": "MAKIN' SERVERZ READY",
|
||||
"starting": "CWAFTY WAKING UP...",
|
||||
"tasks": "STARTIN' TASK SCHEDULERZ"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API KEYS",
|
||||
"auth": "PERMISHUN TO ACESS? ",
|
||||
|
@ -112,6 +112,7 @@
|
||||
"starting": "Aizkavēts-Starts",
|
||||
"status": "Statuss",
|
||||
"stop": "Apturēt",
|
||||
"storage": "Glabātuve",
|
||||
"version": "Versija",
|
||||
"welcome": "Esiet sveicināts Crafty Controller"
|
||||
},
|
||||
@ -592,6 +593,15 @@
|
||||
"newServer": "Izveidot Jaunu Serveri",
|
||||
"servers": "Serveri"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Pabeidz. Vēl tik nedaudz...",
|
||||
"internals": "Konfigurē un Startē Crafty iekšējās komponenetes",
|
||||
"internet": "Pārbauda interneta savienojumu",
|
||||
"server": "Inicializē ",
|
||||
"serverInit": "Inicializē Serverus",
|
||||
"starting": "Crafty Startējas...",
|
||||
"tasks": "Sāknē Notikumu Plānotāju"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API Atslēgas",
|
||||
"auth": "Authorizēts? ",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "Vertraagde start",
|
||||
"status": "Toestand",
|
||||
"stop": "Stoppen",
|
||||
"storage": "Opslagruimte",
|
||||
"version": "Versie",
|
||||
"welcome": "Welkom bij Crafty Controller "
|
||||
},
|
||||
@ -591,6 +592,15 @@
|
||||
"newServer": "Nieuwe server maken",
|
||||
"servers": "Servers"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "De laatste hand leggen. Houd je vast...",
|
||||
"internals": "Crafty's interne componenten configureren en starten",
|
||||
"internet": "Controleren op internetverbinding",
|
||||
"server": "Initialiseren ",
|
||||
"serverInit": "Servers initialiseren",
|
||||
"starting": "Crafty wordt gestart...",
|
||||
"tasks": "Start takenplanner"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API Sleutels",
|
||||
"auth": "Bevoegd? ",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "Opóźniony-Start",
|
||||
"status": "Status",
|
||||
"stop": "Zatrzymaj",
|
||||
"storage": "Przestrzeń dyskowa",
|
||||
"version": "Wersja",
|
||||
"welcome": "Witamy w Crafty Controller"
|
||||
},
|
||||
@ -326,8 +327,8 @@
|
||||
"bePatientDeleteFiles": "Poczekaj, aż usuniemy twój serwer i jego pliki. Strona za chwilę się zamknie.",
|
||||
"bePatientUpdate": "Poczekaj kiedy my aktualizujemy twój serwer. Pobieranie zależy od prędkości twojego internetu.<br /> Strona się odświeży za chwile.",
|
||||
"cancel": "Anuluj",
|
||||
"crashTime": "Crash wyszedł poza limit czasu",
|
||||
"crashTimeDesc": "How long should we wait before we consider your server as crashed?",
|
||||
"crashTime": "Crash serwera wyszedł poza limit czasu",
|
||||
"crashTimeDesc": "Jak długo powinniśmy poczekać zanim uznać serwer za zcrashowany?",
|
||||
"deleteFilesQuestion": "Usuń pliki serwera z maszyny?",
|
||||
"deleteFilesQuestionMessage": "Czy chcesz aby Crafty usunął wszystkie pliki tego serwera? <br><br><strong>To zawiera backupy.</strong>",
|
||||
"deleteServer": "Usuń serwer",
|
||||
@ -403,7 +404,7 @@
|
||||
"filterList": "Filtrowane słowa",
|
||||
"logs": "Logi",
|
||||
"metrics": "Statystyki",
|
||||
"playerControls": "Player Management",
|
||||
"playerControls": "Zarządzanie użytkownikami",
|
||||
"reset": "Resetuj Scrolla",
|
||||
"schedule": "Harmonogram",
|
||||
"serverDetails": "Detale serwera",
|
||||
@ -421,7 +422,7 @@
|
||||
"deleteItemQuestion": "Czy jesteś pewien że chcesz usunąć \" + name + \"?",
|
||||
"deleteItemQuestionMessage": "Usuwasz \\\"\" + path + \"\\\"!<br/><br/>Ta akcja jest nieodwracalna i zostanie usunięta na zawsze!",
|
||||
"download": "Pobierz",
|
||||
"editingFile": "Edytuję plik",
|
||||
"editingFile": "Edytuj plik",
|
||||
"error": "Error while getting files",
|
||||
"fileReadError": "Error odczytu pliku",
|
||||
"files": "Pliki",
|
||||
@ -432,7 +433,7 @@
|
||||
"rename": "Zmień nazwę",
|
||||
"renameItemQuestion": "Jaka ma być nowa nazwa?",
|
||||
"save": "Zapisz",
|
||||
"size": "Włącz zmienianie rozmiaru edytora",
|
||||
"size": "Włącz rozszerzanie i zmniejszanie edytora",
|
||||
"stayHere": "NIE WYCHODŹ Z TEJ STRONY!",
|
||||
"unsupportedLanguage": "Uwaga: To nie jest wspierany typ pliku",
|
||||
"unzip": "Rozpakuj",
|
||||
@ -545,7 +546,7 @@
|
||||
"buildServer": "Zbuduj serwer!",
|
||||
"clickRoot": "Kilknij tutaj aby zaznaczyć główną ścieżkę",
|
||||
"close": "Zamknij",
|
||||
"defaultPort": "25565 podstawowy",
|
||||
"defaultPort": "Domyślnie 25565",
|
||||
"downloading": "Pobieranie serwera...",
|
||||
"explainRoot": "Proszę, kliknij przycisk poniżej aby zaznaczyć główną ścieżkę w tym archiwum",
|
||||
"importServer": "Importuj egzystujący serwer",
|
||||
@ -590,6 +591,15 @@
|
||||
"newServer": "Stwórz nowy serwer",
|
||||
"servers": "Serwery"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "Prawie gotowe! Jeszcze tylko chwilka...",
|
||||
"internals": "Konfigurowanie i włączanie backendu...",
|
||||
"internet": "Sprawdzam połączenie z internetem",
|
||||
"server": "Włączanie ",
|
||||
"serverInit": "Ładuje serwery...",
|
||||
"starting": "Włączam Craftiego...",
|
||||
"tasks": "Włączanie harmonogramu..."
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "Klucze API",
|
||||
"auth": "Autoryzacja? ",
|
||||
@ -640,12 +650,12 @@
|
||||
"edit": "Edytuj",
|
||||
"enabled": "Włączony",
|
||||
"jar_update": "Plik startowy zaktualizowany",
|
||||
"kill": "Serwer zatrzymany",
|
||||
"kill": "Serwer został zabity",
|
||||
"name": "Nazwa",
|
||||
"new": "Nowy Webhook",
|
||||
"newWebhook": "Nowy Webhook",
|
||||
"no-webhook": "Nie posiadasz aktualnie żadnych Webhooków dla tego serwera. Aby dodać webhook kliknij na",
|
||||
"run": "Włącz Webhook",
|
||||
"run": "Przetestuj Webhook",
|
||||
"send_command": "Komenda serwera otrzymana!",
|
||||
"start_server": "Serwer włączony",
|
||||
"stop_server": "Serwer wyłączony",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"starting": "延迟启动",
|
||||
"status": "状态",
|
||||
"stop": "停止",
|
||||
"storage": "存储",
|
||||
"version": "版本",
|
||||
"welcome": "欢迎来到 Crafty Controller"
|
||||
},
|
||||
@ -591,6 +592,15 @@
|
||||
"newServer": "创建新服务器",
|
||||
"servers": "服务器"
|
||||
},
|
||||
"startup": {
|
||||
"almost": "即将完成。请稍候……",
|
||||
"internals": "正在配置并启动 Crafty 的内部组件",
|
||||
"internet": "正在检查网络连接",
|
||||
"server": "正在初始化 ",
|
||||
"serverInit": "正在初始化服务器",
|
||||
"starting": "Crafty 正在启动……",
|
||||
"tasks": "正在启动任务计划器"
|
||||
},
|
||||
"userConfig": {
|
||||
"apiKey": "API 密钥",
|
||||
"auth": "已授权?",
|
||||
|
@ -21,7 +21,7 @@
|
||||
</Branch>
|
||||
<Screenshot>https://wiki.craftycontrol.com/uploads/en/crafty%204%20dashboard%20with%20one%20server.jpeg</Screenshot>
|
||||
<Screenshot>https://wiki.craftycontrol.com/uploads/en/crafty%204%20server%20setup%20details.png</Screenshot>
|
||||
<Overview>Crafty 4 is the next iteration of our Minecraft Server Wrapper / Controller / Launcher. [br]Boasting a clean new look, rebuilt from the ground up. [br] [br] Crafty 4 brings a whole host of new features such as Bedrock support. [br] With SteamCMD support on the way![br] **Default login Credentrails are username: "admin" password: "crafty". ** [br]Crafty 4 is the successor of Crafty Controller. [br]For official support join the Discord server https://discord.gg/9VJPhCE [br] For migration from 3.x please refer to the documentation: https://wiki.craftycontrol.com/en/4/
|
||||
<Overview>Crafty 4 is the next iteration of our Minecraft Server Wrapper / Controller / Launcher. [br]Boasting a clean new look, rebuilt from the ground up. [br] [br] Crafty 4 brings a whole host of new features such as Bedrock support. [br] With SteamCMD support on the way![br] **Default login Credentrails are stored in your Crafty Configuration location in the file default-creds.txt ** [br]Crafty 4 is the successor of Crafty Controller. [br]For official support join the Discord server https://discord.gg/9VJPhCE [br] For migration from 3.x please refer to the documentation: https://wiki.craftycontrol.com/en/4/
|
||||
</Overview>
|
||||
<Category>GameServers: Other:</Category>
|
||||
<WebUI>https://[IP]:[PORT:8443]/</WebUI>
|
||||
|
370
main.py
@ -21,6 +21,18 @@ from app.classes.shared.websocket_manager import WebSocketManager
|
||||
|
||||
console = Console()
|
||||
helper = Helpers()
|
||||
# Get the path our application is running on.
|
||||
if getattr(sys, "frozen", False):
|
||||
APPLICATION_PATH = os.path.dirname(sys.executable)
|
||||
RUNNING_MODE = "Frozen/executable"
|
||||
else:
|
||||
try:
|
||||
app_full_path = os.path.realpath(__file__)
|
||||
APPLICATION_PATH = os.path.dirname(app_full_path)
|
||||
RUNNING_MODE = "Non-interactive (e.g. 'python main.py')"
|
||||
except NameError:
|
||||
APPLICATION_PATH = os.getcwd()
|
||||
RUNNING_MODE = "Interactive"
|
||||
if helper.check_root():
|
||||
Console.critical(
|
||||
"Root detected. Root/Admin access denied. "
|
||||
@ -40,6 +52,7 @@ if not (sys.version_info.major == 3 and sys.version_info.minor >= 9):
|
||||
time.sleep(3)
|
||||
Console.info("Crafty stopped. Exiting...")
|
||||
sys.exit(0)
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
try:
|
||||
from app.classes.models.base_model import database_proxy
|
||||
@ -52,7 +65,180 @@ except ModuleNotFoundError as err:
|
||||
helper.auto_installer_fix(err)
|
||||
|
||||
|
||||
def internet_check():
|
||||
"""
|
||||
This checks to see if the Crafty host is connected to the
|
||||
internet. This will show a warning in the console if no interwebs.
|
||||
"""
|
||||
print()
|
||||
logger.info("Checking Internet. This may take a minute.")
|
||||
Console.info("Checking Internet. This may take a minute.")
|
||||
|
||||
if not helper.check_internet():
|
||||
logger.warning(
|
||||
"We have detected the machine running Crafty has no "
|
||||
"connection to the internet. Client connections to "
|
||||
"the server may be limited."
|
||||
)
|
||||
Console.warning(
|
||||
"We have detected the machine running Crafty has no "
|
||||
"connection to the internet. Client connections to "
|
||||
"the server may be limited."
|
||||
)
|
||||
|
||||
|
||||
def controller_setup():
|
||||
"""
|
||||
Method sets up the software controllers.
|
||||
This also sets the application path as well as the
|
||||
master server dir (if not set).
|
||||
|
||||
This also clears the support logs status.
|
||||
"""
|
||||
if not controller.check_system_user():
|
||||
controller.add_system_user()
|
||||
|
||||
master_server_dir = controller.management.get_master_server_dir()
|
||||
if master_server_dir == "":
|
||||
logger.debug("Could not find master server path. Setting default")
|
||||
controller.set_master_server_dir(
|
||||
os.path.join(controller.project_root, "servers")
|
||||
)
|
||||
else:
|
||||
helper.servers_dir = master_server_dir
|
||||
|
||||
logger.info(f"Execution Mode: {RUNNING_MODE}")
|
||||
logger.info(f"Application path: '{APPLICATION_PATH}'")
|
||||
Console.info(f"Execution Mode: {RUNNING_MODE}")
|
||||
Console.info(f"Application path: '{APPLICATION_PATH}'")
|
||||
|
||||
controller.clear_support_status()
|
||||
|
||||
|
||||
def tasks_starter():
|
||||
"""
|
||||
Method starts stats recording, app scheduler, and
|
||||
serverjars/steamCMD cache refreshers
|
||||
"""
|
||||
# start stats logging
|
||||
tasks_manager.start_stats_recording()
|
||||
|
||||
# once the controller is up and stats are logging, we can kick off
|
||||
# the scheduler officially
|
||||
tasks_manager.start_scheduler()
|
||||
|
||||
# refresh our cache and schedule for every 12 hoursour cache refresh
|
||||
# for serverjars.com
|
||||
tasks_manager.serverjar_cache_refresher()
|
||||
|
||||
|
||||
def signal_handler(signum, _frame):
|
||||
"""
|
||||
Method handles sigterm and shuts the app down.
|
||||
"""
|
||||
if not args.daemon:
|
||||
print() # for newline after prompt
|
||||
signame = signal.Signals(signum).name
|
||||
logger.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
||||
Console.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
||||
tasks_manager._main_graceful_exit()
|
||||
crafty_prompt.universal_exit()
|
||||
|
||||
|
||||
def do_cleanup():
|
||||
"""
|
||||
Checks Crafty's temporary directory and clears it out on boot.
|
||||
"""
|
||||
try:
|
||||
logger.info("Removing old temp dirs")
|
||||
FileHelpers.del_dirs(os.path.join(controller.project_root, "temp"))
|
||||
except:
|
||||
logger.info("Did not find old temp dir.")
|
||||
os.mkdir(os.path.join(controller.project_root, "temp"))
|
||||
|
||||
|
||||
def do_version_check():
|
||||
"""
|
||||
Checks for remote version differences.
|
||||
|
||||
Prints in terminal with differences if true.
|
||||
|
||||
Also sets helper variable to update available when pages
|
||||
are served.
|
||||
"""
|
||||
|
||||
# Check if new version available
|
||||
remote_ver = helper.check_remote_version()
|
||||
if remote_ver:
|
||||
notice = f"""
|
||||
A new version of Crafty is available!
|
||||
{'/' * 37}
|
||||
New version available: {remote_ver}
|
||||
Current version: {pkg_version.parse(helper.get_version_string())}
|
||||
{'/' * 37}
|
||||
"""
|
||||
Console.yellow(notice)
|
||||
|
||||
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
|
||||
|
||||
|
||||
def setup_starter():
|
||||
"""
|
||||
This method starts our setup threads.
|
||||
(tasks scheduler, internet checks, controller setups)
|
||||
|
||||
Once our threads complete we will set our startup
|
||||
variable to false and send a reload to any clients waiting.
|
||||
|
||||
|
||||
"""
|
||||
if not args.daemon:
|
||||
time.sleep(0.01) # Wait for the prompt to start
|
||||
print() # Make a newline after the prompt so logs are on an empty line
|
||||
else:
|
||||
time.sleep(0.01) # Wait for the daemon info message
|
||||
|
||||
Console.info("Setting up Crafty's internal components...")
|
||||
# Start the setup threads
|
||||
web_sock.broadcast("update", {"section": "tasks"})
|
||||
time.sleep(2)
|
||||
tasks_starter_thread.start()
|
||||
web_sock.broadcast("update", {"section": "internet"})
|
||||
time.sleep(2)
|
||||
internet_check_thread.start()
|
||||
web_sock.broadcast(
|
||||
"update",
|
||||
{"section": "internals"},
|
||||
)
|
||||
time.sleep(2)
|
||||
controller_setup_thread.start()
|
||||
|
||||
# Wait for the setup threads to finish
|
||||
web_sock.broadcast(
|
||||
"update",
|
||||
{"section": "almost"},
|
||||
)
|
||||
tasks_starter_thread.join()
|
||||
internet_check_thread.join()
|
||||
controller_setup_thread.join()
|
||||
helper.crafty_starting = False
|
||||
web_sock.broadcast("send_start_reload", "")
|
||||
do_version_check()
|
||||
Console.info("Crafty has fully started and is now ready for use!")
|
||||
|
||||
do_cleanup()
|
||||
|
||||
if not args.daemon:
|
||||
# Put the prompt under the cursor
|
||||
crafty_prompt.print_prompt()
|
||||
|
||||
|
||||
def do_intro():
|
||||
"""
|
||||
Runs the Crafty Controller Terminal Intro with information about the software
|
||||
This method checks for a "settings file" or config.json. If it does not find
|
||||
one it will create one.
|
||||
"""
|
||||
logger.info("***** Crafty Controller Started *****")
|
||||
|
||||
version = helper.get_version_string()
|
||||
@ -73,7 +259,22 @@ def do_intro():
|
||||
|
||||
|
||||
def setup_logging(debug=True):
|
||||
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
|
||||
"""
|
||||
This method sets up our logging for Crafty. It takes
|
||||
one optional (defaulted to True) parameter which
|
||||
determines whether or not the logging level is "debug" or verbose.
|
||||
"""
|
||||
logging_config_file = os.path.join(
|
||||
APPLICATION_PATH, "app", "config", "logging.json"
|
||||
)
|
||||
if not helper.check_file_exists(
|
||||
os.path.join(APPLICATION_PATH, "logs", "auth_tracker.log")
|
||||
):
|
||||
open(
|
||||
os.path.join(APPLICATION_PATH, "logs", "auth_tracker.log"),
|
||||
"a",
|
||||
encoding="utf-8",
|
||||
).close()
|
||||
|
||||
if os.path.exists(logging_config_file):
|
||||
# open our logging config file
|
||||
@ -110,11 +311,11 @@ if __name__ == "__main__":
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
helper.ensure_logging_setup()
|
||||
|
||||
helper.crafty_starting = True
|
||||
# Init WebSocket Manager Here
|
||||
web_sock = WebSocketManager()
|
||||
setup_logging(debug=args.verbose)
|
||||
|
||||
if args.verbose:
|
||||
Console.level = "debug"
|
||||
|
||||
@ -126,20 +327,18 @@ if __name__ == "__main__":
|
||||
|
||||
# print our pretty start message
|
||||
do_intro()
|
||||
|
||||
# our session file, helps prevent multiple controller agents on the same machine.
|
||||
helper.create_session_file(ignore=args.ignore)
|
||||
|
||||
# start the database
|
||||
database = peewee.SqliteDatabase(
|
||||
helper.db_path, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10}
|
||||
)
|
||||
database_proxy.initialize(database)
|
||||
|
||||
migration_manager = MigrationManager(database, helper)
|
||||
migration_manager.up() # Automatically runs migrations
|
||||
|
||||
# do our installer stuff
|
||||
# init classes
|
||||
# now the tables are created, we can load the tasks_manager and server controller
|
||||
user_helper = HelperUsers(database, helper)
|
||||
management_helper = HelpersManagement(database, helper)
|
||||
installer = DatabaseBuilder(database, helper, user_helper, management_helper)
|
||||
@ -153,7 +352,19 @@ if __name__ == "__main__":
|
||||
f"through your router/firewall if you would like to be able "
|
||||
f"to access Crafty remotely."
|
||||
)
|
||||
installer.default_settings()
|
||||
PASSWORD = helper.create_pass()
|
||||
installer.default_settings(PASSWORD)
|
||||
with open(
|
||||
os.path.join(APPLICATION_PATH, "app", "config", "default-creds.txt"),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as cred_file:
|
||||
cred_file.write(
|
||||
json.dumps({"username": "admin", "password": PASSWORD}, indent=4)
|
||||
)
|
||||
os.chmod(
|
||||
os.path.join(APPLICATION_PATH, "app", "config", "default-creds.txt"), 0o600
|
||||
)
|
||||
else:
|
||||
Console.debug("Existing install detected")
|
||||
Console.info("Checking for reset secret flag")
|
||||
@ -164,154 +375,55 @@ if __name__ == "__main__":
|
||||
helper.set_setting("reset_secrets_on_next_boot", False)
|
||||
else:
|
||||
Console.info("No flag found. Secrets are staying")
|
||||
|
||||
# now we've initialized our database for fresh install we
|
||||
# can finishing initializing our controllers/modules
|
||||
file_helper = FileHelpers(helper)
|
||||
import_helper = ImportHelpers(helper, file_helper)
|
||||
# Init WebSocket Manager Here
|
||||
WebSocketManager()
|
||||
# now the tables are created, we can load the tasks_manager and server controller
|
||||
controller = Controller(database, helper, file_helper, import_helper)
|
||||
controller.set_project_root(APPLICATION_PATH)
|
||||
tasks_manager = TasksManager(helper, controller, file_helper)
|
||||
import3 = Import3(helper, controller)
|
||||
migrate_uuid = MigrateUUID(helper, controller)
|
||||
|
||||
# Check to see if client config.json version is different than the
|
||||
# Master config.json in helpers.py
|
||||
Console.info("Checking for remote changes to config.json")
|
||||
controller.get_config_diff()
|
||||
Console.info("Remote change complete.")
|
||||
|
||||
import3 = Import3(helper, controller)
|
||||
migrate_uuid = MigrateUUID(helper, controller)
|
||||
tasks_manager = TasksManager(helper, controller, file_helper)
|
||||
# startup the web server
|
||||
tasks_manager.start_webserver()
|
||||
|
||||
def signal_handler(signum, _frame):
|
||||
if not args.daemon:
|
||||
print() # for newline after prompt
|
||||
signame = signal.Signals(signum).name
|
||||
logger.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
||||
Console.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
||||
tasks_manager._main_graceful_exit()
|
||||
crafty_prompt.universal_exit()
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
# init servers
|
||||
logger.info("Initializing all servers defined")
|
||||
Console.info("Initializing all servers defined")
|
||||
web_sock.broadcast(
|
||||
"update",
|
||||
{"section": "serverInit"},
|
||||
)
|
||||
controller.servers.init_all_servers()
|
||||
|
||||
def tasks_starter():
|
||||
# start stats logging
|
||||
tasks_manager.start_stats_recording()
|
||||
|
||||
# once the controller is up and stats are logging, we can kick off
|
||||
# the scheduler officially
|
||||
tasks_manager.start_scheduler()
|
||||
|
||||
# refresh our cache and schedule for every 12 hoursour cache refresh
|
||||
# for serverjars.com
|
||||
tasks_manager.serverjar_cache_refresher()
|
||||
|
||||
# start up our tasks handler in tasks.py
|
||||
tasks_starter_thread = Thread(target=tasks_starter, name="tasks_starter")
|
||||
|
||||
def internet_check():
|
||||
print()
|
||||
logger.info("Checking Internet. This may take a minute.")
|
||||
Console.info("Checking Internet. This may take a minute.")
|
||||
|
||||
if not helper.check_internet():
|
||||
logger.warning(
|
||||
"We have detected the machine running Crafty has no "
|
||||
"connection to the internet. Client connections to "
|
||||
"the server may be limited."
|
||||
)
|
||||
Console.warning(
|
||||
"We have detected the machine running Crafty has no "
|
||||
"connection to the internet. Client connections to "
|
||||
"the server may be limited."
|
||||
)
|
||||
|
||||
# check to see if instance has internet
|
||||
internet_check_thread = Thread(target=internet_check, name="internet_check")
|
||||
|
||||
def controller_setup():
|
||||
if not controller.check_system_user():
|
||||
controller.add_system_user()
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
application_path = os.path.dirname(sys.executable)
|
||||
running_mode = "Frozen/executable"
|
||||
else:
|
||||
try:
|
||||
app_full_path = os.path.realpath(__file__)
|
||||
application_path = os.path.dirname(app_full_path)
|
||||
running_mode = "Non-interactive (e.g. 'python main.py')"
|
||||
except NameError:
|
||||
application_path = os.getcwd()
|
||||
running_mode = "Interactive"
|
||||
|
||||
controller.set_project_root(application_path)
|
||||
master_server_dir = controller.management.get_master_server_dir()
|
||||
if master_server_dir == "":
|
||||
logger.debug("Could not find master server path. Setting default")
|
||||
controller.set_master_server_dir(
|
||||
os.path.join(controller.project_root, "servers")
|
||||
)
|
||||
else:
|
||||
helper.servers_dir = master_server_dir
|
||||
|
||||
Console.debug(f"Execution Mode: {running_mode}")
|
||||
Console.debug(f"Application path : '{application_path}'")
|
||||
|
||||
controller.clear_support_status()
|
||||
|
||||
# start the Crafty console.
|
||||
crafty_prompt = MainPrompt(
|
||||
helper, tasks_manager, migration_manager, controller, import3, migrate_uuid
|
||||
)
|
||||
|
||||
# set up all controllers
|
||||
controller_setup_thread = Thread(target=controller_setup, name="controller_setup")
|
||||
|
||||
def setup_starter():
|
||||
if not args.daemon:
|
||||
time.sleep(0.01) # Wait for the prompt to start
|
||||
print() # Make a newline after the prompt so logs are on an empty line
|
||||
else:
|
||||
time.sleep(0.01) # Wait for the daemon info message
|
||||
setup_starter_thread = Thread(target=setup_starter, name="setup_starter")
|
||||
|
||||
Console.info("Setting up Crafty's internal components...")
|
||||
|
||||
# Start the setup threads
|
||||
tasks_starter_thread.start()
|
||||
internet_check_thread.start()
|
||||
controller_setup_thread.start()
|
||||
|
||||
# Wait for the setup threads to finish
|
||||
tasks_starter_thread.join()
|
||||
internet_check_thread.join()
|
||||
controller_setup_thread.join()
|
||||
|
||||
Console.info("Crafty has fully started and is now ready for use!")
|
||||
|
||||
# Check if new version available
|
||||
remote_ver = helper.check_remote_version()
|
||||
if remote_ver:
|
||||
notice = f"""
|
||||
A new version of Crafty is available!
|
||||
{'/' * 37}
|
||||
New version available: {remote_ver}
|
||||
Current version: {pkg_version.parse(helper.get_version_string())}
|
||||
{'/' * 37}
|
||||
"""
|
||||
Console.yellow(notice)
|
||||
|
||||
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
|
||||
try:
|
||||
logger.info("Removing old temp dirs")
|
||||
FileHelpers.del_dirs(os.path.join(controller.project_root, "temp"))
|
||||
except:
|
||||
logger.info("Did not find old temp dir.")
|
||||
os.mkdir(os.path.join(controller.project_root, "temp"))
|
||||
|
||||
if not args.daemon:
|
||||
# Put the prompt under the cursor
|
||||
crafty_prompt.print_prompt()
|
||||
|
||||
Thread(target=setup_starter, name="setup_starter").start()
|
||||
setup_starter_thread.start()
|
||||
|
||||
if not args.daemon:
|
||||
# Start the Crafty prompt
|
||||
|
@ -4,13 +4,13 @@ argon2-cffi==23.1.0
|
||||
cached_property==1.5.2
|
||||
colorama==0.4.6
|
||||
croniter==1.4.1
|
||||
cryptography==41.0.4
|
||||
cryptography==41.0.7
|
||||
libgravatar==1.0.4
|
||||
nh3==0.2.14
|
||||
packaging==23.2
|
||||
peewee==3.13
|
||||
psutil==5.9.5
|
||||
pyOpenSSL==23.2.0
|
||||
pyOpenSSL==23.3.0
|
||||
pyjwt==2.8.0
|
||||
PyYAML==6.0.1
|
||||
requests==2.31.0
|
||||
|
@ -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.1
|
||||
sonar.projectVersion=4.2.3
|
||||
sonar.python.version=3.9, 3.10, 3.11
|
||||
sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**
|
||||
|
||||
|