Merge branch 'dev' into refactor/config-json

This commit is contained in:
Zedifus 2023-01-29 20:00:09 +00:00
commit aa2ba4f8cf
41 changed files with 2474 additions and 873 deletions

View File

@ -29,6 +29,7 @@ docker-build-dev:
- docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script:
- |
tag=":$CI_COMMIT_REF_SLUG"
@ -45,6 +46,7 @@ docker-build-dev:
--build-arg "BUILD_REF=${CI_COMMIT_SHA}"
--build-arg "CRAFTY_VER=${VERSION}"
--tag "$CI_REGISTRY_IMAGE${tag}"
--tag "arcadiatechnology/crafty-4${tag}"
--platform linux/arm64/v8,linux/amd64
--push .
after_script:
@ -83,6 +85,7 @@ docker-build-prod:
- docker run --rm --privileged aptman/qus -- -r
- docker run --rm --privileged aptman/qus -s -- -p aarch64 x86_64
- echo $CI_BUILD_TOKEN | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- echo $DOCKERHUB_TOKEN | docker login -u "$DOCKERHUB_USER" --password-stdin $DOCKERHUB_REGISTRY
script:
- |
VERSION="${MAJOR}.${MINOR}.${SUB}"
@ -99,6 +102,8 @@ docker-build-prod:
--build-arg "CRAFTY_VER=${VERSION}"
--tag "$CI_REGISTRY_IMAGE:$VERSION"
--tag "$CI_REGISTRY_IMAGE:latest"
--tag "arcadiatechnology/crafty-4:$VERSION"
--tag "arcadiatechnology/crafty-4:latest"
--platform linux/arm64/v8,linux/amd64
--push .
after_script:

View File

@ -1,7 +1,23 @@
# Changelog
## --- [4.0.19] - 2022/TBD
## --- [4.0.20] - 2022/TBD
### New features
- Add option to run command before backup. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/536))
- Make Config.json editable from panel. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/532))
### Bug fixes
- Fix local java server imports. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/529))
- Fix Schedule Restore | Add Backup Config Preservation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/533))
- Rework `/public` Route. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/538))
### Tweaks
- Hide stats DB directory from files tree. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/530))
- Added further login screen customisation settings. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/531))
- Set backup filename to use same time as schedule. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/534))
- Move Schedules to from DB to Queue Datatype. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/535))
- Move raknet icon failure to a debug log. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/537))
### Lang
TBD
<br><br>
## --- [4.0.19] - 2022/01/07
### Bug fixes
- Fix port tooltip not showing on dash while server online. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/503))
- Fix '+' char in path causing any file operation to fail. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/502))
@ -12,11 +28,18 @@ TBD
- Fix root dir selection in Upload Zip Import ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/508))
- Fix stats error on mac M1 chips ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/512))
- Fix window path escape on java override ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/513))
- Fix Forge import stalling on 1.17 Forge servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/515))
- Fix issue with server config for SU Accounts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/516))
- Fix Nested reaction tasks ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/521))
- Remove legacy unzip code causing issues with single file zip files ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/522))
### Tweaks
- Make server directories non-configurable ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/511))
- Add popover to server port to detail it's purpose ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/514))
- Add server start timeout w/ WS Warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/518))
- Replace google ping for ntp for internet checks in locked-down countries ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/524))
- Add pushing to DockerHub registry (`arcadiatechnology/crafty-4`) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/526))
### Lang
TBD
- Added Czech translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/519))
<br><br>
## --- [4.0.17/4.0.18] - 2022/11/30

View File

@ -2,7 +2,7 @@
*Don't Panic!*<br><br>
First off, thank you for choosing Crafty Controller! <br>
We hope you've been enjoying the beta so far and are absolutely thrilled that you are looking to contribute!
We hope you've been enjoying Crafty so far and are absolutely thrilled that you are looking to contribute!
The following guide will show you how to easily and safely contribute to our current workflow. There are a few components that need to be taken into account and processes that need followed before we can merge your code into our repository.
<br><br>

View File

@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
# Crafty Controller 4.0.19
# Crafty Controller 4.0.20
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?
@ -48,7 +48,7 @@ As the Dockerfile uses the permission structure of `crafty:root` **internally**
### - Using the registry image 🌎
The provided image supports both `arm64` and `amd64` out the box, if you have issues though you can build it yourself with the `compose` file in `docker/`.
The image is located at: `registry.gitlab.com/crafty-controller/crafty-4:latest`
The image is located at: `registry.gitlab.com/crafty-controller/crafty-4:latest` or `arcadiatechnology/crafty-4`
| Branch | Status |
| ----------------- | ------------------------------------------------------------------ |
| :latest | [![pipeline status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master) |

View File

@ -1,4 +1,5 @@
import logging
import queue
from app.classes.models.management import HelpersManagement
from app.classes.models.servers import HelperServers
@ -9,6 +10,26 @@ logger = logging.getLogger(__name__)
class ManagementController:
def __init__(self, management_helper):
self.management_helper = management_helper
self.command_queue = queue.Queue()
# **********************************************************************************
# Config Methods
# **********************************************************************************
@staticmethod
def set_login_image(path):
HelpersManagement.set_login_image(path)
@staticmethod
def get_login_image():
return HelpersManagement.get_login_image()
@staticmethod
def set_login_opacity(opacity):
return HelpersManagement.set_login_opacity(opacity)
@staticmethod
def get_login_opacity():
return HelpersManagement.get_login_opacity()
# **********************************************************************************
# Host_Stats Methods
@ -28,9 +49,6 @@ class ManagementController:
# **********************************************************************************
# Commands Methods
# **********************************************************************************
@staticmethod
def get_unactioned_commands():
return HelpersManagement.get_unactioned_commands()
def send_command(self, user_id, server_id, remote_ip, command):
server_name = HelperServers.get_server_friendly_name(server_id)
@ -42,11 +60,12 @@ class ManagementController:
server_id,
remote_ip,
)
HelpersManagement.add_command(server_id, user_id, remote_ip, command)
self.queue_command(
{"server_id": server_id, "user_id": user_id, "command": command}
)
@staticmethod
def mark_command_complete(command_id=None):
return HelpersManagement.mark_command_complete(command_id)
def queue_command(self, command_data):
self.command_queue.put(command_data)
# **********************************************************************************
# Audit_Log Methods
@ -78,6 +97,10 @@ class ManagementController:
command,
name,
enabled=True,
one_time=False,
cron_string="* * * * *",
parent=None,
delay=0,
):
return HelpersManagement.create_scheduled_task(
server_id,
@ -88,20 +111,16 @@ class ManagementController:
command,
name,
enabled,
one_time,
cron_string,
parent,
delay,
)
@staticmethod
def delete_scheduled_task(schedule_id):
return HelpersManagement.delete_scheduled_task(schedule_id)
@staticmethod
def set_login_image(path):
HelpersManagement.set_login_image(path)
@staticmethod
def get_login_image():
return HelpersManagement.get_login_image()
@staticmethod
def update_scheduled_task(schedule_id, updates):
return HelpersManagement.update_scheduled_task(schedule_id, updates)
@ -145,9 +164,18 @@ class ManagementController:
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
before: str = "",
after: str = "",
):
return self.management_helper.set_backup_config(
server_id, backup_path, max_backups, excluded_dirs, compress, shutdown
server_id,
backup_path,
max_backups,
excluded_dirs,
compress,
shutdown,
before,
after,
)
@staticmethod

View File

@ -192,7 +192,7 @@ class ServerJars:
with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output)
# If this is the newer forge version we will run the installer
if server == "forge" and int(version.split(".")[1]) > 15:
if server == "forge":
ServersController.finish_import(server_id, True)
else:
ServersController.finish_import(server_id)

View File

@ -300,7 +300,7 @@ class Stats:
server_icon = base64.encodebytes(ping_obj["icon"])
except Exception as e:
server_icon = False
logger.info(
logger.debug(
"Unable to read the server icon due to the following error:", exc_info=e
)
ping_data = {

View File

@ -13,7 +13,7 @@ from peewee import (
from playhouse.shortcuts import model_to_dict
from app.classes.models.base_model import BaseModel
from app.classes.models.users import Users, HelperUsers
from app.classes.models.users import HelperUsers
from app.classes.models.servers import Servers
from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.main_models import DatabaseShortcuts
@ -44,6 +44,7 @@ class AuditLog(BaseModel):
class CraftySettings(BaseModel):
secret_api_key = CharField(default="")
login_photo = CharField(default="login_1.jpg")
login_opacity = IntegerField(default=100)
class Meta:
table_name = "crafty_settings"
@ -68,22 +69,6 @@ class HostStats(BaseModel):
table_name = "host_stats"
# **********************************************************************************
# Commands Class
# **********************************************************************************
class Commands(BaseModel):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref="server", index=True)
user = ForeignKeyField(Users, backref="user", index=True)
source_ip = CharField(default="127.0.0.1")
command = CharField(default="")
executed = BooleanField(default=False)
class Meta:
table_name = "commands"
# **********************************************************************************
# Webhooks Class
# **********************************************************************************
@ -131,6 +116,8 @@ class Backups(BaseModel):
server_id = ForeignKeyField(Servers, backref="backups_server")
compress = BooleanField(default=False)
shutdown = BooleanField(default=False)
before = CharField(default="")
after = CharField(default="")
class Meta:
table_name = "backups"
@ -150,33 +137,6 @@ class HelpersManagement:
query = HostStats.select().order_by(HostStats.id.desc()).get()
return model_to_dict(query)
# **********************************************************************************
# Commands Methods
# **********************************************************************************
@staticmethod
def add_command(server_id, user_id, remote_ip, command):
Commands.insert(
{
Commands.server_id: server_id,
Commands.user: user_id,
Commands.source_ip: remote_ip,
Commands.command: command,
}
).execute()
@staticmethod
def get_unactioned_commands():
query = Commands.select().where(Commands.executed == 0)
return query
@staticmethod
def mark_command_complete(command_id=None):
if command_id is not None:
logger.debug(f"Marking Command {command_id} completed")
Commands.update({Commands.executed: True}).where(
Commands.command_id == command_id
).execute()
# **********************************************************************************
# Audit_Log Methods
# **********************************************************************************
@ -255,6 +215,9 @@ class HelpersManagement:
)
return settings[0].secret_api_key
# **********************************************************************************
# Config Methods
# **********************************************************************************
@staticmethod
def get_login_image():
settings = CraftySettings.select(CraftySettings.login_photo).where(
@ -268,6 +231,19 @@ class HelpersManagement:
CraftySettings.id == 1
).execute()
@staticmethod
def get_login_opacity():
settings = CraftySettings.select(CraftySettings.login_opacity).where(
CraftySettings.id == 1
)
return settings[0].login_opacity
@staticmethod
def set_login_opacity(opacity):
CraftySettings.update({CraftySettings.login_opacity: opacity}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
# Schedules Methods
# **********************************************************************************
@ -369,6 +345,8 @@ class HelpersManagement:
"server_id": row.server_id_id,
"compress": row.compress,
"shutdown": row.shutdown,
"before": row.before,
"after": row.after,
}
except IndexError:
conf = {
@ -378,6 +356,8 @@ class HelpersManagement:
"server_id": server_id,
"compress": False,
"shutdown": False,
"before": "",
"after": "",
}
return conf
@ -393,6 +373,8 @@ class HelpersManagement:
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
before: str = "",
after: str = "",
):
logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).exists():
@ -405,6 +387,8 @@ class HelpersManagement:
"server_id": server_id,
"compress": False,
"shutdown": False,
"before": "",
"after": "",
}
new_row = True
if max_backups is not None:
@ -414,6 +398,8 @@ class HelpersManagement:
conf["excluded_dirs"] = dirs_to_exclude
conf["compress"] = compress
conf["shutdown"] = shutdown
conf["before"] = before
conf["after"] = after
if not new_row:
with self.database.atomic():
if backup_path is not None:
@ -473,9 +459,3 @@ class HelpersManagement:
f"Not removing {dir_to_del} from excluded directories - "
f"not in the excluded directory list for server ID {server_id}"
)
@staticmethod
def clear_unexecuted_commands():
Commands.update({Commands.executed: True}).where(
Commands.executed == False # pylint: disable=singleton-comparison
).execute()

View File

@ -298,14 +298,7 @@ class FileHelpers:
try:
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(temp_dir)
for i in enumerate(zip_ref.filelist):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
i
].filename.endswith("/"):
break
full_root_path = temp_dir
full_root_path = temp_dir
for item in os.listdir(full_root_path):
if os.path.isdir(os.path.join(full_root_path, item)):
try:

View File

