Merge branch 'dev' into feature/steamcmd

This commit is contained in:
Zedifus 2022-12-01 00:17:55 +00:00
commit 7b07ac088b
24 changed files with 497 additions and 138 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ venv.bak/
.idea/
/imports/
/servers/
/app/frontend/static/assets/images/auth/custom/
/backups/
/temp/
/docker/servers/

View File

@ -1,13 +1,14 @@
# Changelog
## --- [4.0.17] - 2022/TBD
## --- [4.0.17] - 2022/11/30
### New features
TBD
- Automate forge install process through Crafty server creation for Forge server version 1.16 and greater. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/495))
- Tooltip for server port on dashboard. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/496))
- Custom login image backgrounds. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/494))
### Bug fixes
TBD
- Fix no port on bedrock server creation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/493))
### Tweaks
TBD
### Lang
TBD
- Docker🐋 | Update image base to Ubuntu 22.04 Jammy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/497))<br>
*(OpenJDK16 Removed, no jammy backport)*
<br><br>
## --- [4.0.16] - 2022/10/23

View File

@ -1,4 +1,4 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND="noninteractive"
@ -24,7 +24,6 @@ RUN apt-get update \
default-jre \
openjdk-8-jre-headless \
openjdk-11-jre-headless \
openjdk-16-jre-headless \
openjdk-17-jre-headless \
&& apt-get autoremove \
&& apt-get clean

View File

@ -94,6 +94,14 @@ class ManagementController:
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)

View File

@ -119,9 +119,15 @@ class ServersController(metaclass=Singleton):
return srv.stats_helper.set_import()
@staticmethod
def finish_import(server_id):
def finish_import(server_id, forge=False):
srv = ServersController().get_server_instance_by_id(server_id)
return srv.stats_helper.finish_import()
# This is where we start the forge installerr
if forge:
srv.run_threaded_server(
HelperUsers.get_user_id_by_name("system"), forge_install=True
)
else:
srv.stats_helper.finish_import()
@staticmethod
def get_import_status(server_id):

View File

@ -187,31 +187,28 @@ class ServerJars:
# open a file stream
with requests.get(fetch_url, timeout=2, stream=True) as r:
success = False
try:
with open(path, "wb") as output:
shutil.copyfileobj(r.raw, output)
ServersController.finish_import(server_id)
# If this is the newer forge version we will run the installer
if server == "forge" and int(version.split(".")[1]) > 15:
ServersController.finish_import(server_id, True)
else:
ServersController.finish_import(server_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
return True
success = True
except Exception as e:
logger.error(f"Unable to save jar to {path} due to error:{e}")
ServersController.finish_import(server_id)
server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
return False
for user in server_users:
self.helper.websocket_helper.broadcast_user(
user, "notification", "Executable download finished"
)
time.sleep(3)
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
return success

View File

@ -43,6 +43,7 @@ class AuditLog(BaseModel):
# **********************************************************************************
class CraftySettings(BaseModel):
secret_api_key = CharField(default="")
login_photo = CharField(default="login_1.jpg")
class Meta:
table_name = "crafty_settings"
@ -254,6 +255,19 @@ class HelpersManagement:
)
return settings[0].secret_api_key
@staticmethod
def get_login_image():
settings = CraftySettings.select(CraftySettings.login_photo).where(
CraftySettings.id == 1
)
return settings[0].login_photo
@staticmethod
def set_login_image(photo):
CraftySettings.update({CraftySettings.login_photo: photo}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
# Schedules Methods
# **********************************************************************************

View File

@ -75,6 +75,7 @@ class Controller:
timezone=str(tz)
)
self.first_login = False
self.cached_login = self.management.get_login_image()
self.support_scheduler.start()
@staticmethod
@ -486,17 +487,32 @@ class Controller:
return False
if Helpers.is_os_windows():
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f'-jar "{server_file}" nogui'
)
# Let's check for and setup for install server commands
if server == "forge":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f'-jar "{server_file}" --installServer'
)
else:
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f'-jar "{server_file}" nogui'
)
else:
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f"-jar {server_file} nogui"
)
if server == "forge":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f"-jar {server_file} --installServer"
)
else:
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
f"-jar {server_file} nogui"
)
server_log_file = "./logs/latest.log"
server_stop = "stop"

View File

