Merge branch 'dev' into feature/discord-webhooks

This commit is contained in:
Zedifus 2023-09-27 21:57:13 +01:00
commit f9218ae157
11 changed files with 250 additions and 136 deletions

View File

@ -1,7 +1,7 @@
# Changelog
## --- [4.2.0] - 2023/TBD
### New features
- Finish and Activate Arcadia notification backend ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/621) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/626))
- Finish and Activate Arcadia notification backend ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/621) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/626) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/632))
### Bug fixes
- PWA: Removed the custom offline page in favour of browser default ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/607))
- Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612))
@ -11,6 +11,7 @@
- Fix select installs failing to start, returning missing python package `packaging` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/629))
- Fix public status page not updating #255 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/615))
- Fix service worker vulrn and CQ raised by SonarQ ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/631))
- Fix Backup Restore/Schedules, Backup button function on `remote-comms2` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/634))
### Refactor
- Consolidate remaining frontend functions into API V2, and remove ajax internal API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/585))
- Replace bleach with nh3 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/628))
@ -21,6 +22,7 @@
- Add get_users command to Crafty's console ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/620))
- Make files hover cursor pointer ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/627))
- Use `Jar` class naming for jar refresh to make room for steamCMD naming in the future ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/630))
- Improve ui visibility of Build Wizard selection tabs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/633))
### Lang
TBD
<br><br>

View File

