Add backup configs/remove backup configs

This commit is contained in:
amcmanu3 2024-05-25 22:10:35 -04:00
parent 3cf4ebf073
commit 5d82d79afd
11 changed files with 250 additions and 194 deletions

View File

@ -5,6 +5,7 @@ from prometheus_client import CollectorRegistry, Gauge
from app.classes.models.management import HelpersManagement, HelpersWebhooks
from app.classes.models.servers import HelperServers
from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__)
@ -189,30 +190,34 @@ class ManagementController:
def get_backups_by_server(server_id, model=False):
return HelpersManagement.get_backups_by_server(server_id, model)
@staticmethod
def delete_backup_config(backup_id):
HelpersManagement.remove_backup_config(backup_id)
@staticmethod
def update_backup_config(backup_id, updates):
if "backup_location" in updates:
updates["backup_location"] = Helpers.wtol_path(updates["backup_location"])
return HelpersManagement.update_backup_config(backup_id, updates)
def add_backup_config(
self,
server_id: int,
backup_path: str = "",
max_backups: int = 0,
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
before: str = "",
after: str = "",
):
def add_backup_config(self, data):
if "backup_location" in data:
data["backup_location"] = Helpers.wtol_path(data["backup_location"])
return self.management_helper.add_backup_config(data)
def add_default_backup_config(self, server_id, backup_path):
return self.management_helper.add_backup_config(
server_id,
backup_path,
max_backups,
excluded_dirs,
compress,
shutdown,
before,
after,
{
"backup_name": "afdgahah",
"backup_location": Helpers.wtol_path(backup_path),
"max_backups": 0,
"before": "",
"after": "",
"compress": False,
"shutdown": False,
"server_id": server_id,
"excluded_dirs": [],
}
)
@staticmethod

View File

@ -371,30 +371,10 @@ class HelpersManagement:
return data
@staticmethod
def remove_backup_config(server_id):
Backups.delete().where(Backups.server_id == server_id).execute()
def remove_backup_config(backup_id):
Backups.delete().where(Backups.backup_id == backup_id).execute()
def add_backup_config(
self,
server_id: str,
backup_path: str = "",
max_backups: int = 0,
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
before: str = "",
after: str = "",
):
conf = {
"excluded_dirs": excluded_dirs,
"max_backups": max_backups,
"server_id": server_id,
"backup_location": backup_path,
"compress": compress,
"shutdown": shutdown,
"before": before,
"after": after,
}
def add_backup_config(self, conf):
Backups.create(**conf)
logger.debug("Creating new backup record.")

View File

