Merge branch 'dev' into tweak/master-server-dir-config

This commit is contained in:
Zedifus 2023-02-11 19:47:01 +00:00
commit c151e5f3a7
20 changed files with 287 additions and 185 deletions

View File

@ -1,13 +1,18 @@
# Changelog # Changelog
## --- [4.0.21] - 2023/TBD ## --- [4.0.21] - 2023/TBD
### New features ### New features
TBD - Add better feedback for uploads with a progress bar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546))
- Add ignored exit codes for crash detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/553))
### Bug fixes ### Bug fixes
TBD - Fix exception related to page data on server start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/544))
- Fix logical issue with uploading dynamic files ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/555))
- Fix backups failing by correctly using tz objects ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/556))
### Tweaks ### Tweaks
TBD - Cleanup authentication helpers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/545))
- Optimize file upload progress WS ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546))
- Truncate sidebar servers to a max of 10 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/552))
### Lang ### Lang
TBD - Add additional translations to backups page strings ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/543))
<br><br> <br><br>
## --- [4.0.20] - 2023/01/29 ## --- [4.0.20] - 2023/01/29

View File

@ -40,6 +40,7 @@ class Servers(BaseModel):
show_status = BooleanField(default=1) show_status = BooleanField(default=1)
created_by = IntegerField(default=-100) created_by = IntegerField(default=-100)
shutdown_timeout = IntegerField(default=60) shutdown_timeout = IntegerField(default=60)
ignored_exits = CharField(default="0")
class Meta: class Meta:
table_name = "servers" table_name = "servers"

View File

@ -13,7 +13,6 @@ logger = logging.getLogger(__name__)
class Authentication: class Authentication:
def __init__(self, helper): def __init__(self, helper):
self.helper = helper self.helper = helper
self.secret = "my secret"
try: try:
self.secret = ManagementController.get_crafty_api_key() self.secret = ManagementController.get_crafty_api_key()
if self.secret == "": if self.secret == "":

View File