@ -202,12 +202,15 @@ class ServerInstance:
# remove the scheduled job since it's ran
return self.server_scheduler.remove_job(str(self.server_id))
def run_threaded_server(self, user_id):
def run_threaded_server(self, user_id, forge_install=False):
# start the server
self.server_thread = threading.Thread(
target=self.start_server,
daemon=True,
args=(user_id,),
args=(
user_id,
forge_install,
),
name=f"{self.server_id}_server_thread",
)
self.server_thread.start()
@ -292,13 +295,13 @@ class ServerInstance:
logger.critical(f"Unable to write/access {self.server_path}")
Console.critical(f"Unable to write/access {self.server_path}")
def start_server(self, user_id):
def start_server(self, user_id, forge_install=False):
if not user_id:
user_lang = self.helper.get_setting("language")
else:
user_lang = HelperUsers.get_user_lang_by_id(user_id)
if self.stats_helper.get_import_status():
if self.stats_helper.get_import_status() and not forge_install:
if user_id:
self.helper.websocket_helper.broadcast_user(
user_id,
@ -341,7 +344,9 @@ class ServerInstance:
"eula= true",
"eula =true",
]
# If this is a forge installer we're running we can bypass the eula checks.
if forge_install is True:
e_flag = True
if not e_flag and self.settings["type"] == "minecraft-java":
if user_id:
self.helper.websocket_helper.broadcast_user(
@ -422,6 +427,9 @@ class ServerInstance:
).format(self.name, ex)
},
)
if forge_install:
# Reset import status if failed while forge installing
self.stats_helper.finish_import()
return False
else:
@ -460,6 +468,9 @@ class ServerInstance:
).format(self.name, ex)
},
)
if forge_install:
# Reset import status if failed while forge installing
self.stats_helper.finish_import()
return False
out_buf = ServerOutBuf(self.helper, self.process, self.server_id)
@ -540,6 +551,10 @@ class ServerInstance:
self.detect_crash, "interval", seconds=30, id=f"c_{self.server_id}"
)
# If this is a forge install we'll call the watcher to do the things
if forge_install:
self.forge_install_watcher()
def check_internet_thread(self, user_id, user_lang):
if user_id:
if not Helpers.check_internet():
@ -553,6 +568,78 @@ class ServerInstance:
},
)
def forge_install_watcher(self):
# Enter for install if that parameter is true
while True:
# We'll watch the process
if self.process.poll() is None:
# IF process still has not exited we'll keep looping
time.sleep(5)
Console.debug("Installing Forge...")
else:
# 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)
# 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")
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.\-]+)\/\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.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")
# We'll update the server with the new information now.
HelperServers.update_server(server_obj)
self.stats_helper.finish_import()
server_users = PermissionsServers.get_server_user_list(self.server_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(
user, "send_start_reload", {}
)
break
def stop_crash_detection(self):
# This is only used if the crash detection settings change
# while the server is running.

View File

@ -352,6 +352,38 @@ class AjaxHandler(BaseHandler):
self.controller.clear_unexecuted_commands()
return
elif page == "select_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo == "login_1.jpg":
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = f"{photo}"
else:
self.controller.management.set_login_image(f"custom/{photo}")
self.controller.cached_login = f"custom/{photo}"
return
elif page == "delete_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo and photo != "login_1.jpg":
os.remove(
os.path.join(
self.controller.project_root,
f"app/frontend/static/assets/images/auth/custom/{photo}",
)
)
current = self.controller.cached_login
split = current.split("/")
if len(split) == 1:
current_photo = current
else:
current_photo = split[1]
if current_photo == photo:
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = "login_1.jpg"
return
elif page == "kill":
if not permissions["Commands"] in user_perms:
if not superuser:

View File

@ -849,6 +849,32 @@ 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"]

View File

@ -48,6 +48,7 @@ class PublicHandler(BaseHandler):
template = "public/404.html"
if page == "login":
page_data["background"] = self.controller.cached_login
template = "public/login.html"
elif page == 404:

View File

@ -457,6 +457,9 @@ class ServerHandler(BaseHandler):
server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument("server_name", ""))
port = bleach.clean(self.get_argument("port", ""))
if not port:
port = 19132
if int(port) < 1 or int(port) > 65535:
self.redirect(
"/panel/error?error=Constraint Error: "

View File

@ -152,65 +152,46 @@ class UploadHandler(BaseHandler):
return
self.do_upload = True
if superuser:
exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
if not superuser:
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"superError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
elif api_key is not None:
exec_user_server_permissions = (
self.controller.server_perms.get_api_key_permissions_list(
api_key, server_id
)
return
if not self.request.headers.get("X-Content-Type", None).startswith(
"image/"
):
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
else:
exec_user_server_permissions = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get("X-ServerId", None)
if server_id is None:
logger.warning("Server ID not found in upload handler call")
Console.warning("Server ID not found in upload handler call")
self.do_upload = False
return
if user_id is None:
logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call")
self.do_upload = False
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
logger.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
Console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False
path = self.request.headers.get("X-Path", None)
path = os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
):
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
Console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
self.do_upload = False
if self.do_upload:
try:
self.f = open(full_path, "wb")

View File

@ -28362,11 +28362,6 @@ div.tagsinput span.tag a {
min-height: 100vh;
}
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover;

View File

@ -26992,11 +26992,6 @@ div.tagsinput span.tag a {
min-height: 100vh;
}
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover;

View File

@ -168,22 +168,11 @@
<td draggable="false" id="controls{{server['server_data']['server_id']}}" class="actions_serverlist">
{% if server['user_command_permission'] %}
{% if server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% if server['stats']['importing'] and server['stats']['running'] %}
<!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class=""><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'installing',
data['lang']) }}</i></a>
{% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class=""><i
@ -198,6 +187,21 @@
<a data-id="{{server['server_data']['server_id']}}" class=""><i class="fa fa-spinner fa-spin"></i>
{{ translate('serverTerm', 'importing',
data['lang']) }}</a>
{% elif server['stats']['running'] %}
<a data-id="{{server['server_data']['server_id']}}" class="stop_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'stop' , data['lang']) }}">
<i class="fas fa-stop"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="restart_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'restart' , data['lang']) }}">
<i class="fas fa-sync"></i>
</a> &nbsp;
<a data-id="{{server['server_data']['server_id']}}" class="kill_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'kill' , data['lang']) }}">
<i class="fas fa-skull"></i>
</a> &nbsp;
{% else %}
<a data-id="{{server['server_data']['server_id']}}" class="play_button" data-toggle="tooltip"
title="{{ translate('dashboard', 'start' , data['lang']) }}">
@ -275,22 +279,27 @@
</td>
<td draggable="false" 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 %}
<span class="port" data-toggle="tooltip" title="{{
server['server_data']['server_port'] }}">
{% 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 %}
</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"