@ -561,7 +561,7 @@ class Controller:
server_host=monitoring_host,
server_type=monitoring_type,
)
self.management.add_backup_config(
self.management.add_default_backup_config(
new_server_id,
backup_path,
)

View File

@ -1320,6 +1320,70 @@ class PanelHandler(BaseHandler):
return
template = "panel/server_backup_edit.html"
elif page == "add_backup":
server_id = self.get_argument("id", None)
backup_id = self.get_argument("backup_id", None)
page_data["active_link"] = "backups"
page_data["permissions"] = {
"Commands": EnumPermissionsServer.COMMANDS,
"Terminal": EnumPermissionsServer.TERMINAL,
"Logs": EnumPermissionsServer.LOGS,
"Schedule": EnumPermissionsServer.SCHEDULE,
"Backup": EnumPermissionsServer.BACKUP,
"Files": EnumPermissionsServer.FILES,
"Config": EnumPermissionsServer.CONFIG,
"Players": EnumPermissionsServer.PLAYERS,
}
if not self.failed_server:
server_obj = self.controller.servers.get_server_instance_by_id(
server_id
)
page_data["backup_failed"] = server_obj.last_backup_status()
page_data["user_permissions"] = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_info = self.controller.servers.get_server_data_by_id(server_id)
page_data["backup_config"] = {
"excluded_dirs": [],
"max_backups": 0,
"server_id": server_id,
"backup_location": os.path.join(self.helper.backup_path, server_id),
"compress": False,
"shutdown": False,
"before": "",
"after": "",
}
page_data["backing_up"] = False
page_data["backup_stats"] = (
self.controller.servers.get_server_instance_by_id(
server_id
).send_backup_status()
)
self.controller.servers.refresh_server_settings(server_id)
page_data["backup_list"] = []
page_data["backup_path"] = Helpers.wtol_path(
page_data["backup_config"]["backup_location"]
)
page_data["server_data"] = self.controller.servers.get_server_data_by_id(
server_id
)
page_data["server_stats"] = self.controller.servers.get_server_stats_by_id(
server_id
)
page_data["server_stats"]["server_type"] = (
self.controller.servers.get_server_type_by_id(server_id)
)
page_data["exclusions"] = []
if not EnumPermissionsServer.BACKUP in page_data["user_permissions"]:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access To Schedules")
return
template = "panel/server_backup_edit.html"
elif page == "edit_user":
user_id = self.get_argument("id", None)
role_servers = self.controller.servers.get_authorized_servers(user_id)

View File

@ -38,6 +38,7 @@ from app.classes.web.routes.api.servers.server.backups.index import (
)
from app.classes.web.routes.api.servers.server.backups.backup.index import (
ApiServersServerBackupsBackupIndexHandler,
ApiServersServerBackupsBackupFilesIndexHandler,
)
from app.classes.web.routes.api.servers.server.files import (
ApiServersServerFilesIndexHandler,
@ -223,7 +224,12 @@ def api_handlers(handler_args):
handler_args,
),
(
r"/api/v2/servers/([a-z0-9-]+)/files/?",
r"/api/v2/servers/([a-z0-9-]+)/backups/backup/([a-z0-9-]+)/files/?",
ApiServersServerBackupsBackupFilesIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([a-z0-9-]+)/files(?:/([a-zA-Z0-9-]+))?/?",
ApiServersServerFilesIndexHandler,
handler_args,
),

View File

@ -22,13 +22,14 @@ BACKUP_SCHEMA = {
BACKUP_PATCH_SCHEMA = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"backup_location": {"type": "string", "minLength": 1},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"exclusions": {"type": "array"},
"excluded_dirs": {"type": "array"},
},
"additionalProperties": False,
"minProperties": 1,
@ -37,12 +38,13 @@ BACKUP_PATCH_SCHEMA = {
BASIC_BACKUP_PATCH_SCHEMA = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"exclusions": {"type": "array"},
"excluded_dirs": {"type": "array"},
},
"additionalProperties": False,
"minProperties": 1,
@ -115,38 +117,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, BACKUP_SCHEMA)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
try:
FileHelpers.del_file(
os.path.join(backup_conf["backup_location"], data["filename"])
)
except Exception as e:
return self.finish_json(
400, {"status": "error", "error": f"DELETE FAILED with error {e}"}
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Edited server {server_id}: removed backup {data['filename']}",
f"Edited server {server_id}: removed backup config"
f" {backup_conf['backup_name']}",
server_id,
self.get_remote_ip(),
)
self.controller.management.delete_backup_config(backup_id)
return self.finish_json(200, {"status": "ok"})
@ -275,10 +253,6 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
self.controller.management.add_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:
@ -362,6 +336,74 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
"error_data": "Authorization Error",
},
)
self.controller.management.update_backup_config(backup_id, data)
return self.finish_json(200, {"status": "ok"})
class ApiServersServerBackupsBackupFilesIndexHandler(BaseApiHandler):
def delete(self, server_id: str, backup_id: str):
auth_data = self.authenticate_user()
backup_conf = self.controller.management.get_backup_config(backup_id)
if backup_conf["server_id"]["server_id"] != server_id:
return self.finish_json(
400,
{
"status": "error",
"error": "ID_MISMATCH",
"error_data": "Server ID backup server ID different",
},
)
if not auth_data:
return
mask = self.controller.server_perms.get_lowest_api_perm_mask(
self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
),
auth_data[5],
)
server_permissions = self.controller.server_perms.get_permissions(mask)
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(
400,
{
"status": "error",
"error": "NOT_AUTHORIZED",
"error_data": "Authorization Error",
},
)
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, BACKUP_SCHEMA)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
try:
FileHelpers.del_file(
os.path.join(backup_conf["backup_location"], data["filename"])
)
except Exception as e:
return self.finish_json(
400, {"status": "error", "error": f"DELETE FAILED with error {e}"}
)
self.controller.management.add_to_audit_log(
auth_data[4]["user_id"],
f"Edited server {server_id}: removed backup {data['filename']}",
server_id,
self.get_remote_ip(),
)
return self.finish_json(200, {"status": "ok"})