@ -1023,7 +1023,7 @@ class Helpers:
for item in file_list: for item in file_list:
if os.path.isdir(os.path.join(folder, item)): if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item) dir_list.append(item)
elif str(item) != "crafty.sqlite": elif str(item) != self.ignored_names:
unsorted_files.append(item) unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted( file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold unsorted_files, key=str.casefold
@ -1064,7 +1064,7 @@ class Helpers:
for item in file_list: for item in file_list:
if os.path.isdir(os.path.join(folder, item)): if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item) dir_list.append(item)
elif str(item) != "crafty.sqlite": elif str(item) != self.ignored_names:
unsorted_files.append(item) unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted( file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold unsorted_files, key=str.casefold

View File

@ -12,6 +12,8 @@ import html
import urllib.request import urllib.request
import glob import glob
from zoneinfo import ZoneInfo
# TZLocal is set as a hidden import on win pipeline # TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone from tzlocal import get_localzone
from tzlocal.utils import ZoneInfoNotFoundError from tzlocal.utils import ZoneInfoNotFoundError
@ -136,7 +138,7 @@ class ServerInstance:
logger.error( logger.error(
"Could not capture time zone from system. Falling back to Europe/London" "Could not capture time zone from system. Falling back to Europe/London"
) )
self.tz = "Europe/London" self.tz = ZoneInfo("Europe/London")
self.server_scheduler = BackgroundScheduler(timezone=str(self.tz)) self.server_scheduler = BackgroundScheduler(timezone=str(self.tz))
self.server_scheduler.start() self.server_scheduler.start()
self.backup_thread = threading.Thread( self.backup_thread = threading.Thread(
@ -732,8 +734,11 @@ class ServerInstance:
self.server_thread.join() self.server_thread.join()
def stop_server(self): def stop_server(self):
if self.settings["stop_command"]: running = self.check_running()
self.send_command(self.settings["stop_command"]) if not running:
logger.info(f"Can't stop server {self.name} if it's not running")
Console.info(f"Can't stop server {self.name} if it's not running")
return
if self.settings["crash_detection"]: if self.settings["crash_detection"]:
# remove crash detection watcher # remove crash detection watcher
logger.info(f"Removing crash watcher for server {self.name}") logger.info(f"Removing crash watcher for server {self.name}")
@ -744,14 +749,11 @@ class ServerInstance:
f"Removing crash watcher for server {self.name} failed. " f"Removing crash watcher for server {self.name} failed. "
f"Assuming it was never started." f"Assuming it was never started."
) )
if self.settings["stop_command"]:
self.send_command(self.settings["stop_command"])
else: else:
# windows will need to be handled separately for Ctrl+C # windows will need to be handled separately for Ctrl+C
self.process.terminate() self.process.terminate()
running = self.check_running()
if not running:
logger.info(f"Can't stop server {self.name} if it's not running")
Console.info(f"Can't stop server {self.name} if it's not running")
return
i = 0 i = 0
# caching the name and pid number # caching the name and pid number
@ -921,7 +923,7 @@ class ServerInstance:
if running: if running:
return return
# check the exit code -- This could be a fix for /stop # check the exit code -- This could be a fix for /stop
if self.process.returncode == 0: if str(self.process.returncode) in self.settings["ignored_exits"].split(","):
logger.warning( logger.warning(
f"Process {self.process.pid} exited with code " f"Process {self.process.pid} exited with code "
f"{self.process.returncode}. This is considered a clean exit" f"{self.process.returncode}. This is considered a clean exit"

View File

@ -1588,6 +1588,8 @@ class PanelHandler(BaseHandler):
crash_detection = int(float(self.get_argument("crash_detection", "0"))) crash_detection = int(float(self.get_argument("crash_detection", "0")))
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0"))) logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
java_selection = self.get_argument("java_selection", None) java_selection = self.get_argument("java_selection", None)
# make sure there is no whitespace
ignored_exits = self.get_argument("ignored_exits", "").replace(" ", "")
# subpage = self.get_argument('subpage', None) # subpage = self.get_argument('subpage', None)
server_id = self.check_server_id() server_id = self.check_server_id()
@ -1672,6 +1674,7 @@ class PanelHandler(BaseHandler):
server_obj.auto_start = auto_start server_obj.auto_start = auto_start
server_obj.crash_detection = crash_detection server_obj.crash_detection = crash_detection
server_obj.logs_delete_after = logs_delete_after server_obj.logs_delete_after = logs_delete_after
server_obj.ignored_exits = ignored_exits
failed = False failed = False
for servers in self.controller.servers.failed_servers: for servers in self.controller.servers.failed_servers:
if servers["server_id"] == int(server_id): if servers["server_id"] == int(server_id):

View File

@ -183,6 +183,7 @@ class ServerHandler(BaseHandler):
"version_data": "version_data_here", # TODO "version_data": "version_data_here", # TODO
"user_data": exec_user, "user_data": exec_user,
"show_contribute": self.helper.get_setting("show_contribute_link", True), "show_contribute": self.helper.get_setting("show_contribute_link", True),
"background": self.controller.cached_login,
"lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]), "lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]),
"lang_page": Helpers.get_lang_page( "lang_page": Helpers.get_lang_page(
self.controller.users.get_user_lang_by_id(exec_user["user_id"]) self.controller.users.get_user_lang_by_id(exec_user["user_id"])

View File

@ -7,12 +7,12 @@ logger = logging.getLogger(__name__)
class StatusHandler(BaseHandler): class StatusHandler(BaseHandler):
def get(self): def get(self):
page_data = {"background": self.controller.cached_login} page_data = {
page_data["lang"] = self.helper.get_setting("language") "background": self.controller.cached_login,
page_data["lang_page"] = self.helper.get_lang_page( "lang": self.helper.get_setting("language"),
self.helper.get_setting("language") "lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
) "servers": self.controller.servers.get_all_servers_stats(),
page_data["servers"] = self.controller.servers.get_all_servers_stats() }
running = 0 running = 0
for srv in page_data["servers"]: for srv in page_data["servers"]:
if srv["stats"]["running"]: if srv["stats"]["running"]:

View File

@ -52,18 +52,19 @@ class UploadHandler(BaseHandler):
f"User with ID {user_id} attempted to upload a file that" f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size." f" exceeded the max body size."
) )
self.helper.websocket_helper.broadcast_user(
user_id, return self.finish_json(
"send_start_error", 413,
{ {
"error": self.helper.translation.translate( "status": "error",
"error": "TOO LARGE",
"info": self.helper.translation.translate(
"error", "error",
"fileTooLarge", "fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id), self.controller.users.get_user_lang_by_id(user_id),
), ),
}, },
) )
return
self.do_upload = True self.do_upload = True
if superuser: if superuser:
@ -141,48 +142,50 @@ class UploadHandler(BaseHandler):
f"User with ID {user_id} attempted to upload a file that" f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size." f" exceeded the max body size."
) )
self.helper.websocket_helper.broadcast_user(
user_id, return self.finish_json(
"send_start_error", 413,
{ {
"error": self.helper.translation.translate( "status": "error",
"error": "TOO LARGE",
"info": self.helper.translation.translate(
"error", "error",
"fileTooLarge", "fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id), self.controller.users.get_user_lang_by_id(user_id),
), ),
}, },
) )
return
self.do_upload = True self.do_upload = True
if not superuser: if not superuser:
self.helper.websocket_helper.broadcast_user( return self.finish_json(
user_id, 401,
"send_start_error",
{ {
"error": self.helper.translation.translate( "status": "error",
"error": "UNAUTHORIZED ACCESS",
"info": self.helper.translation.translate(
"error", "error",
"superError", "superError",
self.controller.users.get_user_lang_by_id(user_id), self.controller.users.get_user_lang_by_id(user_id),
), ),
}, },
) )
return
if not self.request.headers.get("X-Content-Type", None).startswith( if not self.request.headers.get("X-Content-Type", None).startswith(
"image/" "image/"
): ):
self.helper.websocket_helper.broadcast_user(
user_id, return self.finish_json(
"send_start_error", 415,
{ {
"error": self.helper.translation.translate( "status": "error",
"error": "TYPE ERROR",
"info": self.helper.translation.translate(
"error", "error",
"fileError", "fileError",
self.controller.users.get_user_lang_by_id(user_id), self.controller.users.get_user_lang_by_id(user_id),
), ),
}, },
) )
return
if user_id is None: if user_id is None:
logger.warning("User ID not found in upload handler call") logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call") Console.warning("User ID not found in upload handler call")
@ -219,18 +222,19 @@ class UploadHandler(BaseHandler):
f"User with ID {user_id} attempted to upload a file that" f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size." f" exceeded the max body size."
) )
self.helper.websocket_helper.broadcast_user(
user_id, return self.finish_json(
"send_start_error", 413,
{ {
"error": self.helper.translation.translate( "status": "error",
"error": "TOO LARGE",
"info": self.helper.translation.translate(
"error", "error",
"fileTooLarge", "fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id), self.controller.users.get_user_lang_by_id(user_id),
), ),
}, },
) )
return
self.do_upload = True self.do_upload = True
if superuser: if superuser:

View File

@ -81,7 +81,7 @@
<i class="menu-arrow"></i> <i class="menu-arrow"></i>
</a> </a>
<div class="collapse" id="page-layouts"> <div class="collapse" id="page-layouts">
<ul class="nav flex-column sub-menu"> <ul class="nav flex-column sub-menu" id="sidebar-servers">
{% if data['crafty_permissions']['Server_Creation'] in data['user_crafty_permissions'] %} {% if data['crafty_permissions']['Server_Creation'] in data['user_crafty_permissions'] %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('sidebar', <a class="nav-link" href="/server/step1"><i class="fas fa-plus-circle"></i> &nbsp; {{ translate('sidebar',
@ -138,4 +138,9 @@
</ul> </ul>
</nav> </nav>
<script>
$(document).ready(function () {
$('#sidebar-servers li:gt(10)').remove();
})
</script>
<!-- partial --> <!-- partial -->

View File

@ -6,7 +6,8 @@
{% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %} {% block title %}Crafty Controller - {{ translate('panelConfig', 'pageTitle', data['lang']) }}{% end %}
{% block content %} {% block content %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css"> <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/css/bootstrap-select.min.css">
<div class="content-wrapper"> <div class="content-wrapper">
@ -71,7 +72,8 @@
<button type="button" class="btn btn-outline-default custom-picker" onclick="$('option', $('#lang_select')).each(function(element) { <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') $(this).removeAttr('selected').prop('selected', false); $('.selectpicker').selectpicker('refresh')
});">Enable all Languages</button> });">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"> <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'] %} {% for lang in data['all_languages'] %}
{% if lang in item[1] %} {% if lang in item[1] %}
<option selected>{{lang}}</option> <option selected>{{lang}}</option>
@ -80,10 +82,13 @@
{% end %} {% end %}
{% end %} {% end %}
</select> </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> <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> </div>
{% elif isinstance(item[1], list) %} {% elif isinstance(item[1], list) %}
<textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}" class="form-control list">{{','.join(item[1])}}</textarea> <textarea value="{{','.join(item[1])}}" type="text" name="{{item[0]}}"
class="form-control list">{{','.join(item[1])}}</textarea>
{% elif isinstance(item[1], bool) %} {% elif isinstance(item[1], bool) %}
<div style="margin-left: 30px;"> <div style="margin-left: 30px;">
{% if item[1] == True %} {% if item[1] == True %}
@ -99,9 +104,11 @@
{% end %} {% end %}
</div> </div>
{% elif isinstance(item[1], int) %} {% 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> <input type="number" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
step="1" min="0" required>
{% else %} {% else %}
<input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}" step="2" min="0" required> <input type="text" class="form-control" name="{{item[0]}}" id="{{item[0]}}" value="{{ item[1] }}"
step="2" min="0" required>
{% end %} {% end %}
</div> </div>
{% end %} {% end %}
@ -276,44 +283,6 @@
}, },
}); });
}) })
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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/js/bootstrap-select.min.js"> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/js/bootstrap-select.min.js">
</script> </script>