@ -594,6 +594,66 @@ class Controller:
return False
return True
def restore_java_zip_server(
self,
server_name: str,
zip_path: str,
server_jar: str,
min_mem: int,
max_mem: int,
port: int,
user_id: int,
):
server_id = Helpers.create_uuid()
new_server_dir = os.path.join(self.helper.servers_dir, server_id)
backup_path = os.path.join(self.helper.backup_path, server_id)
if Helpers.is_os_windows():
new_server_dir = Helpers.wtol_path(new_server_dir)
backup_path = Helpers.wtol_path(backup_path)
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
temp_dir = Helpers.get_os_understandable_path(zip_path)
Helpers.ensure_dir_exists(new_server_dir)
Helpers.ensure_dir_exists(backup_path)
full_jar_path = os.path.join(new_server_dir, server_jar)
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 "{full_jar_path}" 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 {full_jar_path} nogui"
)
logger.debug("command: " + server_command)
server_log_file = "./logs/latest.log"
server_stop = "stop"
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_jar,
server_log_file,
server_stop,
port,
user_id,
server_type="minecraft-java",
)
ServersController.set_import(new_id)
self.import_helper.import_java_zip_server(
temp_dir, new_server_dir, port, new_id
)
return new_id
# **********************************************************************************
# BEDROCK IMPORTS
# **********************************************************************************
@ -691,7 +751,7 @@ class Controller:
self.import_helper.download_bedrock_server(new_server_dir, new_id)
return new_id
def import_bedrock_zip_server(
def restore_bedrock_zip_server(
self,
server_name: str,
zip_path: str,
@ -836,6 +896,7 @@ class Controller:
srv_obj = server["server_obj"]
srv_obj.server_scheduler.shutdown()
srv_obj.dir_scheduler.shutdown()
running = srv_obj.check_running()
if running:

View File

@ -1048,13 +1048,6 @@ class ServerInstance:
logger.info(f"Backup Thread started for server {self.settings['server_name']}.")
def a_backup_server(self):
if len(WebSocketManager().clients) > 0:
WebSocketManager().broadcast_page_params(
"/panel/server_detail",
{"id": str(self.server_id)},
"backup_reload",
{"percent": 0, "total_files": 0},
)
was_server_running = None
logger.info(f"Starting server {self.name} (ID {self.server_id}) backup")
server_users = PermissionsServers.get_server_user_list(self.server_id)
@ -1567,7 +1560,6 @@ class ServerInstance:
# process stats
p_stats = Stats._try_get_process_stats(self.process, self.check_running())
internal_ip = server["server_ip"]
server_port = server["server_port"]
server_name = server.get("server_name", f"ID#{server_id}")

View File

@ -102,7 +102,7 @@ class TasksManager:
)
except:
logger.error(
"Server value requested does not exist! "
f"Server value {cmd['server_id']} requested does not exist! "
"Purging item from waiting commands."
)
continue

View File

@ -121,11 +121,11 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
server_data = self.controller.servers.get_server_data_by_id(server_id)
zip_name = data["filename"]
# import the server again based on zipfile
if server_data["type"] == "minecraft-java":
backup_path = svr_obj.backup_path
if Helpers.validate_traversal(backup_path, zip_name):
temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
new_server = self.controller.import_zip_server(
backup_path = svr_obj.backup_path
if Helpers.validate_traversal(backup_path, zip_name):
temp_dir = Helpers.unzip_backup_archive(backup_path, zip_name)
if server_data["type"] == "minecraft-java":
new_server = self.controller.restore_java_zip_server(
svr_obj.server_name,
temp_dir,
server_data["executable"],
@ -134,71 +134,78 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
server_data["server_port"],
server_data["created_by"],
)
new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server)
self.controller.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
elif server_data["type"] == "minecraft-bedrock":
new_server = self.controller.restore_bedrock_zip_server(
svr_obj.server_name,
temp_dir,
server_data["executable"],
server_data["server_port"],
server_data["created_by"],
)
# preserve current schedules
for schedule in self.controller.management.get_schedules_by_server(
server_id
):
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(
new_server_id
new_server_id = new_server
new_server = self.controller.servers.get_server_data(new_server)
self.controller.rename_backup_dir(
server_id, new_server_id, new_server["server_uuid"]
)
# preserve current schedules
for schedule in self.controller.management.get_schedules_by_server(
server_id
):
job_data = self.controller.management.get_scheduled_task(
schedule.schedule_id
)
new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if svr_obj.path in svr_obj.executable:
new_server_obj.executable = str(svr_obj.executable).replace(
svr_obj.path, new_server_obj.path
)
# reset run command path
if svr_obj.path in svr_obj.execution_command:
new_server_obj.execution_command = str(
svr_obj.execution_command
).replace(svr_obj.path, new_server_obj.path)
# reset log path
if svr_obj.path in svr_obj.log_path:
new_server_obj.log_path = str(svr_obj.log_path).replace(
svr_obj.path, new_server_obj.path
)
self.controller.servers.update_server(new_server_obj)
job_data["server_id"] = new_server_id
del job_data["schedule_id"]
self.tasks_manager.update_job(schedule.schedule_id, job_data)
# preserve execution command
new_server_obj = self.controller.servers.get_server_obj(new_server_id)
new_server_obj.execution_command = server_data["execution_command"]
# reset executable path
if svr_obj.path in svr_obj.executable:
new_server_obj.executable = str(svr_obj.executable).replace(
svr_obj.path, new_server_obj.path
)
# reset run command path
if svr_obj.path in svr_obj.execution_command:
new_server_obj.execution_command = str(
svr_obj.execution_command
).replace(svr_obj.path, new_server_obj.path)
# reset log path
if svr_obj.path in svr_obj.log_path:
new_server_obj.log_path = str(svr_obj.log_path).replace(
svr_obj.path, new_server_obj.path
)
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)
except JobLookupError as e:
logger.info("No active tasks found for server: {e}")
self.controller.remove_server(server_id, True)
except Exception:
# 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)
except JobLookupError as e:
logger.info("No active tasks found for server: {e}")
self.controller.remove_server(server_id, True)
except Exception as e:
return self.finish_json(
400, {"status": "error", "error": "NO BACKUP FOUND"}
400, {"status": "error", "error": f"NO BACKUP FOUND {e}"}
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],

View File

@ -22979,27 +22979,42 @@ ul li {
padding-left: 0;
}
.tab-simple-styled {
.nav-tabs.tab-simple-styled {
border-bottom: none;
margin-top: 30px;
margin-bottom: 30px;
}
.tab-simple-styled .nav-item {
.nav-tabs.tab-simple-styled .nav-item {
margin-right: 30px;
}
.tab-simple-styled .nav-item .nav-link {
.nav-tabs.tab-simple-styled .nav-item .nav-link {
border: none;
padding: 0;
color: var(--base-text);
}
.tab-simple-styled .nav-item .nav-link.active {
.nav-tabs.tab-simple-styled .nav-item .nav-link.active {
background: var(--dropdown-bg);
color: var(--info);
}
.nav-pills.tab-simple-styled {
border-bottom: none;
/*margin-top: 1.5rem;*/
margin-bottom: 1.5rem;
}
/*.nav-pills.tab-simple-styled .nav-item {
margin-right: 1.5rem;
}*/
.nav-pills.tab-simple-styled .nav-item .nav-link.active {
background: var(--info);
color: #ffffff;
}
.tab-tile-style {
display: -webkit-box;
display: -ms-flexbox;

View File

@ -21494,27 +21494,42 @@ ul li {
padding-left: 0;
}
.tab-simple-styled {
.nav-tabs.tab-simple-styled {
border-bottom: none;
margin-top: 30px;
margin-bottom: 30px;
}
.tab-simple-styled .nav-item {
.nav-tabs.tab-simple-styled .nav-item {
margin-right: 30px;
}
.tab-simple-styled .nav-item .nav-link {
.nav-tabs.tab-simple-styled .nav-item .nav-link {
border: none;
padding: 0;
color: var(--gray);
}
.tab-simple-styled .nav-item .nav-link.active {
.nav-tabs.tab-simple-styled .nav-item .nav-link.active {
background: #fff;
color: var(--info);
}
.nav-pills.tab-simple-styled {
border-bottom: none;
/*margin-top: 1.5rem;*/
margin-bottom: 1.5rem;
}
/*.nav-pills.tab-simple-styled .nav-item {
margin-right: 1.5rem;
}*/
.nav-pills.tab-simple-styled .nav-item .nav-link.active {
background: var(--info);
color: #ffffff;
}
.tab-tile-style {
display: -webkit-box;
display: -ms-flexbox;

View File

@ -1,7 +1,6 @@
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link count-indicator dropdown-toggle" id="notifDropdown" href="#" data-toggle="dropdown"
aria-expanded="false">
<li class="nav-item dropdown notif-li">
<a class="nav-link count-indicator dropdown-toggle" id="notifDropdown" href="#" aria-expanded="false">
<i class="fas fa-broadcast-tower
{% if data.get('update_available') %}
text-danger
@ -21,12 +20,10 @@
<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>
<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">
<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'] %}
@ -38,23 +35,19 @@
<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',
<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 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',
<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',
<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>{{
@ -79,16 +72,19 @@
.clear-button:hover {
cursor: pointer;
}
/* Hide scrollbar for Chrome, Safari and Opera */
.notif-div::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.notif-div {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* Hide scrollbar for Chrome, Safari and Opera */
.notif-div::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.notif-div {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
</style>
<script>
function pfpError(image) {
@ -104,14 +100,17 @@
}
if (data.length > 0) {
localStorage.setItem("notif-count", data.length);
$("#notif-count").show()
$("#notif-count").html(data.length);
$("#announcements").html(text);
} else {
$("#announcements").html(`<p style='margin-top: 15px;' class='text-center'><i class="fa fa-bell-slash" aria-hidden="true"></i>
</p>`);
$("#notif-count").hide()
}
$(".clear-button").on("click", function (event) {
console.log("CLEAR BUTTON")
event.stopPropagation();
let uuid = event.target.getAttribute("data-id");
$(`#${uuid}`).remove();
send_clear(uuid);
@ -162,6 +161,28 @@
bootbox.alert(responseData.error)
}
}
/* Open / Close with Button */
$('li.dropdown.notif-li a').on('click', function (event) {
$(this).parent().toggleClass("show");
$('div.notif-div').toggleClass("show");
if ($('li.dropdown.notif-li a').attr('aria-expanded') === 'false') {
$('li.dropdown.notif-li a').attr('aria-expanded', "true");
}
else {
$('li.dropdown.notif-li a').attr('aria-expanded', "false");
}
});
/* Close when clicking ouside */
$('body').on('click', function (e) {
if (!$('li.dropdown.notif-li').is(e.target) && $('li.dropdown.notif-li').has(e.target).length === 0 && $('show').has(e.target).length === 0) {
$('li.notif-li').removeClass("show");
$('li.dropdown.notif-li a').attr('aria-expanded', "false");
$('div.notif-div').removeClass("show");
}
});
$(document).ready(function () {
getAnnouncements();
})

View File

@ -44,25 +44,25 @@
<div class="col-md-6 col-sm-12">
<br>
<br>
<form id="backup-form" class="forms-sample">
{% if data['backing_up'] %}
<div class="progress" style="height: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
data['backup_stats']['percent'] }}%</div>
</div>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>
{% end %}
{% if data['backing_up'] %}
<div class="progress" style="height: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
data['backup_stats']['percent'] }}%</div>
</div>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>
{% end %}
<br>
{% if not data['backing_up'] %}
<div id="backup_button" class="form-group">
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow',
data['lang']) }}</button>
</div>
{% end %}
<br>
{% if not data['backing_up'] %}
<div id="backup_button" class="form-group">
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow',
data['lang']) }}</button>
</div>
{% end %}
<form id="backup-form" class="forms-sample">
<div class="form-group">
{% if data['super_user'] %}
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small
@ -309,11 +309,6 @@
async function backup_started() {
const token = getCookie("_xsrf")
document.getElementById('backup_button').style.visibility = 'hidden';
var dialog = bootbox.dialog({
message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}",
closeButton: false
});
let res = await fetch(`/api/v2/servers/${server_id}/action/backup_server`, {
method: 'POST',
headers: {
@ -323,8 +318,14 @@
let responseData = await res.json();
if (responseData.status === "ok") {
console.log(responseData);
process_tree_response(responseData);
$("#backup_button").html(`<div class="progress" style="height: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
data['backup_stats']['percent'] }}%</div>
</div>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>`);
} else {
bootbox.alert({

View File

@ -5,7 +5,7 @@
{% block content %}
<div class="content-wrapper">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<ul class="nav nav-pills tab-simple-styled " role="tablist">
<li class="nav-item term-nav-item">
<a class="nav-link" href="/server/step1" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Minecraft-Java</a>

View File

@ -5,7 +5,7 @@
{% block content %}
<div class="content-wrapper">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<ul class="nav nav-pills tab-simple-styled">
<li class="nav-item term-nav-item">
<a class="nav-link active" href="/server/step1" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Minecraft-Java</a>