View File

@ -10,13 +10,14 @@ logger = logging.getLogger(__name__)
backup_patch_schema = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"backup_location": {"type": "string", "minLength": 1},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"exclusions": {"type": "array"},
"excluded_dirs": {"type": "array"},
},
"additionalProperties": False,
"minProperties": 1,
@ -25,12 +26,13 @@ backup_patch_schema = {
basic_backup_patch_schema = {
"type": "object",
"properties": {
"backup_name": {"type": "string", "minLength": 3},
"max_backups": {"type": "integer"},
"compress": {"type": "boolean"},
"shutdown": {"type": "boolean"},
"before": {"type": "string"},
"after": {"type": "string"},
"exclusions": {"type": "array"},
"excluded_dirs": {"type": "array"},
},
"additionalProperties": False,
"minProperties": 1,
@ -95,6 +97,8 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.controller.management.update_backup_config(server_id, data)
data["server_id"] = server_id
if not data.get("excluded_dirs", None):
data["excluded_dirs"] = []
self.controller.management.add_backup_config(data)
return self.finish_json(200, {"status": "ok"})

View File

@ -72,7 +72,7 @@ file_delete_schema = {
class ApiServersServerFilesIndexHandler(BaseApiHandler):
def post(self, server_id: str):
def post(self, server_id: str, backup_id=None):
auth_data = self.authenticate_user()
if not auth_data:
return
@ -149,21 +149,35 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
server_id
):
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": True,
}
if backup_id:
if str(
dpath
) in self.controller.management.get_excluded_backup_dirs(backup_id):
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": True,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": True,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": True,
}
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": False,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": False,
}
else:
if os.path.isdir(rel):
return_json[filename] = {

View File

@ -247,15 +247,14 @@
}
return;
}
async function del_backup(filename, id) {
async function del_backup(backup_id) {
const token = getCookie("_xsrf")
let contents = JSON.stringify({ "filename": filename })
let res = await fetch(`/api/v2/servers/${id}/backups/backup/`, {
let res = await fetch(`/api/v2/servers/${server_id}/backups/backup/${backup_id}`, {
method: 'DELETE',
headers: {
'token': token,
},
body: contents
body: {}
});
let responseData = await res.json();
if (responseData.status === "ok") {
@ -268,90 +267,8 @@
}
}
async function restore_backup(filename, id) {
const token = getCookie("_xsrf")
let contents = JSON.stringify({ "filename": filename })
var dialog = bootbox.dialog({
message: "<i class='fa fa-spin fa-spinner'></i> {{ translate('serverBackups', 'restoring', data['lang']) }}",
closeButton: false
});
let res = await fetch(`/api/v2/servers/${id}/backups/backup/`, {
method: 'POST',
headers: {
'token': token,
},
body: contents
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.href = "/panel/dashboard";
} else {
bootbox.alert({
"title": responseData.status,
"message": responseData.error
})
}
}
$("#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("");
}
});
function replacer(key, value) {
if (key != "backup_before" && key != "backup_after") {
if (typeof value == "boolean" || key === "executable_update_url") {
return value
} else {
return (isNaN(value) ? value : +value);
}
} else {
return value;
}
}
$(document).ready(function () {
try {
if ($('#backup_path').val() == '') {
console.log('true')
try {
document.getElementById('backup_now_button').disabled = true;
} catch {
}
} else {
document.getElementById('backup_now_button').disabled = false;
}
} catch {
try {
document.getElementById('backup_now_button').disabled = false;
} catch {
}
}
console.log("ready!");
$("#backup_config_box").hide();
$("#backup_save_note").hide();
$("#show_config").click(function () {
$("#backup_config_box").toggle();
$('#backup_button').hide();
$('#backup_save_note').show();
$('#backup_data').hide();
});
$('#backup_table').DataTable({
"order": [[1, "desc"]],
@ -365,6 +282,7 @@
});
$(".del_button").click(function () {
let backup = $(this).data('backup');
var file_to_del = $(this).data("file");
var backup_path = $(this).data('backup_path');
@ -384,8 +302,8 @@
callback: function (result) {
console.log(result);
if (result == true) {
var full_path = backup_path + '/' + file_to_del;
del_backup(file_to_del, server_id);
del_backup(backup);
}
}
});

View File

@ -63,6 +63,16 @@
{% end %}
<form id="backup-form" class="forms-sample">
<div class="form-group">
<label for="backup_name">{{ translate('serverBackups', 'name', data['lang']) }} </label>
{% if data["backup_config"].get("backup_id", None) %}
<input type="text" class="form-control" name="backup_name" id="backup_name"
value="{{ data['backup_config']['backup_name'] }}">
{% else %}
<input type="text" class="form-control" name="backup_name" id="backup_name"
placeholder="{{ translate('serverBackups', 'myBackup', data['lang']) }}">
{% end %}
<br>
<br>
{% if data['super_user'] %}
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang'])
@ -341,7 +351,7 @@
async function del_backup(filename, id) {
const token = getCookie("_xsrf")
let contents = JSON.stringify({ "filename": filename })
let res = await fetch(`/api/v2/servers/${id}/backups/backup/${backup_id}/`, {
let res = await fetch(`/api/v2/servers/${server_id}/backups/backup/${backup_id}/files/`, {
method: 'DELETE',
headers: {
'token': token,
@ -366,7 +376,7 @@
message: "<i class='fa fa-spin fa-spinner'></i> {{ translate('serverBackups', 'restoring', data['lang']) }}",
closeButton: false
});
let res = await fetch(`/api/v2/servers/${id}/backups/backup/${backup_id}/`, {
let res = await fetch(`/api/v2/servers/${server_id}/backups/backup/${backup_id}/`, {
method: 'POST',
headers: {
'token': token,
@ -402,7 +412,7 @@
});
function replacer(key, value) {
if (key === "exclusions") {
if (key === "excluded_dirs") {
if (value == 0) {
return []
} else {
@ -436,10 +446,11 @@
formDataObject.compress = $("#compress").prop('checked');
formDataObject.shutdown = $("#shutdown").prop('checked');
if ($("#root_files_button").hasClass("clicked")) {
excluded = []
$('input.excluded:checkbox:checked').each(function () {
excluded.push($(this).val());
});
formDataObject.exclusions = excluded;
formDataObject.excluded_dirs = excluded;
}
delete formDataObject.root_path
console.log(formDataObject);
@ -447,9 +458,14 @@
let formDataJsonString = JSON.stringify(formDataObject, replacer);
console.log(formDataJsonString);
let res = await fetch(`/api/v2/servers/${server_id}/backups/backup/${backup_id}`, {
method: 'PATCH',
let url = `/api/v2/servers/${server_id}/backups/backup/${backup_id}/`
let method = "PATCH"
if (!backup_id) {
url = `/api/v2/servers/${server_id}/backups/`
method = "POST";
}
let res = await fetch(url, {
method: method,
headers: {
'X-XSRFToken': token
},
@ -457,7 +473,7 @@
});
let responseData = await res.json();
if (responseData.status === "ok") {
window.location.reload();
window.location.href = `/panel/server_detail?id=${server_id}&subpage=backup`;
} else {
bootbox.alert({
@ -631,7 +647,13 @@
async function getTreeView(path) {
console.log(path)
const token = getCookie("_xsrf");
let res = await fetch(`/api/v2/servers/${server_id}/files`, {
let url = `/api/v2/servers/${server_id}/files/${backup_id}`
if (!backup_id) {
url = `/api/v2/servers/${server_id}/files/`
console.log("NEW URL")
}
console.log(url);
let res = await fetch(url, {
method: 'POST',
headers: {
'X-XSRFToken': token

View File

@ -318,6 +318,7 @@
"exclusionsTitle": "Backup Exclusions",
"maxBackups": "Max Backups",
"maxBackupsDesc": "Crafty will not store more than N backups, deleting the oldest (enter 0 to keep all)",
"myBackup": "My New Backup",
"options": "Options",
"path": "Path",
"restore": "Restore",