View File

@ -352,7 +352,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] 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>'; document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = file.name let fileName = file.name
@ -361,6 +361,15 @@
let size = file.size let size = file.size
let type = 'background' let type = 'background'
xmlHttpRequest.upload.addEventListener('progress', function (e) {
if (e.loaded <= size) {
var percent = Math.round(e.loaded / size * 100);
$(`#upload-progress-bar`).css('width', percent + '%');
$(`#upload-progress-bar`).html(percent + '%');
}
});
xmlHttpRequest.open('POST', target, true); xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token); xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
@ -377,7 +386,15 @@
}, 2000); }, 2000);
} }
else { else {
alert('Upload failed with response: ' + event.target.responseText); let response_text = JSON.parse(event.target.responseText);
var x = document.querySelector('.bootbox');
console.log(JSON.parse(event.target.responseText).info)
bootbox.alert({
message: JSON.parse(event.target.responseText).info,
callback: function () {
window.location.reload();
}
});
doUpload = false; doUpload = false;
} }
}, false); }, false);

View File

@ -110,15 +110,15 @@
<div class="form-group"> <div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label> <label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['before'] %} {% if data['backup_config']['before'] %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check" checked>Run <input type="checkbox" class="form-check-input" id="before-check" name="before-check" checked>{{
Command Before Backup translate('serverBackups', 'before', data['lang']) }}
<br> <br>
<input type="text" class="form-control" name="backup_before" id="backup_before" <input type="text" class="form-control" name="backup_before" id="backup_before"
value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you" value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you"
style="display: inline-block;"> style="display: inline-block;">
{% else %} {% else %}
<input type="checkbox" class="form-check-input" id="before-check" name="before-check">Run Command <input type="checkbox" class="form-check-input" id="before-check" name="before-check">{{
Before Backup translate('serverBackups', 'before', data['lang']) }}
<br> <br>
<input type="text" class="form-control" name="backup_before" id="backup_before" value="" <input type="text" class="form-control" name="backup_before" id="backup_before" value=""
placeholder="We enter the / for you." style="display: none;"> placeholder="We enter the / for you." style="display: none;">
@ -127,15 +127,15 @@
<div class="form-group"> <div class="form-group">
<label for="command-check" class="form-check-label ml-4 mb-4"></label> <label for="command-check" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['after'] %} {% if data['backup_config']['after'] %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check" checked>Run <input type="checkbox" class="form-check-input" id="after-check" name="after-check" checked>{{
Command After Backup translate('serverBackups', 'after', data['lang']) }}
<br> <br>
<input type="text" class="form-control" name="backup_after" id="backup_after" <input type="text" class="form-control" name="backup_after" id="backup_after"
value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you" value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you"
style="display: inline-block;"> style="display: inline-block;">
{% else %} {% else %}
<input type="checkbox" class="form-check-input" id="after-check" name="after-check">Run Command <input type="checkbox" class="form-check-input" id="after-check" name="after-check">{{
Before Backup translate('serverBackups', 'after', data['lang']) }}
<br> <br>
<input type="text" class="form-control" name="backup_after" id="backup_after" value="" <input type="text" class="form-control" name="backup_after" id="backup_after" value=""
placeholder="We enter the / for you." style="display: none;"> placeholder="We enter the / for you." style="display: none;">