View File

@ -229,6 +229,69 @@
</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>
{% end %}
</div>
</div>
@ -279,6 +342,13 @@
$('.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>
@ -307,6 +377,70 @@
},
});
})
$('.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

@ -124,7 +124,7 @@ data['lang']) }}{% end %}
<div class="form-group">
<label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang'])
}}</label>
<select class="form-select form-control form-control-lg select-css" id="language"
<select class="form-select form-control form-control-lg select-css" id="theme"
name="theme" form="user_form">
<option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option>
{% for theme in data['themes'] %}

View File

@ -62,7 +62,21 @@
</span>
</div>
{% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['server_stats']['updating']%}
{% if data['importing'] and data['server_stats']['running']%}
<div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;"
class="btn btn-warning m-1 flex-grow-1 disabled"><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{translate('serverTerm', 'installing', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;"
class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart',
data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;"
class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang'])
}}</button>
</div>
{% elif data['server_stats']['updating']%}
<div id="update_control_buttons"
class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0"
style="visibility: visible">

View File

@ -60,6 +60,11 @@
.login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}");
background-size: cover;
}
</style>
{% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post">

View File

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

View File

@ -97,7 +97,8 @@
"status": "Status",
"stop": "Stop",
"version": "Version",
"welcome": "Welcome to Crafty Controller"
"welcome": "Welcome to Crafty Controller",
"installing": "Installing..."
},
"datatables": {
"i18n": {
@ -181,7 +182,9 @@
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"start-error": "Server {} failed to start with error code: {}",
"terribleFailure": "What a Terrible Failure!"
"terribleFailure": "What a Terrible Failure!",
"superError": "You must be a super user to complete this action.",
"fileError": "File type must be an image."
},
"footer": {
"allRightsReserved": "All rights reserved",
@ -224,7 +227,13 @@
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel settings, etc.). They can even revoke your superuser rights.",
"superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User",
"users": "Users"
"users": "Users",
"loginImage": "Upload a background image for the login screen.",
"backgroundUpload": "Background Upload",
"loginBackground": "Login Background Image",
"select": "Select",
"selectImage": "Select an image",
"preview": "Preview"
},
"rolesConfig": {
"config": "Role Config",
@ -481,7 +490,8 @@
"starting": "Delayed-Start",
"stop": "Stop",
"stopScroll": "Stop Auto Scrolling",
"updating": "Updating..."
"updating": "Updating...",
"installing": "Installing..."
},
"serverMetrics": {
"resetZoom": "Reset Zoom",

View File

@ -97,7 +97,8 @@
"status": "Statut",
"stop": "Arrêter",
"version": "Version",
"welcome": "Bienvenue sur Crafty Controller"
"welcome": "Bienvenue sur Crafty Controller",
"installing": "Installation ..."
},
"datatables": {
"i18n": {
@ -450,7 +451,13 @@
"starting": "Démarrage retardé",
"stop": "Arrêter",
"stopScroll": "Stopper l'Auto Défilement",
"updating": "Mise à Jour ..."
"updating": "Mise à Jour ...",
"installing": "Installation ..."
},
"serverMetrics": {
"resetZoom": "Reset Zoom",
"zoomHint1": "Pour zoomer le graphique, maintenir la touche SHIFT et utiliser la molette de la souris.",
"zoomHint2": "Il est aussi possible de maintenir la touche SHIFT puis de clicker et déplacer la zone sur laquelle zoomer."
},
"serverWizard": {
"absoluteServerPath": "Chemin absolu de Votre Serveur",