@ -78,6 +78,7 @@ class Helpers:
self.websocket_helper = WebSocketHelper(self)
self.translation = Translation(self)
self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"]
@staticmethod
def auto_installer_fix(ex):
@ -272,7 +273,7 @@ class Helpers:
@staticmethod
def check_internet():
try:
requests.get("https://google.com", timeout=1)
requests.get("https://ntp.org", timeout=1)
return True
except Exception:
return False
@ -1006,8 +1007,7 @@ class Helpers:
return data
@staticmethod
def generate_tree(folder, output=""):
def generate_tree(self, folder, output=""):
dir_list = []
unsorted_files = []
file_list = os.listdir(folder)
@ -1024,17 +1024,18 @@ class Helpers:
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if os.path.isdir(rel):
output += f"""<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">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
{filename}
</span>
</div><li>
\n"""
if filename not in self.ignored_names:
output += f"""<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">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
{filename}
</span>
</div><li>
\n"""
else:
if filename != "crafty_managed.txt":
if filename not in self.ignored_names:
output += f"""<li
class="d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}"
@ -1043,8 +1044,7 @@ class Helpers:
<i class="far fa-file"></i></span>{filename}</li>"""
return output
@staticmethod
def generate_dir(folder, output=""):
def generate_dir(self, folder, output=""):
dir_list = []
unsorted_files = []
@ -1063,16 +1063,17 @@ class Helpers:
dpath = os.path.join(folder, filename)
rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel):
output += f"""<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">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
{filename}
</span>
</div><li>"""
if filename not in self.ignored_names:
output += f"""<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">
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
<i style="color: var(--info);" class="far fa-folder"></i>
<i style="color: var(--info);" class="far fa-folder-open"></i>
{filename}
</span>
</div><li>"""
else:
if filename != "crafty_managed.txt":
if filename not in self.ignored_names:
output += f"""<li
class="d-block tree-ctx-item tree-file tree-item"
data-path="{dpath}"

View File

@ -843,6 +843,7 @@ class Controller:
user_id,
server_type="minecraft-bedrock",
)
ServersController.set_import(new_id)
self.import_helper.import_bedrock_zip_server(
temp_dir, new_server_dir, full_jar_path, port, new_id
)
@ -992,10 +993,6 @@ class Controller:
# remove the server from the DB
self.servers.remove_server(server_id)
@staticmethod
def clear_unexecuted_commands():
HelpersManagement.clear_unexecuted_commands()
@staticmethod
def clear_support_status():
HelperUsers.clear_support_status()

View File

@ -10,6 +10,7 @@ import logging.config
import subprocess
import html
import urllib.request
import glob
# TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
@ -130,13 +131,13 @@ class ServerInstance:
self.stats_helper = HelperServerStats(self.server_id)
self.last_backup_failed = False
try:
tz = get_localzone()
self.tz = get_localzone()
except ZoneInfoNotFoundError:
logger.error(
"Could not capture time zone from system. Falling back to Europe/London"
)
tz = "Europe/London"
self.server_scheduler = BackgroundScheduler(timezone=str(tz))
self.tz = "Europe/London"
self.server_scheduler = BackgroundScheduler(timezone=str(self.tz))
self.server_scheduler.start()
self.backup_thread = threading.Thread(
target=self.a_backup_server, daemon=True, name=f"backup_{self.name}"
@ -174,7 +175,6 @@ class ServerInstance:
self.name = server_name
self.settings = server_data_obj
self.stats_helper.init_database(server_id)
self.record_server_stats()
# build our server run command
@ -580,54 +580,105 @@ class ServerInstance:
# Process has exited. Lets do some work to setup the new
# run command.
# Let's grab the server object we're going to update.
server_obj = HelperServers.get_server_obj(self.server_id)
server_obj: Servers = HelperServers.get_server_obj(self.server_id)
# The forge install is done so we can delete that install file.
os.remove(os.path.join(server_obj.path, server_obj.executable))
# We need to grab the exact forge version number.
# We know we can find it here in the run.sh/bat script.
run_file_path = ""
if self.helper.is_os_windows():
run_file_path = os.path.join(server_obj.path, "run.bat")
else:
run_file_path = os.path.join(server_obj.path, "run.sh")
try:
if Helpers.check_file_perms(run_file_path) and os.path.isfile(
run_file_path
):
run_file = open(run_file_path, "r", encoding="utf-8")
run_file_text = run_file.read()
else:
Console.error(
"ERROR ! Forge install can't read the scripts files."
" Aborting ..."
# Getting the forge version from the executable command
version = re.findall(
r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
server_obj.execution_command,
)
return
version_param = version[0][0].split(".")
version_major = int(version_param[0])
version_minor = int(version_param[1])
# We get the server command parameters from forge script
server_command = re.findall(
r"java @([a-zA-Z0-9_\.]+)"
r" @([a-z.\/\-]+)([0-9.\-]+)\/\b([a-z_0-9]+\.txt)\b( .{2,4})?",
run_file_text,
)[0]
# Checking which version we are with
if version_major <= 1 and version_minor < 17:
# OLD VERSION < 1.17
version = server_command[2]
executable_path = f"{server_command[1]}{server_command[2]}/"
# Retrieving the executable jar filename
file_path = glob.glob(
f"{server_obj.path}/forge-{version[0][0]}*.jar"
)[0]
file_name = re.findall(
r"(forge[-0-9.]+.jar)",
file_path,
)[0]
# Let's set the proper server executable
server_obj.executable = os.path.join(
f"{executable_path}forge-{version}-server.jar"
)
# Now lets set up the new run command.
# This is based off the run.sh/bat that
# Forge uses in 1.16 and <
execution_command = (
f"java @{server_command[0]}"
f" @{executable_path}{server_command[3]} nogui {server_command[4]}"
)
server_obj.execution_command = execution_command
Console.debug("SUCCESS! Forge install completed")
# Let's set the proper server executable
server_obj.executable = os.path.join(file_name)
# Get memory values
memory_values = re.findall(
r"-Xms([A-Z0-9\.]+) -Xmx([A-Z0-9\.]+)",
server_obj.execution_command,
)
# Now lets set up the new run command.
# This is based off the run.sh/bat that
# Forge uses in 1.17 and <
execution_command = (
f"java -Xms{memory_values[0][0]} -Xmx{memory_values[0][1]}"
f' -jar "{file_name}" nogui'
)
server_obj.execution_command = execution_command
Console.debug("SUCCESS! Forge install completed")
else:
# NEW VERSION >= 1.17
run_file_path = ""
if self.helper.is_os_windows():
run_file_path = os.path.join(server_obj.path, "run.bat")
else:
run_file_path = os.path.join(server_obj.path, "run.sh")
if Helpers.check_file_perms(run_file_path) and os.path.isfile(
run_file_path
):
run_file = open(run_file_path, "r", encoding="utf-8")
run_file_text = run_file.read()
else:
Console.error(
"ERROR ! Forge install can't read the scripts files."
" Aborting ..."
)
return
# We get the server command parameters from forge script
server_command = re.findall(
r"java @([a-zA-Z0-9_\.]+)"
r" @([a-z.\/\-]+)([0-9.\-]+)"
r"\/\b([a-z_0-9]+\.txt)\b( .{2,4})?",
run_file_text,
)[0]
version = server_command[2]
executable_path = f"{server_command[1]}{server_command[2]}/"
# Let's set the proper server executable
server_obj.executable = os.path.join(
f"{executable_path}forge-{version}-server.jar"
)
# Now lets set up the new run command.
# This is based off the run.sh/bat that
# Forge uses in 1.17 and <
execution_command = (
f"java @{server_command[0]}"
f" @{executable_path}{server_command[3]} nogui"
" {server_command[4]}"
)
server_obj.execution_command = execution_command
Console.debug("SUCCESS! Forge install completed")
except:
logger.debug("Could not find run file.")
# TODO Use regex to get version and rebuild simple execution
# We'll update the server with the new information now.
HelperServers.update_server(server_obj)
@ -972,7 +1023,17 @@ class ServerInstance:
)
time.sleep(3)
conf = HelpersManagement.get_backup_config(self.server_id)
if conf["before"]:
if self.check_running():
logger.debug(
"Found running server and send command option. Sending command"
)
self.send_command(conf["before"])
if conf["shutdown"]:
if conf["before"]:
# pause to let people read message.
time.sleep(5)
logger.info(
"Found shutdown preference. Delaying"
+ "backup start. Shutting down server."
@ -985,7 +1046,7 @@ class ServerInstance:
try:
backup_filename = (
f"{self.settings['backup_path']}/"
f"{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
f"{datetime.datetime.now().astimezone(self.tz).strftime('%Y-%m-%d_%H-%M-%S')}" # pylint: disable=line-too-long
)
logger.info(
f"Creating backup of server '{self.settings['server_name']}'"
@ -1053,6 +1114,14 @@ class ServerInstance:
self.run_threaded_server(HelperUsers.get_user_id_by_name("system"))
time.sleep(3)
self.last_backup_failed = False
if conf["after"]:
if self.check_running():
logger.debug(
"Found running server and send command option. Sending command"
)
self.send_command(conf["after"])
# pause to let people read message.
time.sleep(5)
except:
logger.exception(
f"Failed to create backup of server {self.name} (ID {self.server_id})"

View File

@ -91,22 +91,21 @@ class TasksManager:
def command_watcher(self):
while True:
# select any commands waiting to be processed
commands = HelpersManagement.get_unactioned_commands()
for cmd in commands:
if not self.controller.management.command_queue.empty():
cmd = self.controller.management.command_queue.get()
try:
svr = self.controller.servers.get_server_instance_by_id(
cmd.server_id.server_id
cmd["server_id"]
)
except:
logger.error(
"Server value requested does not exist! "
"Purging item from waiting commands."
)
HelpersManagement.mark_command_complete(cmd.command_id)
continue
user_id = cmd.user_id
command = cmd.command
user_id = cmd["user_id"]
command = cmd["command"]
if command == "start_server":
svr.run_threaded_server(user_id)
@ -136,8 +135,6 @@ class TasksManager:
else:
svr.send_command(command)
HelpersManagement.mark_command_complete(cmd.command_id)
time.sleep(1)
def _main_graceful_exit(self):
@ -212,16 +209,19 @@ class TasksManager:
if schedule.cron_string != "":
try:
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
CronTrigger.from_crontab(
schedule.cron_string, timezone=str(self.tz)
),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
{
"server_id": schedule.server_id.server_id,
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": schedule.command,
}
],
)
except Exception as e:
@ -237,45 +237,54 @@ class TasksManager:
else:
if schedule.interval_type == "hours":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute=0,
hour="*/" + str(schedule.interval),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
{
"server_id": schedule.server_id.server_id,
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": schedule.command,
}
],
)
elif schedule.interval_type == "minutes":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute="*/" + str(schedule.interval),
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
{
"server_id": schedule.server_id.server_id,
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": schedule.command,
}
],
)
elif schedule.interval_type == "days":
curr_time = schedule.start_time.split(":")
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
day="*/" + str(schedule.interval),
hour=curr_time[0],
minute=curr_time[1],
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
{
"server_id": schedule.server_id.server_id,
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": schedule.command,
}
],
)
if new_job != "error":
@ -322,16 +331,19 @@ class TasksManager:
if job_data["cron_string"] != "":
try:
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
except Exception as e:
@ -345,45 +357,54 @@ class TasksManager:
else:
if job_data["interval_type"] == "hours":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute=0,
hour="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
elif job_data["interval_type"] == "minutes":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
day="*/" + str(job_data["interval"]),
hour=curr_time[0],
minute=curr_time[1],
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
logger.info("Added job. Current enabled schedules: ")
@ -460,16 +481,19 @@ class TasksManager:
if job_data["cron_string"] != "":
try:
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
CronTrigger.from_crontab(
job_data["cron_string"], timezone=str(self.tz)
),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
except Exception as e:
@ -480,45 +504,54 @@ class TasksManager:
else:
if job_data["interval_type"] == "hours":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute=0,
hour="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
elif job_data["interval_type"] == "minutes":
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
minute="*/" + str(job_data["interval"]),
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
elif job_data["interval_type"] == "days":
curr_time = job_data["start_time"].split(":")
new_job = self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"cron",
day="*/" + str(job_data["interval"]),
hour=curr_time[0],
minute=curr_time[1],
id=str(sch_id),
args=[
job_data["server_id"],
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
job_data["command"],
{
"server_id": job_data["server_id"],
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": job_data["command"],
}
],
)
if new_job != "error":
@ -556,7 +589,7 @@ class TasksManager:
if task.one_time:
self.remove_job(task.schedule_id)
logger.info("one time task detected. Deleting...")
else:
elif task.interval_type != "reaction":
self.controller.management.update_scheduled_task(
task.schedule_id,
{
@ -579,15 +612,18 @@ class TasksManager:
seconds=schedule.delay
)
self.scheduler.add_job(
HelpersManagement.add_command,
self.controller.management.queue_command,
"date",
run_date=delaytime,
id=str(schedule.schedule_id),
args=[
schedule.server_id,
self.users_controller.get_id_by_name("system"),
"127.0.0.1",
schedule.command,
{
"server_id": schedule.server_id.server_id,
"user_id": self.users_controller.get_id_by_name(
"system"
),
"command": schedule.command,
}
],
)
else:

View File

@ -348,14 +348,11 @@ class AjaxHandler(BaseHandler):
server.backup_server()
elif page == "clear_comms":
if exec_user["superuser"]:
self.controller.clear_unexecuted_commands()
return
elif page == "select_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
photo = urllib.parse.unquote(self.get_argument("photo", ""))
opacity = self.get_argument("opacity", 100)
self.controller.management.set_login_opacity(int(opacity))
if photo == "login_1.jpg":
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = f"{photo}"
@ -366,7 +363,7 @@ class AjaxHandler(BaseHandler):
elif page == "delete_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
photo = urllib.parse.unquote(self.get_argument("photo", None))
if photo and photo != "login_1.jpg":
os.remove(
os.path.join(
@ -440,15 +437,8 @@ class AjaxHandler(BaseHandler):
for schedule in self.controller.management.get_schedules_by_server(
server_id
):
self.controller.management.create_scheduled_task(
new_server_id,
schedule.action,
schedule.interval,
schedule.interval_type,
schedule.start_time,
schedule.command,
schedule.name,
schedule.enabled,
self.tasks_manager.update_job(
schedule.schedule_id, {"server_id": new_server_id}
)
# preserve execution command
new_server_obj = self.controller.servers.get_server_obj(
@ -456,6 +446,29 @@ class AjaxHandler(BaseHandler):
)
new_server_obj.execution_command = server_data["execution_command"]
self.controller.servers.update_server(new_server_obj)
# preserve backup config
backup_config = self.controller.management.get_backup_config(
server_id
)
excluded_dirs = []
server_obj = self.controller.servers.get_server_obj(server_id)
loop_backup_path = self.helper.wtol_path(server_obj.path)
for item in self.controller.management.get_excluded_backup_dirs(
server_id
):
item_path = self.helper.wtol_path(item)
bu_path = os.path.relpath(item_path, loop_backup_path)
bu_path = os.path.join(new_server_obj.path, bu_path)
excluded_dirs.append(bu_path)
self.controller.management.set_backup_config(
new_server_id,
new_server_obj.backup_path,
backup_config["max_backups"],
excluded_dirs,
backup_config["compress"],
backup_config["shutdown"],
)
# remove old server's tasks
try:
self.tasks_manager.remove_all_server_tasks(server_id)
@ -484,15 +497,8 @@ class AjaxHandler(BaseHandler):
for schedule in self.controller.management.get_schedules_by_server(
server_id
):
self.controller.management.create_scheduled_task(
new_server_id,
schedule.action,
schedule.interval,
schedule.interval_type,
schedule.start_time,
schedule.command,
schedule.name,
schedule.enabled,
self.tasks_manager.update_job(
schedule.schedule_id, {"server_id": new_server_id}
)
# preserve execution command
new_server_obj = self.controller.servers.get_server_obj(
@ -500,6 +506,29 @@ class AjaxHandler(BaseHandler):
)
new_server_obj.execution_command = server_data["execution_command"]
self.controller.servers.update_server(new_server_obj)
# preserve backup config
backup_config = self.controller.management.get_backup_config(
server_id
)
excluded_dirs = []
server_obj = self.controller.servers.get_server_obj(server_id)
loop_backup_path = self.helper.wtol_path(server_obj.path)
for item in self.controller.management.get_excluded_backup_dirs(
server_id
):
item_path = self.helper.wtol_path(item)
bu_path = os.path.relpath(item_path, loop_backup_path)
bu_path = os.path.join(new_server_obj.path, bu_path)
excluded_dirs.append(bu_path)
self.controller.management.set_backup_config(
new_server_id,
new_server_obj.backup_path,
backup_config["max_backups"],
excluded_dirs,
backup_config["compress"],
backup_config["shutdown"],
)
try:
self.tasks_manager.remove_all_server_tasks(server_id)
except:

View File

@ -17,6 +17,5 @@ class DefaultHandler(BaseHandler):
)
else:
self.redirect(
"/public/login",
# translate=self.translator.translate,
"/login",
)

View File

@ -100,7 +100,7 @@ class FileHandler(BaseHandler):
self.write(
Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_tree(path)
+ self.helper.generate_tree(path)
)
self.finish()
@ -121,7 +121,7 @@ class FileHandler(BaseHandler):
self.write(
Helpers.get_os_understandable_path(path)
+ "\n"
+ Helpers.generate_dir(path)
+ self.helper.generate_dir(path)
)
self.finish()

View File

@ -291,6 +291,7 @@ class PanelHandler(BaseHandler):
# todo: make this actually pull and compare version data
"update_available": self.helper.update_available,
"background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
"serverTZ": tz,
"version_data": self.helper.get_version_string(),
"failed_servers": self.controller.servers.failed_servers,
@ -857,32 +858,6 @@ class PanelHandler(BaseHandler):
page_data["roles"] = self.controller.roles.get_all_roles()
page_data["auth-servers"][user.user_id] = super_auth_servers
page_data["managed_users"] = []
page_data["backgrounds"] = []
cached_split = self.controller.cached_login.split("/")
if len(cached_split) == 1:
page_data["backgrounds"].append(
self.controller.cached_login
)
else:
page_data["backgrounds"].append(cached_split[1])
if "login_1.jpg" not in page_data["backgrounds"]:
page_data["backgrounds"].append("login_1.jpg")
self.helper.ensure_dir_exists(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
)
for item in os.listdir(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
):
if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item)
page_data["background"] = self.controller.cached_login
else:
page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"]
@ -895,8 +870,65 @@ class PanelHandler(BaseHandler):
exec_user["user_id"]
)
page_data["active_link"] = "panel_config"
template = "panel/panel_config.html"
elif page == "config_json":
if exec_user["superuser"]:
with open(self.helper.settings_file, "r", encoding="utf-8") as f:
data = json.load(f)
page_data["config-json"] = data
page_data["availables_languages"] = []
page_data["all_languages"] = []
for file in sorted(
os.listdir(
os.path.join(self.helper.root_dir, "app", "translations")
)
):
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
page_data["availables_languages"].append(file.split(".")[0])
page_data["all_languages"].append(file.split(".")[0])
page_data["active_link"] = "config_json"
template = "panel/config_json.html"
elif page == "custom_login":
if exec_user["superuser"]:
page_data["backgrounds"] = []
cached_split = self.controller.cached_login.split("/")
if len(cached_split) == 1:
page_data["backgrounds"].append(self.controller.cached_login)
else:
page_data["backgrounds"].append(cached_split[1])
if "login_1.jpg" not in page_data["backgrounds"]:
page_data["backgrounds"].append("login_1.jpg")
self.helper.ensure_dir_exists(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
)
for item in os.listdir(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
):
if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item)
page_data["background"] = self.controller.cached_login
page_data[
"login_opacity"
] = self.controller.management.get_login_opacity()
page_data["active_link"] = "custom_login"
template = "panel/custom_login.html"
elif page == "add_user":
page_data["new_user"] = True
page_data["user"] = {}
@ -953,7 +985,9 @@ class PanelHandler(BaseHandler):
os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
):
if file.endswith(".json"):
if file not in self.helper.get_setting("disabled_language_files"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
if file != str(page_data["languages"][0] + ".json"):
page_data["languages"].append(file.split(".")[0])
@ -1164,7 +1198,9 @@ class PanelHandler(BaseHandler):
os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
):
if file.endswith(".json"):
if file not in self.helper.get_setting("disabled_language_files"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
):
if file != str(page_data["languages"][0] + ".json"):
page_data["languages"].append(file.split(".")[0])
@ -1521,10 +1557,6 @@ class PanelHandler(BaseHandler):
server_obj = self.controller.servers.get_server_obj(server_id)
shutdown_timeout = self.get_argument("shutdown_timeout", 60)
if superuser:
server_path = self.get_argument("server_path", None)
if Helpers.is_os_windows():
server_path.replace(" ", "^ ")
server_path = Helpers.wtol_path(server_path)
log_path = self.get_argument("log_path", "")
if log_path:
if Helpers.is_os_windows():
@ -1613,7 +1645,7 @@ class PanelHandler(BaseHandler):
server_obj.shutdown_timeout = shutdown_timeout
if superuser:
if Helpers.validate_traversal(
self.helper.get_servers_root_dir(), server_path
self.helper.get_servers_root_dir(), server_obj.path
):
server_obj.log_path = log_path
if Helpers.validate_traversal(
@ -1682,6 +1714,8 @@ class PanelHandler(BaseHandler):
compress = self.get_argument("compress", False)
shutdown = self.get_argument("shutdown", False)
check_changed = self.get_argument("changed")
before = self.get_argument("backup_before", "")
after = self.get_argument("backup_after", "")
if str(check_changed) == str(1):
checked = self.get_body_arguments("root_path")
else:
@ -1705,6 +1739,8 @@ class PanelHandler(BaseHandler):
excluded_dirs=checked,
compress=bool(compress),
shutdown=bool(shutdown),
before=before,
after=after,
)
self.controller.management.add_to_audit_log(
@ -1716,6 +1752,38 @@ class PanelHandler(BaseHandler):
self.tasks_manager.reload_schedule_from_db()
self.redirect(f"/panel/server_detail?id={server_id}&subpage=backup")
elif page == "config_json":
try:
data = {}
with open(self.helper.settings_file, "r", encoding="utf-8") as f:
keys = json.load(f).keys()
this_uuid = self.get_argument("uuid")
for key in keys:
arg_data = self.get_argument(key)
if arg_data.startswith(this_uuid):
arg_data = arg_data.split(",")
arg_data.pop(0)
data[key] = arg_data
else:
try:
data[key] = int(arg_data)
except:
if arg_data == "True":
data[key] = True
elif arg_data == "False":
data[key] = False
else:
data[key] = arg_data
with open(self.helper.settings_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
except Exception as e:
logger.critical(
"Config File Error: Unable to read "
f"{self.helper.settings_file} due to {e}"
)
self.redirect("/panel/config_json")
if page == "new_schedule":
server_id = self.check_server_id()
if not server_id:

View File

@ -40,6 +40,7 @@ class PublicHandler(BaseHandler):
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"query": "",
"background": self.controller.cached_login,
"login_opacity": self.controller.management.get_login_opacity(),
}
if self.request.query:
@ -61,15 +62,15 @@ class PublicHandler(BaseHandler):
self.clear_cookie("token")
# self.clear_cookie("user")
# self.clear_cookie("user_data")
self.redirect("/public/login")
self.redirect("/login")
return
# if we have no page, let's go to login
else:
if self.request.query:
self.redirect("/public/login?" + self.request.query)
self.redirect("/login?" + self.request.query)
else:
self.redirect("/public/login")
self.redirect("/login")
return
self.render(
@ -96,9 +97,9 @@ class PublicHandler(BaseHandler):
if page == "login":
next_page = "/public/login"
next_page = "/login"
if self.request.query:
next_page = "/public/login?" + self.request.query
next_page = "/login?" + self.request.query
entered_username = bleach.clean(self.get_argument("username"))
entered_password = bleach.clean(self.get_argument("password"))
@ -113,11 +114,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data")
self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/public/login?error_msg={error_msg}")
self.redirect(f"/login?error_msg={error_msg}")
return
# if we don't have a user
@ -127,11 +126,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data")
self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/public/login?error_msg={error_msg}")
self.redirect(f"/login?error_msg={error_msg}")
return
# if they are disabled
@ -144,11 +141,9 @@ class PublicHandler(BaseHandler):
# self.clear_cookie("user_data")
self.clear_cookie("token")
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/public/login?error_msg={error_msg}")
self.redirect(f"/login?error_msg={error_msg}")
return
login_result = self.helper.verify_pass(entered_password, user_data.password)
@ -187,13 +182,11 @@ class PublicHandler(BaseHandler):
user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
)
if self.request.query:
self.redirect(
f"/public/login?error_msg={error_msg}&{self.request.query}"
)
self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
else:
self.redirect(f"/public/login?error_msg={error_msg}")
self.redirect(f"/login?error_msg={error_msg}")
else:
if self.request.query:
self.redirect("/public/login?" + self.request.query)
self.redirect("/login?" + self.request.query)
else:
self.redirect("/public/login")
self.redirect("/login")

View File

@ -331,7 +331,7 @@ class ServerHandler(BaseHandler):
return
if import_type == "import_jar":
if not self.helper.is_subdir(
if self.helper.is_subdir(
import_server_path, self.controller.project_root
):
self.redirect(

View File

@ -147,7 +147,6 @@ class Webserver:
}
handlers = [
(r"/", DefaultHandler, handler_args),
(r"/public/(.*)", PublicHandler, handler_args),
(r"/panel/(.*)", PanelHandler, handler_args),
(r"/server/(.*)", ServerHandler, handler_args),
(r"/ajax/(.*)", AjaxHandler, handler_args),
@ -168,6 +167,9 @@ class Webserver:
(r"/api/v1/users/delete_user", DeleteUser, handler_args),
# API Routes V2
*api_handlers(handler_args),
# Using this one at the end
# to catch all the other requests to Public Handler
(r"/(.*)", PublicHandler, handler_args),
]
app = tornado.web.Application(
@ -179,21 +181,14 @@ class Webserver:
xsrf_cookies=True,
autoreload=False,
log_function=self.log_function,
login_url="/public/login",
login_url="/login",
default_handler_class=PublicHandler,
static_handler_class=CustomStaticHandler,
serve_traceback=debug_errors,
)
http_handers = [
(r"/", HTTPHandler, handler_args),
(r"/public/(.*)", HTTPHandlerPage, handler_args),
(r"/panel/(.*)", HTTPHandlerPage, handler_args),
(r"/server/(.*)", HTTPHandlerPage, handler_args),
(r"/ajax/(.*)", HTTPHandlerPage, handler_args),
(r"/api/stats/servers", HTTPHandlerPage, handler_args),
(r"/api/stats/node", HTTPHandlerPage, handler_args),
(r"/ws", HTTPHandlerPage, handler_args),
(r"/upload", HTTPHandlerPage, handler_args),
(r"/(.+)", HTTPHandlerPage, handler_args),
]
http_app = tornado.web.Application(
http_handers,
@ -205,7 +200,7 @@ class Webserver:
autoreload=False,
log_function=self.log_function,
default_handler_class=HTTPHandler,
login_url="/public/login",
login_url="/login",
serve_traceback=debug_errors,
)

View File

@ -1,5 +1,5 @@
{
"major": 4,
"minor": 0,
"sub": 19
"sub": 20
}

View File

@ -1,57 +1,59 @@
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link count-indicator">
<i class="fas fa-broadcast-tower
<li class="nav-item dropdown">
<a class="nav-link count-indicator">
<i class="fas fa-broadcast-tower
{% if data.get('update_available') %}
text-danger
{% end %}
"></i>
<!-- <span class="count bg-success">3</span>-->
</a>
</li>
<!-- <span class="count bg-success">3</span>-->
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link count-indicator" href="/panel/panel_config">
<i class="fas fa-cogs"></i>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link count-indicator" href="/panel/panel_config">
<i class="fas fa-cogs"></i>
</a>
</li>
<li class="nav-item dropdown user-dropdown">
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
<img class="img-xs rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image"> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
<div class="dropdown-header text-center">
<img class="img-md rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image">
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
<p class="font-weight-light text-muted mb-0">Roles: </p>
{% for r in data['user_role'] %}
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %}
{% if data.get('api_key') %}
<p class="mt-3">Logged in as API key "{{ data['api_key']['name'] }}"</p>
{% end %}
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div>
{% if data['user_data']['preparing'] %}
<span class="dropdown-item" id="support_progress"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}<br><br></span>
<span class="dropdown-item" id="support_progress"><div class="support_progress" style="height: 15px; width: 100%;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div></span>
{% else %}
<a class="dropdown-item" id="support_logs" ><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}</i></a>
<li class="nav-item dropdown user-dropdown">
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
<img class="img-xs rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image"> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
<div class="dropdown-header text-center">
<img class="img-md rounded-circle profile-picture" onerror="pfpError(this)" src="{{ data['user_data']['pfp'] }}" alt="Profile image">
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
<p class="font-weight-light text-muted mb-0">Roles: </p>
{% for r in data['user_role'] %}
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %}
{% if data['superuser'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify', 'activityLog', data['lang']) }}</a>
{% if data.get('api_key') %}
<p class="mt-3">Logged in as API key "{{ data['api_key']['name'] }}"</p>
{% end %}
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div>
</li>
</ul>
{% if data['user_data']['preparing'] %}
<span class="dropdown-item" id="support_progress"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}<br><br></span>
<span class="dropdown-item" id="support_progress">
<div class="support_progress" style="height: 15px; width: 100%;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="logs_progress_bar" role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div>
</span>
{% else %}
<a class="dropdown-item" id="support_logs"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i>{{ translate('notify', 'supportLogs', data['lang']) }}</i></a>
{% end %}
{% if data['superuser'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i>{{ translate('notify', 'activityLog', data['lang']) }}</a>
{% end %}
<a class="dropdown-item" href="/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>{{ translate('notify', 'logout', data['lang']) }}</a>
</div>
</li>
</ul>
<script>
function pfpError(image) {
<script>
function pfpError(image) {
image.onerror = "";
image.src = "/static/assets/images/faces-clipart/pic-3.png";
return true;
}
</script>
</script>

View File

@ -0,0 +1,318 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">Config.json</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<form id="config-form" class="forms-sample" method="post" action="/panel/config_json">
{% raw xsrf_form_html() %}
{% for item in data['config-json'].items() %}
<div class="form-group">
<label class="form" for="{{item[0]}}">{{item[0]}}
<small class="text-muted ml-1">
</small> </label><br />
{% if item[0] == 'language' %}
<select name="{{item[0]}}" class="form-control">
{% for lang in data['availables_languages'] %}
{% if lang == item[1] %}
<option selected>{{lang}}</option>
{% else %}
<option>{{lang}}</option>
{% end %}
{% end %}
</select>
{% elif item[0] == 'disabled_language_files' %}
<div class="input-group">
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) {
$(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
});">Enable all Languages</button>
<select id="lang_select" class="form-control selectpicker show-tick" data-icon-base="fas" data-tick-icon="fa-check" multiple data-style="custom-picker">
{% for lang in data['all_languages'] %}
{% if lang in item[1] %}
<option selected>{{lang}}</option>
{% else %}
<option>{{lang}}</option>
{% end %}
{% end %}
</select>
<textarea id="disabled_lang" name="{{item[0]}}" class="form-control list hidden" rows="{{ len(data['all_languages']) }}" value="{{','.join(item[1])}}" hidden>{{','.join(item[1])}}</textarea>
</div>
{% elif isinstance(item[1], list) %}
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea>
{% elif isinstance(item[1], bool) %}
{% if item[1] == True %}
<div style="margin-left: 30px;">
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True" checked>
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False">
 <label for="False">False</label>
</div>
{% else %}
<div style="margin-left: 30px;">
<input type="radio" class="form-check-input" name="{{item[0]}}" id="True" value="True">
 <label for="True">True</label><br>
<input type="radio" class="form-check-input" name="{{item[0]}}" id="False" value="False" checked>
 <label for="False">False</label>
</div>
{% end %}
{% elif isinstance(item[1], int) %}
<input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="1" min="0" required>
{% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required>
{% end %}
</div>
{% end %}
<button class="btn btn-success" type="submit">Submit</button>&nbsp;<span id="submit-status"></span>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.custom-picker {
border: 1px solid var(--outline);
}
.dropdown-menu.inner {
display: inline-block !important;
}
.popover-body {
color: white !important;
;
}
input[type="radio"] {
-ms-transform: scale(1.5);
/* IE 9 */
-webkit-transform: scale(1.5);
/* Chrome, Safari, Opera */
transform: scale(1.5);
}
</style>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
$("#config-form").submit(function (e) {
let uuid = uuidv4();
var token = getCookie("_xsrf")
e.preventDefault();
$("#submit-status").html('<i class="fa fa-spinner fa-spin"></i>');
/* Convert multiple select to text list */
let selected_Lang = $('#lang_select').val();
$('#disabled_lang').val(selected_Lang);
let class_list = document.getElementsByClassName("list");
let form_json = convertFormToJSON($("#config-form"));
for (let i = 0; i < class_list.length; i++) {
let str = String($(class_list.item(i)).val())
form_json[$(class_list.item(i)).attr("name")] = uuid + "," + str.replace(/\s/g, '');
};
form_json['uuid'] = uuid;
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
dataType: "text",
url: '/panel/config_json',
data: form_json,
success: function (data) {
$("#submit-status").html('<i class="fa fa-check"></i>');
},
});
});
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
function convertFormToJSON(form) {
const array = $(form).serializeArray(); // Encodes the set of form elements as an array of names and values.
const json = {};
$.each(array, function () {
json[this.name] = this.value || "";
});
return json;
}
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {
$('.too_small').popover("show");
$('.too_small2').popover("show");
}
});
$(window).ready(function () {
$('body').click(function () {
$('.too_small').popover("hide");
$('.too_small2').popover("hide");
});
});
$(window).resize(function () {
// This will execute whenever the window is resized
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
else {
$('.too_small').popover("hide");
} // New width
if ($(window).width() < 1000) {
$('.too_small2').popover("show");
}
else {
$('.too_small2').popover("hide");
} // New width
});
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
console.log("File changed good");
}
});
</script>
<script>
$(document).ready(function () {
console.log('ready for JS!');
$('.selectpicker').selectpicker("refresh");
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.clear-comm').click(function () {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/clear_comm',
success: function (data) {
},
});
})
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + photo,
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + photo,
success: function (data) {
window.location.reload();
},
});
})
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/js/bootstrap-select.min.js">
</script>
{% end %}

View File

@ -0,0 +1,391 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller - {{ translate('customLogin', 'pageTitle', data['lang']) }}{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('customLogin', 'customLoginPage', data['lang']) }}</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-12">
<h4>{{ translate('customLogin', 'loginImage', data['lang']) }}</h4>
<hr>
<form class="form-row" name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="col form-group">
<span id="upload_input"><input type="file" class="form-control-file" id="file" name="file"
multiple="false" required></span>
</div>
<div class="col form-group">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()"
disabled>UPLOAD</button>
</div>
</form>
<hr>
<hr />
</div>
<div class="col-12">
<div>
<h6>{{ translate('customLogin', 'preview', data['lang']) }}:</h6>
<form id="photo_form">
<div class="form-group row">
<label for="photo" class="col-sm-6 col-form-label">Selected Background Image</label>
<div class="col-sm-6">
<select class="form-select form-control form-control-lg select-css form-control-plaintext"
id="photo" name="photo" form="photo_form" onchange="updateBackgroundPreview()">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
</select>
</div>
</div>
<div id="photo_loading" class="form-group" hidden>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i
class="fa-solid fa-spinner"></i></div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3" for="formControlRange">{{ translate('customLogin', 'loginOpacity',
data['lang']) }}</label>
<label class="col-sm-1" id="opacityValue">{{ data['login_opacity'] }}%</label>
<div class="range col-sm-8">
<input type="range" class="form-control-range" id="modal_opacity" name="modal_opacity"
onchange="previewOpacity()" min="0" max="100" value="{{ data['login_opacity'] }}">
</div>
</div>
<div id="login_preview" style="position: relative;">
<img id="bg-preview" src="../../static/assets/images/auth/{{ data['background'] }}"
class="img-fluid" alt="Responsive image">
<div id="login-form-preview">
<div id="login-form-background" class="auto-form-wrapper login-modal">
<div class="text-center auto-form-logo">
<img src="/static/assets/images/logo_long.svg">
</div>
<style>
#login-form-preview {
display: flex;
position: absolute;
overflow: hidden;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
max-width: 90%;
max-height: 90%;
}
.auto-form-wrapper {
background: rgb(34, 36, 55, 1);
padding: 2rem 2rem 0.5rem;
border-radius: 4px;
-webkit-box-shadow: 0 -25px 37.7px 11.3px rgb(8 143 220 / 7%);
box-shadow: 0 -25px 37.7px 11.3px rgb(8 143 220 / 7%);
color: #fff;
}
/*.auto-form-logo {
background: #222437;
padding: 0rem;
margin: 0.5rem 0rem;
border-radius: 0.2rem;
color: #fff;
}*/
.login-modal {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2) !important;
}
.login-text-input {
border: none !important;
background-color: hsl(234, 30%, 45%);
color: var(--white) !important;
}
.login-text-input:hover,
.login-text-input:focus {
background-color: hsl(234, 30%, 39%) !important;
}
.login-input {
border-radius: 0.4rem !important;
box-shadow: 0 8px 12px 0 hsla(0, 0%, 0%, 0.2);
transition: all 0.3s ease-in-out;
}
.login-input:hover,
.login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
</style>
<div id="login_form_data">
<input type="hidden" name="_xsrf"
value="2|1d603267|809fb6bd82f677d440e484dde7c3a310|1671726040" disabled>
<div class="form-group">
<label class="label">Username</label>
<div class="input-group">
<input type="text" class="form-control login-text-input login-input"
placeholder="Username" name="username" id="username" required="true" disabled>
</div>
</div>
<div class="form-group">
<label class="label">Password</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="Password" name="password" id="password" required="true" disabled>
</div>
</div>
<div class="form-group">
<button class="login-input btn btn-primary submit-btn btn-block" disabled>Log
In</button>
</div>
<fieldset style="color: red; text-align: center;">
<span></span>
</fieldset>
<div class="form-group d-flex justify-content-between">
<div class="form-check form-check-flat mt-0">
&nbsp;
</div>
<a href="#" class="text-small forgot-password" disabled>Forgot Password</a>
</div>
<div class="text-block text-center my-3">
<span class="text-small font-weight-semibold"><a
href="https://craftycontrol.com/">Crafty Control
4.0.20</a> </span>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-outline-success select-photo" type="button">{{
translate('customLogin',
'apply', data['lang']) }}</button>
<button class="btn btn-outline-danger delete-photo" type="button">{{
translate('customLogin',
'delete', data['lang']) }}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.popover-body {
color: white !important;
;
}
</style>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
$(document).ready(function () {
$('[data-toggle="popover"]').popover();
if ($(window).width() < 1000) {
$('.too_small').popover("show");
$('.too_small2').popover("show");
}
});
$(window).ready(function () {
$('body').click(function () {
$('.too_small').popover("hide");
$('.too_small2').popover("hide");
});
});
$(window).resize(function () {
// This will execute whenever the window is resized
if ($(window).width() < 1000) {
$('.too_small').popover("show");
}
else {
$('.too_small').popover("hide");
} // New width
if ($(window).width() < 1000) {
$('.too_small2').popover("show");
}
else {
$('.too_small2').popover("hide");
} // New width
});
$('#file').change(function () {
console.log("File changed");
if ($('#file').val()) {
$('#upload-button').prop("disabled", false);
console.log("File changed good");
}
});
</script>
<script>
$(document).ready(function () {
console.log('ready for JS!')
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + encodeURIComponent(photo),
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
let opacity = $('#modal_opacity').val();
let enc_photo = encodeURIComponent(photo);
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + enc_photo + '&opacity=' + opacity,
success: function (data) {
window.location.reload();
},
});
})
$(document).ready(function () {
let opacity = parseInt($("#modal_opacity").val());
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
});
function previewOpacity() {
let opacity = parseInt($("#modal_opacity").val())
console.debug("Selected Opacity = " + opacity + "%");
document.getElementById('opacityValue').innerHTML = (opacity) + "%";
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
}
function updateBackgroundSelect() {
$("#photo").val($("#try_photo").val()).change();
}
function updateBackgroundPreview() {
var img = document.getElementById('bg-preview');
if ($("#photo").val() == "login_1.jpg") {
var src_path = "../../static/assets/images/auth/".concat($("#photo").val());
}
else {
var src_path = "../../static/assets/images/auth/custom/".concat($("#photo").val());
}
img.src = src_path;
}
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>';
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
{% end %}

View File

@ -282,184 +282,184 @@
<span class="port" data-toggle="tooltip" title="{{
server['server_data']['server_port'] }}">
<div id="server_running_status_{{server['server_data']['server_id']}}">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
{% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard',
'crashed',
data['lang']) }}</span>
{% else %}
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
<br />
<br />
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
{% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{ translate('dashboard',
'crashed',
data['lang']) }}</span>
{% else %}
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
<br />
<br />
</td>
<span class="server-player-totals" id="server_players_{{server['server_data']['server_id']}}"
data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"></span>
</tr>
{% end %}
</div>
</span>
{% for server in data['failed_servers'] %}
<tr id="{{server['server_id']}}" draggable="false">
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning"
href="/panel/server_detail?id={{server['server_id']}}&subpage=config">{{server['server_name']}}</a>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><i class="fas fa-cloud"></i>&nbsp;Unloaded</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</span>
{% for server in data['failed_servers'] %}
<tr id="{{server['server_id']}}" draggable="false">
<td class="text-warning"><i class="fas fa-server"></i>&nbsp;<a class="text-warning"
href="/panel/server_detail?id={{server['server_id']}}&subpage=config">{{server['server_name']}}</a>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><i class="fas fa-cloud"></i>&nbsp;Unloaded</td>
</tr>
{% end %}
{% if len(data['servers']) > 0 %}
<!-- View for Small screen -->
<div class="d-sm-none d-block">
<div class="accordion" id="accordionServers">
{% for server in data['servers'] %}
<div class="card">
<div class="card-header" id="heading-{{server['server_data']['server_id']}}">
<h2 class="mb-0 container overflow-hidden">
<div class="row">
<div class="col-10 col-lg-3 mx-0 px-0">
{% if server['alert'] %}
<a style="color: red !important" class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}&nbsp; <i
class="fas fa-exclamation-triangle"></i>
</a>
</tbody>
</table>
</div>
{% end %}
{% if len(data['servers']) > 0 %}
<!-- View for Small screen -->
<div class="d-sm-none d-block">
<div class="accordion" id="accordionServers">
{% for server in data['servers'] %}
<div class="card">
<div class="card-header" id="heading-{{server['server_data']['server_id']}}">
<h2 class="mb-0 container overflow-hidden">
<div class="row">
<div class="col-10 col-lg-3 mx-0 px-0">
{% if server['alert'] %}
<a style="color: red !important" class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}&nbsp; <i
class="fas fa-exclamation-triangle"></i>
</a>
{% else %}
<a class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}
</a>
{% end %}
</div>
<div class="col-2 col-lg-3 mx-0 px-0">
<a class="btn btn-link d-flex justify-content-center" type="button" data-toggle="collapse"
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
aria-controls="collapse-{{server['server_data']['server_id']}}">
<i class="fas fa-chart-bar"></i>
</a>
</div>
<div class="col-4 col-lg-3 mx-0 px-0">
<a id="m_server_running_status_{{server['server_data']['server_id']}}"
class="btn btn-link d-flex justify-content-start" type="button">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
{% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{
translate('dashboard',
'crashed',
data['lang']) }}</span>
{% else %}
<a class="btn btn-link d-flex justify-content-start" type="button"
href="/panel/server_detail?id={{server['server_data']['server_id']}}">
<i class="fas fa-server"></i> {{ server['server_data']['server_name'] }}
</a>
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
</a>
</div>
<div class="col-8 col-lg-3 mx-0 px-0">
<div id="controls{{server['server_data']['server_id']}}" class="container overflow-hidden">
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a>
</div>
</div>
{% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE -->
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link">{{
translate('serverTerm', 'updating',
data['lang']) }}</i></a>
</div>
</div>
{% elif server['stats']['waiting_start']%}
<!-- WHAT HAPPENED HERE -->
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link" title="{{
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
data['lang']) }}</i></a>
</div>
</div>
{% elif server['stats']['importing']%}
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i
class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'importing', data['lang']) }}</a>
</div>
</div>
{% else %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn play_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn clone_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i></a>
</div>
</div>
{% end %}
{% end %}
</div>
<div class="col-2 col-lg-3 mx-0 px-0">
<a class="btn btn-link d-flex justify-content-center" type="button" data-toggle="collapse"
data-target="#collapse-{{server['server_data']['server_id']}}" aria-expanded="false"
aria-controls="collapse-{{server['server_data']['server_id']}}">
<i class="fas fa-chart-bar"></i>
</a>
</div>
<div class="col-4 col-lg-3 mx-0 px-0">
<a id="m_server_running_status_{{server['server_data']['server_id']}}"
class="btn btn-link d-flex justify-content-start" type="button">
{% if server['stats']['running'] %}
<span class="text-success"><i class="fas fa-signal"></i> {{ translate('dashboard', 'online',
data['lang']) }}</span>
{% elif server['stats']['crashed'] %}
<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> {{
translate('dashboard',
'crashed',
data['lang']) }}</span>
{% else %}
<span class="text-warning"><i class="fas fa-ban"></i> {{ translate('dashboard', 'offline',
data['lang']) }}</span>
{% end %}
</a>
</div>
<div class="col-8 col-lg-3 mx-0 px-0">
<div id="controls{{server['server_data']['server_id']}}" class="container overflow-hidden">
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link stop_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link restart_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn btn-link kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a>
</div>
</div>
{% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE -->
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link">{{
translate('serverTerm', 'updating',
data['lang']) }}</i></a>
</div>
</div>
{% elif server['stats']['waiting_start']%}
<!-- WHAT HAPPENED HERE -->
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link" title="{{
translate('dashboard', 'delay-explained' , data['lang'])}}">{{ translate('dashboard', 'starting',
data['lang']) }}</i></a>
</div>
</div>
{% elif server['stats']['importing']%}
<div class="row">
<div class="col-12 px-0">
<a data-id="{{server['server_data']['server_id']}}" class="btn btn-link"><i
class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'importing', data['lang']) }}</a>
</div>
</div>
{% else %}
<div class="row">
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn play_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
<i class="fas fa-play"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn clone_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'clone' , data['lang']) }}">
<i class="fas fa-clone"></i>
</a>
</div>
<div class="col-4 px-0">
<a data-id="{{server['server_data']['server_id']}}"
class="btn kill_button actions_serveritem" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i></a>
</div>
</div>
{% end %}
{% end %}
</div>
</div>
</div>
</h2>
</div>
</div>
</h2>
</div>
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
<div class="card-body">
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</h6>
<div id="m_server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
<div id="collapse-{{server['server_data']['server_id']}}" class="collapse"
aria-labelledby="heading-{{server['server_data']['server_id']}}" data-parent="#accordionServers">
<div class="card-body">
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'cpuUsage', data['lang']) }}</h6>
<div id="m_server_cpu_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['cpu']}}">
<div class="progress-bar
{% if server['stats']['cpu'] <= 33 %}
bg-success
{% elif 34 <= server['stats']['cpu'] <= 66 %}
@ -468,17 +468,17 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
aria-valuemax="100"></div>
</div>
{{server['stats']['cpu']}}%
</div>
<div class="col-6">
<h6>{{ translate('dashboard', 'memUsage', data['lang']) }}</h6>
<div draggable="false" id="m_server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
</div>
<div class="col-6">
<h6>{{ translate('dashboard', 'memUsage', data['lang']) }}</h6>
<div draggable="false" id="m_server_mem_{{server['server_data']['server_id']}}">
<div class="progress mb-1" data-toggle="tooltip" data-placement="top"
title="{{server['stats']['mem']}}">
<div class="progress-bar
{% if server['stats']['mem_percent'] <= 33 %}
bg-success
{% elif 34 <= server['stats']['mem_percent'] <= 66 %}
@ -487,58 +487,58 @@
bg-danger
{% end %}
" role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
{% if server['stats']['mem'] == 0 %}
0 MB
{% else %}
{{server['stats']['mem']}}
{% end %}
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{{server['stats']['mem_percent']}}% -
{% if server['stats']['mem'] == 0 %}
0 MB
{% else %}
{{server['stats']['mem']}}
{% end %}
</div>
</div>
<br />
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'size', data['lang']) }}</h6>
<div draggable="false" id="m_server_world_{{server['server_data']['server_id']}}">
{{ server['stats']['world_size'] }}
</div>
</div>
<br />
<div class="row">
<div class="col-6">
<h6>{{ translate('dashboard', 'size', data['lang']) }}</h6>
<div draggable="false" id="m_server_world_{{server['server_data']['server_id']}}">
{{ server['stats']['world_size'] }}
</div>
<div class="col-6" style="width: auto;">
<h6>{{ translate('dashboard', 'players', data['lang']) }}</h6>
<div draggable="false" id="m_server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard',
'max',
data['lang']) }} <br />
</div>
<div class="col-6" style="width: auto;">
<h6>{{ translate('dashboard', 'players', data['lang']) }}</h6>
<div draggable="false" id="m_server_desc_{{server['server_data']['server_id']}}">
{% if server['stats']['int_ping_results'] %}
{{ server['stats']['online'] }} / {{ server['stats']['max'] }} {{ translate('dashboard',
'max',
data['lang']) }} <br />
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id"
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">
{{ server['stats']['desc'] }}</div> <br />
{% end %}
{% if server['stats']['desc'] != 'False' %}
<div id="desc_id"
style="overflow-wrap: break-word !important; max-width: 85px !important; overflow: scroll;">
{{ server['stats']['desc'] }}</div> <br />
{% end %}
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
</div>
{% if server['stats']['version'] != 'False' %}
{{ server['stats']['version'] }}
{% end %}
{% end %}
</div>
</div>
</div>
</div>
</div>
{% end %}
</div>
{% end %}
</div>
{% end %}
</div>
{% end %}
</div>
</div>
</div>
</div>
</div>
@ -606,7 +606,6 @@
function send_command(server_id, command) {
/* this getCookie function is in base.html */
const token = getCookie("_xsrf");
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
@ -623,6 +622,51 @@
});
}
function warn(message, link = null, className = null) {
var closeEl = document.createElement('span');
var strongEL = document.createElement('strong');
var msgEl = document.createElement('div');
closeEl.innerHTML = '&times;';
strongEL.textContent = 'Warning: ';
msgEl.append(strongEL, message);
closeEl.style.marginLeft = '15px';
closeEl.style.fontWeight = 'bold';
closeEl.style.float = 'right';
closeEl.style.fontSize = '22px';
closeEl.style.lineHeight = '20px';
closeEl.style.cursor = 'pointer';
closeEl.addEventListener('click', function () { this.parentElement.style.display = 'none'; });
var parentEl = document.createElement('div');
parentEl.style.padding = '20px';
parentEl.style.backgroundColor = '#f7970f';
parentEl.appendChild(closeEl);
parentEl.appendChild(msgEl);
if (link) {
let linkEl = document.createElement('a')
linkEl.href = link;
linkEl.innerHTML = "See our documentation for details.";
linkEl.style.color = 'white';
linkEl.style.textDecoration = 'underline';
linkEl.target = "_blank";
parentEl.appendChild(linkEl);
}
if (className) {
parentEl.classList.add(className);
}
document.querySelector('.dynamicMsg').appendChild(parentEl);
}
function send_kill(server_id) {
/* this getCookie function is in base.html */
const token = getCookie("_xsrf");
@ -774,11 +818,15 @@
send_command(server_id, 'start_server');
bootbox.alert({
backdrop: true,
title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}',
title: '<span class="dynamicMsg">{% raw translate("dashboard", "sendingCommand", data["lang"]) %}</span>',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("dashboard", "bePatientStart", data["lang"]) %} </div>'
});
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.",
null, 'wssError');
}
$(".stop_button").click(function () {
console.log("stopping server");
server_id = $(this).attr("data-id");

View File

@ -1,69 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Crafty Controller</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper">
<div class="text-center">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Crafty Controller</title>
<!-- plugins:css -->
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
<!-- endinject -->
<!-- Plugin css for this page -->
<!-- End Plugin css for this page -->
<!-- Layout styles -->
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
<!-- End Layout styles -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/images/logo_small.svg">
<link rel="alternate icon" href="/static/assets/images/favicon.png" />
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg");
background-size: cover;
}
</style>
<body class="dark-theme">
<div class="container-scroller">
<div class="container-fluid page-body-wrapper full-page-wrapper">
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
<div class="row w-100">
<div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper">
<div class="text-center">
<img src="/static/assets/images/logo_long.svg"><br /><br />
<div class="col-sm-12 grid-margin stretch-card">
<div class="card card-statistics social-card google-card card-colored">
<div class="card-body">
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied', 'accessDenied', data['lang']) }}</h4>
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}</h5>
<p class="mb-2 comment font-weight-light">
{{ translate('accessDenied', 'contactAdmin', data['lang']) }}<br /><br />
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{ translate('accessDenied', 'contact', data['lang']) }}</a>
</p>
<div class="col-sm-12 grid-margin stretch-card">
<div class="card card-statistics social-card google-card card-colored">
<div class="card-body">
<h4 class="platform-name mb-3 mt-4 font-weight-semibold user-name">{{ translate('accessDenied',
'accessDenied', data['lang']) }}</h4>
<h5 class="headline font-weight-medium">{{ translate('accessDenied', 'noAccess', data['lang']) }}
</h5>
<p class="mb-2 comment font-weight-light">
{{ translate('accessDenied', 'contactAdmin', data['lang']) }}<br /><br />
<a class="d-inline font-weight-medium" href="https://discord.gg/9VJPhCE"> {{
translate('accessDenied', 'contact', data['lang']) }}</a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
</div>
<!-- page-body-wrapper ends -->
<!-- content-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
</body>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
<!-- plugins:js -->
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
<script src="/static/assets/js/shared/misc.js"></script>
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
</body>
</html>

View File

@ -8,16 +8,16 @@
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4>
<h4 class="page-title">
{{ translate('panelConfig', 'title', data['lang']) }}
<br />
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
@ -26,6 +26,23 @@
<div class="card">
<div class="card-body">
{% if data['superuser'] %}
{% include "parts/crafty_config_list.html %}
{% end %}
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<!-- TODO: Translate the following -->
<h4 class="page-title">{{ translate('panelConfig', 'pageTitle', data['lang']) }}</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-md-12 col-lg-12 grid-margin stretch-card">
<div class="card">
@ -33,9 +50,7 @@
<h4 class="card-title"><i class="fas fa-users"></i> {{ translate('panelConfig', 'users', data['lang'])
}}</h4>
{% if data['user_data']['hints'] %}
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
<span class="too_small" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
{% end %}
<!-- TODO: Translate the following -->
<div><a class="nav-link" href="/panel/add_user"><i class="fas fa-plus-circle"></i> &nbsp; {{
@ -133,9 +148,7 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'roles',
data['lang']) }}</h4>
{% if data['user_data']['hints'] %}
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" ,
data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" ,
data-placement="top"></span>
<span class="too_small2" title="{{ translate('dashboard', 'cannotSee', data['lang']) }}" , data-content="{{ translate('dashboard', 'cannotSeeOnMobile2', data['lang']) }}" , data-placement="top"></span>
{% end %}
<div><a class="nav-link" href="/panel/add_role"><i class="fas fa-plus-circle"></i> &nbsp; {{
translate('panelConfig', 'newRole', data['lang']) }}</a></div>
@ -221,74 +234,6 @@
<h4 class="card-title"><i class="fas fa-user-tag"></i> {{ translate('panelConfig', 'adminControls',
data['lang']) }}</h4>
</div>
<div class="card-body">
<button type="button" class="btn btn-outline-danger clear-comm">{{ translate('panelConfig',
'clearComms', data['lang']) }}</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('panelConfig', 'loginImage', data['lang']) }}</h4>
<br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('panelConfig', 'backgroundUpload', data['lang'])
}}</label><br>
<span id="upload_input">
<input type="file" multiple="false" class="form-control" id="file" name="file" required
style="width: 70%;">
<button type="button" class="btn btn-info" id="upload-button" onclick="sendFile()"
disabled>UPLOAD</button>
</span>
</div>
</div>
</div>
</div>
</div>
</form>
</p>
</div>
</div>
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('panelConfig', 'loginBackground', data['lang']) }}</h4><br /><br><br />
<form id="photo_form">
<select class="form-select form-control form-control-lg select-css" id="photo" name="photo"
form="photo_form">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
</select>
<div>
<br>
<h6>{{ translate('panelConfig', 'preview', data['lang']) }}:</h6>
<img style="width: 200px; height: 113px;"
src="../../static/assets/images/auth/{{ data['background'] }}">
</div>
<br />
<br />
<button class="btn btn-outline-success select-photo" type="button">{{ translate('panelConfig',
'select', data['lang']) }}</button>
<button class="btn btn-outline-danger delete-photo" type="button">{{ translate('panelConfig',
'delete', data['lang']) }}</button>
</form>
</div>
</div>
</div>
</div>
@ -350,97 +295,5 @@
}
});
</script>
<script>
$(document).ready(function () {
console.log('ready for JS!')
});
$(".show_button").click(function () {
console.log("showing key");
api_key = $(this).attr("data-id");
bootbox.alert({
backdrop: true,
title: '',
message: api_key,
});
});
$('.clear-comm').click(function () {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/clear_comm',
success: function (data) {
},
});
})
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + photo,
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + photo,
success: function (data) {
window.location.reload();
},
});
})
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
setTimeout(function () {
window.location.reload();
}, 2000);
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
{% end %}

View File

@ -0,0 +1,14 @@
<ul class="nav nav-tabs col-md-12 tab-simple-styled" role="tablist" style="margin-top: 0;">
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'panel_config' %}active{% end %}" href="/panel/panel_config" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Panel Config</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'config_json' %}active{% end %}" href="/panel/config_json" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Config.json</a>
</li>
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'custom_login' %}active{% end %}" href="/panel/custom_login" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Custom Login</a>
</li>
</ul>

View File

@ -107,6 +107,40 @@
translate('serverBackups', 'shutdown', data['lang']) }}
{% end %}
</div>
<div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['before'] %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check" checked>Run
Command Before Backup
<br>
<input type="text" class="form-control" name="backup_before" id="backup_before"
value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
{% else %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check">Run Command
Before Backup
<br>
<input type="text" class="form-control" name="backup_before" id="backup_before" value=""
placeholder="We enter the / for you." style="display: none;">
{% end %}
</div>
<div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['after'] %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check" checked>Run
Command After Backup
<br>
<input type="text" class="form-control" name="backup_after" id="backup_after"
value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you"
style="display: inline-block;">
{% else %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check">Run Command
Before Backup
<br>
<input type="text" class="form-control" name="backup_after" id="backup_after" value=""
placeholder="We enter the / for you." style="display: none;">
{% end %}
</div>
<div class="form-group">
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
@ -344,6 +378,22 @@
});
}
$("#before-check").on("click", function () {
if ($("#before-check:checked").val()) {
$("#backup_before").css("display", "inline-block");
} else {
$("#backup_before").css("display", "none");
$("#backup_before").val("");
}
});
$("#after-check").on("click", function () {
if ($("#after-check:checked").val()) {
$("#backup_after").css("display", "inline-block");
} else {
$("#backup_after").css("display", "none");
$("#backup_after").val("");
}
});
$(document).ready(function () {
try {

View File

@ -23,7 +23,7 @@
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"),
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg");
background-size: cover;
}

View File

@ -23,7 +23,7 @@
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"),
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg");
background-size: cover;
}

View File

@ -23,9 +23,10 @@
</head>
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"),
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg");
background-size: cover;
background-position: center;
}
</style>
@ -36,8 +37,9 @@
<div class="row w-100">
<div class="col-lg-4 mx-auto">
<div class="auto-form-wrapper login-modal">
<div class="text-center">
<div id="login-form-background" class="auto-form-wrapper login-modal">
<div id="login_opacity" data-value="{{ data['login_opacity'] }}" hidden></div>
<div class="text-center auto-form-logo">
<img src="/static/assets/images/logo_long.svg">
</div>
<style>
@ -69,25 +71,25 @@
}
</style>
{% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post">
<form action="/login?{{ data['query'] }}" method="post">
{% else %}
<form action="/public/login" method="post">
<form action="/login" method="post">
{% end %}
{% raw xsrf_form_html() %}
<div class="form-group">
<label class="label">{{ translate('login', 'username', data['lang']) }}</label>
<div class="input-group">
<input type="text" class="form-control login-text-input login-input"
placeholder="{{ translate('login', 'username', data['lang']) }}" name="username" id="username"
required="true">
placeholder="{{ translate('login', 'username', data['lang']) }}" name="username" id="username"
required="true">
</div>
</div>
<div class="form-group">
<label class="label">{{ translate('login', 'password', data['lang']) }}</label>
<div class="input-group">
<input type="password" class="form-control login-text-input login-input"
placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password"
required="true">
placeholder="{{ translate('login', 'password', data['lang']) }}" name="password" id="password"
required="true">
</div>
</div>
<div class="form-group">
@ -133,6 +135,13 @@
<script src="/static/assets/js/shared/settings.js"></script>
<script src="/static/assets/js/shared/todolist.js"></script>
<!-- endinject -->
<script>
$(document).ready(function () {
let login_opacity_div = document.getElementById('login_opacity');
let opacity = login_opacity_div.getAttribute('data-value');
document.getElementById('login-form-background').style.background = 'rgb(34, 36, 55, ' + (opacity / 100) + ')';
});
</script>
</body>
</html>

View File

@ -9,7 +9,7 @@
<!-- View for Large screen -->
<style>
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}"),
background: url("../../static/assets/images/auth/{% raw data['background'] %}"),
url("../../static/assets/images/auth/login-1.jpg");
background-size: cover;
}

View File

@ -5,61 +5,61 @@
{% block content %}
<div class="auto-form-wrapper">
<div class="text-center">
<div class="text-center">
<!-- <img src="/static/assets/images/logo_long.svg">-->
{{ _('Configure Your Existing Server') }}<br /><br />
{{ _('Configure Your Existing Server') }}<br /><br />
</div>
<form action="/login" method="post">
{% raw xsrf_form_html() %}
<div class="form-group">
<label class="label">
{{ _('Server Name') }} - <small>{{ _('Example Survival Server') }}</small>
</label>
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Name') }}" name="server_name" value="{{_('MyFirstServer') }}" maxlength="55">
</div>
</div>
<form action="/public/login" method="post">
{% raw xsrf_form_html() %}
<div class="form-group">
<label class="label">
{{ _('Server Name') }} - <small>{{ _('Example Survival Server') }}</small>
</label>
<div class="form-group">
{% if data['is_windows'] %}
<label class="label">
{{ _('Server Path') }} - <small>{{ _('Example c:\minecraft\server') }}</small>
</label>
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Name') }}" name="server_name" value="{{_('MyFirstServer') }}" maxlength="55">
</div>
</div>
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
value="c:\windows\minecraft" maxlength="255">
</div>
<div class="form-group">
{% if data['is_windows'] %}
<label class="label">
{{ _('Server Path') }} - <small>{{ _('Example c:\minecraft\server') }}</small>
</label>
{% else %}
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
value="c:\windows\minecraft" maxlength="255">
</div>
<label class="label">
{{ _('Server Path') }} - <small>{{ _("Example: /var/opt/minecraft/server") }}</small>
</label>
{% else %}
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
value="c:\windows\minecraft" maxlength="255">
</div>
{% end %}
</div>
<label class="label">
{{ _('Server Path') }} - <small>{{ _("Example: /var/opt/minecraft/server") }}</small>
</label>
<div class="form-group">
<label class="label">
{{ _('Server Jar') }} - <small>{{ _('Example paper.jar') }}</small>
</label>
<div class="input-group">
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
value="c:\windows\minecraft" maxlength="255">
</div>
{% end %}
</div>
<div class="form-group">
<label class="label">
{{ _('Server Jar') }} - <small>{{ _('Example paper.jar') }}</small>
</label>
<input type="text" class="form-control" placeholder="{{ _('Server Jar') }}" name="server_jar" value="paper.jar" maxlength="255">
</div>
<input type="text" class="form-control" placeholder="{{ _('Server Jar') }}" name="server_jar" value="paper.jar" maxlength="255">
</div>
<div class="form-group">
<button class="btn btn-primary submit-btn btn-block"><i class="fas fa-save"></i> Save</button>
</div>
<div class="form-group">
<button class="btn btn-primary submit-btn btn-block"><i class="fas fa-save"></i> Save</button>
</div>
</form>
</form>
</div>

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns(
"crafty_settings", login_opacity=peewee.IntegerField(default=100)
)
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("crafty_settings", ["login_opacity"])
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,35 @@
# Generated by database migrator
import datetime
from peewee import *
from app.classes.models.users import Users
from app.classes.models.servers import Servers
def migrate(migrator, database, **kwargs):
migrator.drop_table("commands")
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
db = database
class Commands(Model):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref="server", index=True)
user = ForeignKeyField(Users, backref="user", index=True)
source_ip = CharField(default="127.0.0.1")
command = CharField(default="")
executed = BooleanField(default=False)
class Meta:
table_name = "commands"
database = db
migrator.create_table(Commands)
"""
Write your rollback migrations here.
"""

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("backups", before=peewee.CharField(default=""))
migrator.add_columns("backups", after=peewee.CharField(default=""))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("backups", ["before"])
migrator.drop_columns("backups", ["after"])
"""
Write your rollback migrations here.
"""

606
app/translations/cs_CS.json Normal file
View File

@ -0,0 +1,606 @@
{
"404": {
"contact": "Kontaktujte podporu Crafty přes Discord",
"notFound": "Stránka nebyla nalezena",
"unableToFind": "Hledanou stránku se nám nepodařilo najít. Zkuste to prosím znovu nebo se vraťte a obnovte stránku."
},
"accessDenied": {
"accessDenied": "Přístup odepřen",
"contact": "Kontaktujte podporu Crafty přes Discord",
"contactAdmin": "Pro přístup k tomuto prostředku se obraťte na správce serveru, nebo pokud si myslíte, že byste k němu měli mít přístup, kontaktujte podporu.",
"noAccess": "K tomuto zdroji nemáte přístup"
},
"apiKeys": {
"apiKeys": "Klíče API",
"auth": "Autorizován? ",
"buttons": "Tlačítka",
"config": "Nastavení",
"crafty": "Crafty: ",
"created": "Vytvořen",
"createNew": "Vytvořit nový token API",
"deleteKeyConfirmation": "Chcete tento API klíč odstranit? Tuto akci nelze vrátit zpět.",
"deleteKeyConfirmationTitle": "Odstranit klíč API ${keyId}?",
"getToken": "Získat token",
"name": "Jméno",
"nameDesc": "Jak chcete nazvat tento token API? ",
"no": "Ne",
"pageTitle": "Úprava uživatelských klíčů API",
"permName": "Název oprávnění",
"perms": "Oprávnění",
"server": "Server: ",
"superUser": "Super uživatel",
"yes": "Ano"
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Varování: </strong>Crafty nefunguje správně, pokud není povolen JavaScript!"
},
"credits": {
"developmentTeam": "Vývojový tým",
"hugeDesc": "Neskutečně",
"pageDescription": "Bez těchto lidí bychom neměli Crafty.",
"pageTitle": "Zásluhy",
"patreonDesc": "našim příznivcům Patreonu / Ko-fi!",
"patreonOther": "Další",
"patreonSupporter": "Podpůrci Patreonu / Ko-fi",
"patreonUpdate": "Poslední aktualizace:",
"retiredStaff": "Bývalí zaměstnanci",
"subscriberName": "Jméno",
"subscriptionLevel": "Úroveň",
"supportTeam": "Tým podpory a dokumentace",
"thankYou": "DĚKUJEME",
"translationDesc": "naší komunitě, která překládá! [ Aktivní = 🟢 Neaktivní/ukončený = ⚪ ]",
"translationName": "Jazyk",
"translationTitle": "Překlad do jazyků",
"translator": "Překladatelé"
},
"dashboard": {
"actions": "Akce",
"allServers": "Všechny servery",
"avg": "Průměr",
"backups": "Zálohy",
"bePatientClone": "Buďte prosím trpěliví, než se dokončí klonování serveru.<br /> Tato obrazovka se za okamžik aktualizuje",
"bePatientRestart": "Buďte prosím trpěliví, než se dokončí restart serveru.<br /> Tato obrazovka se za okamžik aktualizuje",
"bePatientStart": "Buďte prosím trpěliví, než se dokončí spuštění serveru.<br /> Tato obrazovka se za okamžik aktualizuje",
"bePatientStop": "Buďte prosím trpěliví, než se dokončí zastavení serveru.<br /> Tato obrazovka se za okamžik aktualizuje",
"cannotSee": "Nezobrazuje se vám vše?",
"cannotSeeOnMobile": "Nezobrazuje se vám vše na mobilu?",
"cannotSeeOnMobile2": "Zkuste posunout tabulku do strany.",
"clone": "Klon",
"cloneConfirm": "Opravdu chcete tento server naklonovat? Tento proces může chvíli trvat.",
"cpuCores": "Jádra CPU",
"cpuCurFreq": "Aktuální takt CPU",
"cpuMaxFreq": "Maximální takt CPU",
"cpuUsage": "Využití CPU",
"crashed": "Crashnuté",
"dashboard": "Ovládací panel",
"delay-explained": "Služba/agent byla nedávno spuštěna a zpožďuje spuštění instance minecraft serveru.",
"host": "Hostitel",
"kill": "Zabít proces",
"killing": "Zabíjím proces...",
"lastBackup": "Poslední:",
"max": "Max",
"memUsage": "Využití paměti",
"motd": "MOTD",
"newServer": "Vytvořit nový server",
"nextBackup": "Další:",
"no-servers": "V současné době nejsou k dispozici žádné servery. Chcete-li začít, klikněte na",
"offline": "Offline",
"online": "Online",
"players": "Hráči",
"restart": "Restartovat",
"sendingCommand": "Odeslání příkazu",
"server": "Server",
"servers": "Servery",
"size": "Velikost složky serveru",
"start": "Start",
"starting": "Zpožděný start",
"status": "Stav",
"stop": "Zastavit",
"version": "Verze",
"welcome": "Vítejte v Crafty Controlleru",
"installing": "Instalace..."
},
"datatables": {
"i18n": {
"aria": {
"sortAscending": ": aktivace řazení sloupce vzestupně",
"sortDescending": ": aktivace seřazení sloupce sestupně"
},
"buttons": {
"collection": "Sbírka <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'/>",
"colvis": "Viditelnost sloupců",
"colvisRestore": "Obnovit viditelnost",
"copy": "Kopírovat",
"copyKeys": "Stisknutím kláves Ctrl nebo u2318 + C zkopírujete data tabulky do systémové schránky.<br><br>Chcete-li tuto zprávu zrušit, klikněte na ni nebo stiskněte klávesu ESC.",
"copySuccess": {
"1": "Zkopírován 1 řádek do schránky",
"2": "Zkopírovány 2 řádky do schránky",
"3": "Zkopírovány 3 řádky do schránky",
"4": "Zkopírovány 4 řádky do schránky",
"_": "Zkopírováno %d řádků do schránky"
},
"copyTitle": "Zkopírovat do schránky",
"csv": "CSV",
"excel": "Excel",
"pageLength": {
"1": "Zobrazit 1 řádek",
"2": "Zobrazit 2 řádky",
"3": "Zobrazit 3 řádky",
"4": "Zobrazit 4 řádky",
"-1": "Zobrazit všechny řádky",
"_": "Zobrazit %d řádků"
},
"pdf": "PDF",
"print": "Tisk"
},
"decimal": "",
"emptyTable": "V tabulce nejsou k dispozici žádné údaje",
"info": "Zobrazeno _START_ až _END_ z _TOTAL_ záznamů",
"infoEmpty": "Zobrazeno 0 až 0 z 0 záznamů",
"infoFiltered": "(filtrováno z _MAX_ celkových záznamů)",
"infoPostFix": "",
"lengthMenu": "Zobrazit položky _MENU_",
"loadingRecords": "Načítání...",
"paginate": {
"first": "První",
"last": "Poslední",
"next": "Další",
"previous": "Předchozí"
},
"processing": "Zpracování...",
"search": "Hledat:",
"select": {
"cells": {
"0": "Kliknutím na buňku ji vyberete",
"1": "Vybraná %d buňka",
"2": "Vybrané %d buňky",
"3": "Vybrané %d buňky",
"4": "Vybrané %d buňky",
"_": "Vybráno %d buněk"
},
"columns": {
"0": "Kliknutím na sloupec jej vyberete",
"1": "Vybraný %d sloupec",
"2": "Vybrané %d sloupce",
"3": "Vybrané %d sloupce",
"4": "Vybrané %d sloupce",
"_": "Vybráno %d sloupců"
},
"rows": {
"0": "Kliknutím na řádek jej vyberete",
"1": "Vybraný %d řádek",
"2": "Vybrané %d řádky",
"3": "Vybrané %d řádky",
"4": "Vybrané %d řádky",
"_": "Vybráno %d řádků"
}
},
"thousands": " ",
"zeroRecords": "Nebyly nalezeny žádné odpovídající záznamy"
}
},
"error": {
"contact": "Kontaktujte podporu Crafty přes Discord",
"embarassing": "Ach jo, no, to je trapné.",
"error": "Chyba!",
"eulaAgree": "Souhlasíte?",
"eulaMsg": "Musíte souhlasit s ",
"privMsg": "a ",
"eulaTitle": "Souhlas s EULA",
"agree": "Souhlasím",
"cancel": "Zrušit",
"fileTooLarge": "Odeslání se nezdařilo. Příliš velký nahraný soubor. Obraťte se na správce systému.",
"hereIsTheError": "Zde je chyba",
"internet": "Zjistili jsme, že počítač se spuštěným programem Crafty není připojen k internetu. Připojení klientů k serveru může být omezeno.",
"no-file": "Zdá se, že nemůžeme najít požadovaný soubor. Překontrolujte cestu. Má Crafty správné oprávnění?",
"noJava": "Server {} se nepodařilo spustit s kódem chyby: Zjistili jsme, že Java není nainstalována. Nainstalujte prosím Javu a poté spusťte server.",
"not-downloaded": "Zdá se, že nemůžeme najít váš spustitelný soubor. Bylo jeho stahování dokončeno? Jsou oprávnění nastavena na spustitelný soubor?",
"portReminder": "Zjistili jsme, že server {} byl spuštěn poprvé. Ujistěte se, že jste přesměrovali port {} přes váš směrovač/firewall, aby byl tento port vzdáleně přístupný z internetu.",
"start-error": "Server {} se nepodařilo spustit s kódem chyby: {}",
"terribleFailure": "Jaké strašné selhání!",
"superError": "Pro dokončení této akce musíte být super uživatel.",
"fileError": "Typ souboru musí být obrázek."
},
"footer": {
"allRightsReserved": "Všechna práva vyhrazena",
"copyright": "Autorská práva",
"version": "Verze"
},
"login": {
"forgotPassword": "Zapomenuté heslo",
"login": "Přihlásit se",
"password": "Heslo",
"username": "Uživatelské jméno"
},
"notify": {
"activityLog": "Záznamy o činnosti",
"backupComplete": "Zálohování serveru {} bylo úspěšně dokončeno",
"backupStarted": "Bylo spuštěno zálohování serveru {}",
"downloadLogs": "Stáhnout protokoly podpory?",
"finishedPreparing": "Dokončili jsme přípravu protokolů podpory. Klikněte na tlačítko Stáhnout pro stažení",
"logout": "Odhlásit se",
"preparingLogs": " Počkejte prosím, než připravíme vaše protokoly... Až budou připraveny, pošleme vám oznámení. U rozsáhlých zavádění to může chvíli trvat.",
"supportLogs": "Protokoly podpory"
},
"panelConfig": {
"adminControls": "Ovládání správce",
"allowedServers": "Povolené servery",
"assignedRoles": "Přidělené role",
"cancel": "Zrušit",
"clearComms": "Vymazat nevykonané příkazy",
"delete": "Smazat",
"edit": "Upravit",
"enabled": "Zapnuto",
"match": "Hesla musí být stejná",
"newRole": "Přidat novou roli",
"newUser": "Přidat nového uživatele",
"pageTitle": "Nastavení panelu",
"role": "Role",
"roles": "Role",
"roleUsers": "Uživatelé s rolí",
"save": "Uložit",
"superConfirm": "Postupujte pouze v případě, že chcete, aby měl tento uživatel přístup ke VŠEM (ke všem uživatelským účtům, serverům, nastavení panelu atd.). Může vám dokonce odebrat práva superuživatele.",
"superConfirmTitle": "Povolit superuživatele? Jste si jisti?",
"user": "Uživatel",
"users": "Uživatelé",
"loginImage": "Nahrajte obrázek na pozadí přihlašovací obrazovky.",
"backgroundUpload": "Nahrání pozadí",
"loginBackground": "Přihlašovací obrázek na pozadí",
"select": "Vyberte",
"selectImage": "Vyberte obrázek",
"preview": "Náhled"
},
"rolesConfig": {
"config": "Nastavení role",
"configDesc": "Zde můžete změnit nastavení své role.",
"configUpdate": "Poslední aktualizace: ",
"created": "Vytvořený: ",
"delRole": "Smazat roli",
"doesNotExist": "Nemůžete odstranit něco, co ještě neexistuje",
"pageTitle": "Upravit roli",
"pageTitleNew": "Nová role",
"permAccess": "Přístup?",
"permName": "Název oprávnění",
"permsServer": "Oprávnění, která má tato role pro tyto servery",
"roleConfigArea": "Oblast nastavení role",
"roleDesc": "Jak byste chtěl aby se tato role jmenovala?",
"roleName": "Název role: ",
"rolePerms": "Oprávnění role",
"roleServers": "Povolené servery",
"roleTitle": "Nastavení rolí",
"roleUserName": "Uživatelské jméno",
"roleUsers": "Uživatelé role: ",
"serverAccess": "Přístup?",
"serverName": "Název serveru",
"serversDesc": "servery, ke kterým má tato role přístup",
"selectManager": "Výběr manažera pro tuto roli"
},
"serverBackups": {
"backupAtMidnight": "Automatické zálohování o půlnoci?",
"backupNow": "Zálohovat nyní!",
"backupTask": "Bylo spuštěno zálohování.",
"cancel": "Zrušit",
"clickExclude": "Kliknutím vyberete výjimku",
"compress": "Komprimovat zálohu",
"confirm": "Potvrdit",
"confirmDelete": "Chcete tuto zálohu odstranit? Tuto akci nelze vrátit zpět.",
"confirmRestore": "Jste si jisti, že chcete provést obnovu z této zálohy. Všechny aktuální soubory serveru se změní na stav zálohy a nebude možné je obnovit.",
"currentBackups": "Aktuální zálohy",
"delete": "Smazat",
"destroyBackup": "Zničit zálohu \" + file_to_del + \"?",
"download": "Stáhnout",
"excludedBackups": "Vyloučené cesty: ",
"excludedChoose": "Vyberte cesty, které chcete ze zálohování vyloučit.",
"exclusionsTitle": "Vyloučení ze zálohování",
"maxBackups": "Maximální počet záloh",
"maxBackupsDesc": "Crafty neuloží více než N záloh a odstraní nejstarší (zadejte 0 pro zachování všech).",
"options": "Nastavení",
"path": "Cesta",
"restore": "Obnovit",
"restoring": "Obnovení zálohy. To může chvíli trvat. Buďte prosím trpěliví.",
"save": "Uložit",
"shutdown": "Vypnout server po dobu zálohování",
"size": "Velikost",
"storageLocation": "Umístění úložiště",
"storageLocationDesc": "Kam chcete ukládat zálohy?"
},
"serverConfig": {
"bePatientDelete": "Buďte prosím trpěliví, než odstraníme váš server z panelu Crafty. Tato obrazovka se za chvíli zavře.",
"bePatientDeleteFiles": "Buďte prosím trpěliví, než odstraníme váš server z panelu Crafty a všechny jeho soubory. Tato obrazovka se za chvíli zavře.",
"bePatientUpdate": "Prosím, buďte trpěliví, dokud server neaktualizujeme. Doba stahování se může lišit v závislosti na rychlosti vašeho internetu.<br /> Tato obrazovka se za chvíli aktualizuje",
"cancel": "Zrušit",
"crashTime": "Časový limit havárie",
"crashTimeDesc": "Jak dlouho bychom měli čekat, než budeme váš server považovat za havarovaný?",
"deleteFilesQuestion": "Odstranit soubory serveru z přístroje?",
"deleteFilesQuestionMessage": "Chcete, aby Crafty odstranil všechny soubory serveru z hostitelského počítače? <br><br><strong>To se týká i zálohování serverů.</strong>",
"deleteServer": "Odstranit server",
"deleteServerQuestion": "Odstranit server?",
"deleteServerQuestionMessage": "Opravdu chcete tento server odstranit? Po tomto kroku již není cesty zpět...",
"exeUpdateURL": "Adresa URL pro aktualizaci spustitelných souborů serveru",
"exeUpdateURLDesc": "Adresa URL pro přímé stahování aktualizací.",
"javaNoChange": "Nepřepisujte",
"javaVersion": "Přepsat aktuální verzi Javy",
"javaVersionDesc": "Pokud se chystáte přepsat Javu, ujistěte se, že je aktuální cesta k Javě v příkazu 'execution command' zabalena do uvozovek (výchozí proměnná 'java' je vyloučena).",
"noDelete": "Ne, vrať se zpět",
"noDeleteFiles": "Ne, stačí jen vyjmout z panelu",
"removeOldLogsAfter": "Odstranit staré protokoly po",
"removeOldLogsAfterDesc": "Kolik dní musí být soubor protokolu starý, aby byl smazán (0 je vypnuto)",
"save": "Uložit",
"sendingDelete": "Odstraňování serveru",
"sendingRequest": "Odeslání žádosti...",
"serverAutoStart": "Automatické spuštění serveru",
"serverAutostartDelay": "Zpoždění automatického spuštění serveru",
"serverAutostartDelayDesc": "Zpoždění před automatickým spuštěním (je-li povoleno níže)",
"serverCrashDetection": "Detekce pádu serveru",
"serverExecutable": "Spustitelný soubor serveru",
"serverExecutableDesc": "Spustitelný soubor pro server",
"serverExecutionCommand": "Příkaz pro spuštění serveru",
"serverExecutionCommandDesc": "Co se spustí ve skrytém terminálu",
"serverIP": "Adresa serveru",
"serverIPDesc": "IP adresa, ke které by se měl Crafty připojit pro statistiky (pokud máte problémy, zkuste místo 127.0.0.1 zadat skutečnou IP adresu).",
"serverLogLocation": "Umístění protokolu serveru",
"serverLogLocationDesc": "Cesta k souboru protokolu",
"serverName": "Název serveru",
"serverNameDesc": "Jak chcete aby se tento server jmenoval",
"serverPath": "Pracovní adresář serveru",
"serverPathDesc": "Absolutní úplná cesta (bez spustitelného souboru)",
"serverPort": "Port serveru",
"serverPortDesc": "Port Crafty should connect to for stats",
"serverStopCommand": "Příkaz pro zastavení serveru",
"serverStopCommandDesc": "Příkaz k odeslání programu pro jeho zastavení",
"showStatus": "Zobrazit na veřejné stavové stránce",
"stopBeforeDeleting": "Před odstraněním serveru jej prosím zastavte",
"update": "Aktualizovat spustitelný soubor",
"yesDelete": "Ano, smazat",
"yesDeleteFiles": "Ano, smazat soubory",
"shutdownTimeout": "Časový limit pro vypnutí",
"timeoutExplain1": "Jak dlouho bude Crafty čekat na vypnutí serveru po provedení příkazu",
"timeoutExplain2": "než proces ukončí."
},
"serverConfigHelp": {
"desc": "Zde můžete změnit konfiguraci serveru.",
"perms": [
"Doporučujeme <code>NEMĚNIT</code> cesty serveru spravované Craftym.",
"Změna cest <code>MŮŽE</code> něco rozbít, zejména v operačních systémech typu Linux, kde jsou práva k souborům více uzamčena.",
"<br /><br/>",
"Pokud máte pocit, že musíte změnit místo, kde je server umístěn, můžete tak učinit, pokud dáte uživateli \"crafty\" oprávnění ke čtení / zápisu k cestě k serveru.",
"<br />",
"<br />",
"V systému Linux to nejlépe provedete následujícím příkazem:<br />",
"<code>",
" sudo chown crafty:crafty /cesta/k/vašemu/serveru -R<br />",
" sudo chmod 2775 /cesta/k/vašemu/serveru -R<br />",
"</code>"
],
"title": "Oblast nastavení serveru"
},
"serverDetails": {
"backup": "Záloha",
"config": "Nastavení",
"files": "Soubory",
"logs": "Protokoly",
"playerControls": "Správa hráčů",
"schedule": "Harmonogram",
"serverDetails": "Podrobnosti o serveru",
"terminal": "Terminál",
"metrics": "Metrika",
"reset": "Obnovit posuvník",
"filter": "Filtrovat protokoly",
"filterList": "Filtrovaná slova"
},
"serverFiles": {
"clickUpload": "Klikněte sem a vyberte své soubory",
"close": "Zavřít",
"createDir": "Vytvořit složku",
"createDirQuestion": "Jaký chcete pojmenovat novou složku?",
"createFile": "Vytvořit soubor",
"createFileQuestion": "Jaké jméno chcete zvolit pro nový soubor?",
"default": "Výchozí",
"delete": "Smazat",
"deleteItemQuestion": "Jste si jisti, že chcete odstranit \" + name + \"?",
"deleteItemQuestionMessage": "Odstraňujete \\\"\" + path + \"\\\"!<br/><br/>Tato akce bude nevratná a navždy ztracená!",
"download": "Stáhnout",
"editingFile": "Upravit soubor",
"error": "Chyba při získání souborů",
"fileReadError": "Chyba při čtení souboru",
"files": "Soubory",
"keybindings": "Klávesové zkratky",
"loadingRecords": "Načítání souborů...",
"noDelete": "Ne",
"noscript": "Správce souborů nefunguje bez JavaScriptu",
"rename": "Přejmenovat",
"renameItemQuestion": "Jaký by měl být nový název?",
"save": "Uložit",
"size": "Přepnout velikost editoru",
"stayHere": "NEOPOUŠTĚJTE TUTO STRÁNKU!",
"unsupportedLanguage": "Varování: Tento typ souboru není podporovaný",
"unzip": "Rozbalit",
"upload": "Nahrát",
"uploadTitle": "Nahrát soubory do: ",
"waitUpload": "Počkejte prosím, než se vaše soubory nahrají... To může chvíli trvat.",
"yesDelete": "Ano, chápu následky"
},
"serverPlayerManagement": {
"bannedPlayers": "Zabanovaní hráči",
"loadingBannedPlayers": "Načítání zabanovaných hráčů",
"players": "Hráči"
},
"serverScheduleConfig": {
"backup": "Zálohovat server",
"select": "Základní / Cron / Řetězová reakce",
"basic": "Základní",
"children": "Propojené dětské úlohy:",
"command": "Příkaz",
"command-explain": "Jaký příkaz máme provést? Neuvádějte znak '/'",
"cron": "Cron",
"cron-explain": "Zadejte řetězec cronu -- POZNÁMKA: 0 = Pondělí v poslední možnosti.",
"custom": "Vlastní příkaz",
"days": "Dny",
"enabled": "Zapnuto",
"hours": "Hodiny",
"interval": "Interval",
"interval-explain": "Jak často chcete, aby se tento plán prováděl?",
"minutes": "Minuty",
"offset": "Posunutí prodlevy",
"offset-explain": "Jak dlouho bychom měli čekat na spuštění této úlohy po spuštění první úlohy? (sekund)",
"one-time": "Odstranit po provedení",
"parent": "Výběr plánu rodiče",
"parent-explain": "Který plán by měl spustit tento?",
"reaction": "Reakce",
"restart": "Restartovat server",
"start": "Spustit server",
"stop": "Vypnout server",
"time": "Čas",
"time-explain": "V kolik hodin chcete, aby byl váš plán spuštěn?"
},
"serverSchedules": {
"scheduledTasks": "Naplánované úlohy",
"create": "Vytvořit nový plán",
"name": "Název",
"action": "Akce",
"command": "Příkaz",
"interval": "Interval",
"nextRun": "Příští spuštění",
"enabled": "Zapnuto",
"edit": "Upravit",
"every": "Každý",
"yes": "Ano",
"no": "Ne",
"cron": "Řetězec Crong",
"details": "Podrobnosti o plánu",
"child": "Dítě plánu s ID",
"areYouSure": "Odstranění naplánované úlohy?",
"close": "Zavřít",
"delete": "Odstranit",
"cancel": "Zrušit",
"cannotSee": "Nevidíte všechno?",
"cannotSeeOnMobile": "Kliknutím na naplánovanou úlohu získáte podrobné informace.",
"confirm": "Potvrdit",
"confirmDelete": "Chcete tuto naplánovanou úlohu odstranit? Tuto akci nelze vrátit zpět."
},
"serverStats": {
"cpuUsage": "Využití CPU",
"description": "Popis",
"errorCalculatingUptime": "Chyba při výpočtu doby provozu",
"memUsage": "Využití paměti",
"offline": "Offline",
"online": "Online",
"players": "Hráči",
"serverStarted": "Server spuštěn",
"serverStatus": "Stav serveru",
"serverTime": "UTC čas",
"serverTimeZone": "Časové pásmo serveru",
"serverUptime": "Doba provozu serveru",
"starting": "Zpožděný start",
"unableToConnect": "Nelze se připojit",
"version": "Verze"
},
"serverTerm": {
"commandInput": "Zadejte příkaz",
"delay-explained": "Služba/agent byla nedávno spuštěna a zpožďuje spuštění instance minecraft serveru.",
"importing": "Importování...",
"restart": "Restartovat",
"sendCommand": "Odeslat příkaz",
"start": "Spustit",
"starting": "Zpožděný start",
"stop": "Zastavit",
"stopScroll": "Zastavit automatické posouvání",
"updating": "Aktualizace...",
"installing": "Instalace..."
},
"serverMetrics": {
"resetZoom": "Reset Zoom",
"zoomHint1": "Chcete-li graf přiblížit, podržte klávesu Shift a poté použijte kolečko myši.",
"zoomHint2": "Případně můžete podržet klávesu Shift a kliknout a přetáhnout oblast, kterou chcete přiblížit."
},
"serverWizard": {
"absoluteServerPath": "Absolutní cesta k serveru",
"absoluteZipPath": "Absolutní cesta k serveru",
"addRole": "Přidání serveru k existujícím rolím",
"autoCreate": "Pokud nebude vybrána žádná, Crafty ji vyrobí!",
"bePatient": "Buďte prosím trpěliví, protože musíme ' + (importing ? 'importovat' : 'stáhnout') + ' server",
"buildServer": "Sestavit server!",
"clickRoot": "Klikněte zde pro výběr kořenového adresáře",
"close": "Zavřít",
"defaultPort": "25565 výchozí hodnota",
"downloading": "Stahování serveru...",
"explainRoot": "Klikněte na tlačítko níže a vyberte kořenový adresář vašeho serveru z archivu.",
"importing": "Importování serveru...",
"importServer": "Importování existujícího serveru",
"importServerButton": "Importovat server!",
"importZip": "Imporovat ze souboru Zip",
"uploadZip": "Nahrání souboru Zip pro importování serveru",
"maxMem": "Maximální paměť",
"minMem": "Minimální paměť",
"myNewServer": "Nový server",
"newServer": "Vytvořit nový server",
"quickSettings": "Rychlé nastavení",
"quickSettingsDescription": "Nebojte se, můžete je změnit později.",
"resetForm": "Obnovit nastavení formuláře",
"save": "Uložit",
"selectRole": "Vyberte roli(e)",
"selectRoot": "Vyberte kořenový adresář archivu",
"selectType": "Typ serveru (Vanilla, Servery, Modované atd.)",
"selectServer": "Vyberte server",
"selectVersion": "Vyberte verzi",
"selectZipDir": "Vyberte adresář v archivu, ze kterého mají být soubory rozbaleny.",
"serverJar": "Soubor spustitelný serverem",
"serverName": "Název serveru",
"serverPath": "Cesta k serveru",
"serverPort": "Port serveru",
"serverType": "Typ serveru",
"serverSelect": "Výběr serveru",
"serverVersion": "Verze serveru",
"sizeInGB": "Velikost v GB",
"zipPath": "Cesta k serveru"
},
"sidebar": {
"contribute": "Přispět",
"credits": "Zásluhy",
"dashboard": "Ovládací panel",
"documentation": "Dokumentace",
"navigation": "Navigace",
"newServer": "Vytvořit nový server",
"servers": "Servery"
},
"userConfig": {
"apiKey": "Klíče API",
"auth": "Autorizovaný? ",
"config": "Nastavení",
"configArea": "Uživatelská nastavení",
"configAreaDesc": "Zde můžete změnit všechna nastavení uživatele.",
"confirmDelete": "Jste si jisti, že chcete tohoto uživatele odstranit? Tato akce je nevratná.",
"craftyPermDesc": "Oprávnění tohoto uživatele ",
"craftyPerms": "Oprávnění: ",
"created": "Vytvořeno: ",
"deleteUser": "Smazat uživatele: ",
"deleteUserB": "Smazat uživatele",
"delSuper": "Super uživatele nelze odstranit",
"enabled": "Povolen",
"gravDesc": "Tento e-mail je určen výhradně pro použití se službou Gravatar™. Crafty nebude v žádném případě používat tento e-mail k ničemu jinému než k vyhledávání vašeho Gravataru™.",
"gravEmail": "Gravatar™ E-mail",
"lastIP": "Poslední IP: ",
"lastLogin": "Poslední přihlášení: ",
"lastUpdate": "Poslední aktualizace: ",
"leaveBlank": "Chcete-li upravit uživatele beze změny hesla, ponechte pole prázdné.",
"member": "Člen?",
"notExist": "Nemůžete odstranit něco, co neexistuje!",
"pageTitle": "Upravit uživatele",
"pageTitleNew": "Vytvořit uživatele",
"password": "Nové heslo",
"permName": "Název povolení",
"repeat": "Zopakujte heslo",
"roleName": "Název role",
"super": "Super uživatel",
"userLang": "Jazyk uživatele",
"userTheme": "Motiv UI",
"userName": "Uživatelské jméno",
"userNameDesc": "Jak chcete aby se tento uživatel jmenoval?",
"userRoles": "Role uživatele",
"userRolesDesc": "Role, jejichž je tento uživatel členem.",
"userSettings": "Nastavení uživatele",
"uses": "Počet povolených použití (-1==bez omezení)",
"manager": "Správce",
"selectManager": "Vyberte Správce pro uživatele"
}
}

View File

@ -213,6 +213,8 @@
"assignedRoles": "Assigned Roles",
"cancel": "Cancel",
"clearComms": "Clear Un-executed Commands",
"select": "Select",
"apply": "Apply",
"delete": "Delete",
"edit": "Edit",
"enabled": "Enabled",
@ -228,12 +230,20 @@
"superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User",
"users": "Users",
"title": "Crafty Configuration"
},
"customLogin": {
"customLoginPage": "Customise the Login Page",
"loginImage": "Upload a background image for the login screen.",
"backgroundUpload": "Background Upload",
"loginBackground": "Login Background Image",
"loginOpacity": "Select the Login Window Opacity",
"select": "Select",
"apply": "Apply",
"delete": "Delete",
"selectImage": "Select an image",
"preview": "Preview"
"preview": "Preview",
"pageTitle": "Custom Login Page"
},
"rolesConfig": {
"config": "Role Config",

View File

@ -1,20 +1,27 @@
<?xml version="1.0"?>
<Container version="2">
<Beta>True</Beta>
<Beta>False</Beta>
<Name>Crafty-4</Name>
<Repository>registry.gitlab.com/crafty-controller/crafty-4:latest</Repository>
<Registry>registry.gitlab.com/crafty-controller/crafty-4</Registry>
<Repository>arcadiatechnology/crafty-4</Repository>
<Registry>https://hub.docker.com/r/arcadiatechnology/crafty-4</Registry>
<Network>bridge</Network>
<MyIP/>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://discord.gg/9VJPhCE</Support>
<Project>https://craftycontrol.com/</Project>
<Overview>Crafty 4 is the next iteration of our Minecraft Server Wrapper / Controller / Launcher. Boasting a clean new look, rebuilt from the ground up. Crafty 4 brings a whole host of new features such as Bedrock support. With SteamCMD support on the way!&#xD;
Default login Credentrails are username: "admin" password: "crafty"&#xD;
Crafty 4 is the successor of Crafty Controller, the Docker image is no longer maintained on DockerHub. (now on GitLab)&#xD;
For official support join the Discord: https://discord.gg/9VJPhCE&#xD;
For migration from 3.x please refer to the documentation: https://wiki.craftycontrol.com/en/4/&#xD;
<License>GNU GPL V3</License>
<Branch>
<Tag>latest</Tag>
<TagDescription>Latest version of Crafty, which should be used for production purposes as it is the most stable</TagDescription>
</Branch>
<Branch>
<Tag>dev</Tag>
<TagDescription>Development version of Crafty, only generally used for testing purposes, because of its less stable nature</TagDescription>
</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>
<Category>GameServers: Other:</Category>
<WebUI>https://[IP]:[PORT:8443]/</WebUI>
@ -26,12 +33,6 @@ For migration from 3.x please refer to the documentation: https://wiki.craftycon
<DateInstalled/>
<DonateText>Help to support Crafty on Kofi</DonateText>
<DonateLink>https://ko-fi.com/arcadiatech</DonateLink>
<Description>Crafty 4 is the next iteration of our Minecraft Server Wrapper / Controller / Launcher. Boasting a clean new look, rebuilt from the ground up. Crafty 4 brings a whole host of new features such as Bedrock support. With SteamCMD support on the way!&#xD;
Default login Credentrails are username: "admin" password: "crafty"&#xD;
Crafty 4 is the successor of Crafty Controller, the Docker image is no longer maintained on DockerHub. (now on GitLab)&#xD;
For official support join the Discord: https://discord.gg/9VJPhCE&#xD;
For migration from 3.x please refer to the documentation: https://wiki.craftycontrol.com/en/4/&#xD;
</Description>
<Networking>
<Mode>bridge</Mode>
<Publish>

View File

@ -219,7 +219,6 @@ if __name__ == "__main__":
Console.debug(f"Execution Mode: {running_mode}")
Console.debug(f"Application path : '{application_path}'")
controller.clear_unexecuted_commands()
controller.clear_support_status()
crafty_prompt = MainPrompt(