View File

@ -184,6 +184,15 @@
value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60" value="{{ data['server_stats']['server_id']['shutdown_timeout'] }}" step="2" max="300" min="60"
required> required>
</div> </div>
<div class="form-group">
<label for="ignored_exits">{{ translate('serverConfig', 'ignoredExits', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'ignoredExitsExplain',
data['lang'])
}}
</small> </label>
<input type="text" class="form-control" name="ignored_exits" id="ignored_exits"
value="{{ data['server_stats']['server_id']['ignored_exits'] }}">
</div>
<div class="form-group"> <div class="form-group">
<label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }} <label for="logs_delete_after">{{ translate('serverConfig', 'removeOldLogsAfter', data['lang']) }}

View File

@ -705,6 +705,15 @@
let mimeType = file.type let mimeType = file.type
let size = file.size let size = file.size
xmlHttpRequest.upload.addEventListener('progress', function (e) {
if (e.loaded <= size) {
var percent = Math.round(e.loaded / size * 100);
$(`#upload-progress-bar-${i + 1}`).css('width', percent + '%');
$(`#upload-progress-bar-${i + 1}`).html(percent + '%');
}
});
xmlHttpRequest.open('POST', target, true); xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token); xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
@ -721,10 +730,19 @@
if (event.target.responseText == 'success') { if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!'); console.log('Upload for file', file.name, 'was successful!');
let caught = false; let caught = false;
try {
if (document.getElementById(path).classList.contains("clicked")) {
var expanded = true;
}
} catch {
var expanded = false;
}
try { try {
var par_el = document.getElementById(path + "ul"); var par_el = document.getElementById(path + "ul");
var items = par_el.children; var items = par_el.children;
} catch { } catch (err) {
console.log(err)
caught = true; caught = true;
var par_el = document.getElementById("files-tree"); var par_el = document.getElementById("files-tree");
var items = par_el.children; var items = par_el.children;
@ -739,9 +757,9 @@
} }
} }
if (!flag) { if (!flag) {
if (caught) { if (caught && expanded == false) {
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="d-block tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>'); $(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="d-block tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
} else { } else if (expanded == true) {
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>'); $(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
} }
setTreeViewContext(); setTreeViewContext();
@ -751,7 +769,22 @@
$(`#upload-progress-bar-${i + 1}`).html('<i style="color: black;" class="fas fa-box-check"></i>') $(`#upload-progress-bar-${i + 1}`).html('<i style="color: black;" class="fas fa-box-check"></i>')
} }
else { else {
alert('Upload failed with response: ' + event.target.responseText); let response_text = JSON.parse(event.target.responseText);
var x = document.querySelector('.bootbox');
if (x) {
x.remove()
}
var x = document.querySelector('.modal-content');
if (x) {
x.remove()
}
console.log(JSON.parse(event.target.responseText).info)
bootbox.alert({
message: JSON.parse(event.target.responseText).info,
callback: function () {
window.location.reload();
}
});
doUpload = false; doUpload = false;
} }
}, false); }, false);
@ -788,7 +821,7 @@
var height = files.files.length * 50; var height = files.files.length * 50;
var waitMessage = '<p class="text-center mb-0">' + var waitMessage = '<p class="text-center mb-0">' +
'<i class="fa fa-spin fa-cog"></i>' + '<i class="fa fa-spin fa-cog"></i>&nbsp;' +
"{{ translate('serverFiles', 'waitUpload', data['lang']) }}" + '<br>' + "{{ translate('serverFiles', 'waitUpload', data['lang']) }}" + '<br>' +
'<strong>' + "{{ translate('serverFiles', 'stayHere', data['lang']) }}" + '</strong>' + '<strong>' + "{{ translate('serverFiles', 'stayHere', data['lang']) }}" + '</strong>' +
'</p>' + '</p>' +
@ -826,7 +859,7 @@
await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => { await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => {
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress) $(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%') $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
}); });
} }
hideUploadBox(); hideUploadBox();

View File

@ -562,7 +562,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] 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>' document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = encodeURIComponent(file.name) let fileName = encodeURIComponent(file.name)
@ -571,6 +571,15 @@
let size = file.size let size = file.size
let type = 'server_import' let type = 'server_import'
xmlHttpRequest.upload.addEventListener('progress', function (e) {
if (e.loaded <= size) {
var percent = Math.round(e.loaded / size * 100);
$(`#upload-progress-bar`).css('width', percent + '%');
$(`#upload-progress-bar`).html(percent + '%');
}
});
xmlHttpRequest.open('POST', target, true); xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token); xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
@ -585,7 +594,15 @@
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
} }
else { else {
alert('Upload failed with response: ' + event.target.responseText); let response_text = JSON.parse(event.target.responseText);
var x = document.querySelector('.bootbox');
console.log(JSON.parse(event.target.responseText).info)
bootbox.alert({
message: JSON.parse(event.target.responseText).info,
callback: function () {
window.location.reload();
}
});
doUpload = false; doUpload = false;
} }
}, false); }, false);

View File

@ -804,7 +804,7 @@
var file; var file;
function sendFile() { function sendFile() {
file = $("#file")[0].files[0] 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>' document.getElementById("upload_input").innerHTML = '<div class="progress"><div id="upload-progress-bar" 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 xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf") let token = getCookie("_xsrf")
let fileName = file.name let fileName = file.name
@ -813,6 +813,15 @@
let size = file.size let size = file.size
let type = 'server_import' let type = 'server_import'
xmlHttpRequest.upload.addEventListener('progress', function (e) {
if (e.loaded <= size) {
var percent = Math.round(e.loaded / size * 100);
$(`#upload-progress-bar`).css('width', percent + '%');
$(`#upload-progress-bar`).html(percent + '%');
}
});
xmlHttpRequest.open('POST', target, true); xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token); xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
@ -827,7 +836,15 @@
document.getElementById("lower_half").style.visibility = "visible"; document.getElementById("lower_half").style.visibility = "visible";
} }
else { else {
alert('Upload failed with response: ' + event.target.responseText); let response_text = JSON.parse(event.target.responseText);
var x = document.querySelector('.bootbox');
console.log(JSON.parse(event.target.responseText).info)
bootbox.alert({
message: JSON.parse(event.target.responseText).info,
callback: function () {
window.location.reload();
}
});
doUpload = false; doUpload = false;
} }
}, false); }, false);

View File

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

View File

@ -299,7 +299,9 @@
"shutdown": "Shutdown server for duration of backup", "shutdown": "Shutdown server for duration of backup",
"size": "Size", "size": "Size",
"storageLocation": "Storage Location", "storageLocation": "Storage Location",
"storageLocationDesc": "Where do you want to store backups?" "storageLocationDesc": "Where do you want to store backups?",
"before": "Run command before backup",
"after": "Run command after backup"
}, },
"serverConfig": { "serverConfig": {
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.", "bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",
@ -354,7 +356,9 @@
"timeoutExplain1": "How long Crafty will wait for your server to shutdown after executing the", "timeoutExplain1": "How long Crafty will wait for your server to shutdown after executing the",
"timeoutExplain2": "command before it forces the process down.", "timeoutExplain2": "command before it forces the process down.",
"statsHint1": "The port your server is running on should go here. This is just how Crafty opens a connection to your server for stats.", "statsHint1": "The port your server is running on should go here. This is just how Crafty opens a connection to your server for stats.",
"statsHint2": "This does not change the port of your server. You must still change the port in your server config file." "statsHint2": "This does not change the port of your server. You must still change the port in your server config file.",
"ignoredExits": "Ignored Crash Exit Codes",
"ignoredExitsExplain": "Exit codes Crafty's Crash detection should ignore as a normal 'stop' (separated by commas)"
}, },
"serverConfigHelp": { "serverConfigHelp": {
"desc": "Here is where you can change the configuration of your server", "desc": "Here is where you can change the configuration of your server",