diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py
index d256c52a..6efe492a 100644
--- a/app/classes/controllers/management_controller.py
+++ b/app/classes/controllers/management_controller.py
@@ -104,5 +104,17 @@ class Management_Controller:
return management_helper.get_backup_config(server_id)
@staticmethod
- def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
- return management_helper.set_backup_config(server_id, backup_path, max_backups)
+ def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None):
+ return management_helper.set_backup_config(server_id, backup_path, max_backups, excluded_dirs)
+
+ @staticmethod
+ def get_excluded_backup_dirs(server_id: int):
+ return management_helper.get_excluded_backup_dirs(server_id)
+
+ @staticmethod
+ def add_excluded_backup_dir(server_id: int, dir_to_add: str):
+ management_helper.add_excluded_backup_dir(server_id, dir_to_add)
+
+ @staticmethod
+ def del_excluded_backup_dir(server_id: int, dir_to_del: str):
+ management_helper.del_excluded_backup_dir(server_id, dir_to_del)
diff --git a/app/classes/models/management.py b/app/classes/models/management.py
index b652ff11..e197add6 100644
--- a/app/classes/models/management.py
+++ b/app/classes/models/management.py
@@ -125,7 +125,7 @@ class Schedules(Model):
# Backups Class
#************************************************************************************************
class Backups(Model):
- directories = CharField(null=True)
+ excluded_dirs = CharField(null=True)
max_backups = IntegerField()
server_id = ForeignKeyField(Servers, backref='backups_server')
class Meta:
@@ -311,34 +311,37 @@ class helpers_management:
row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
conf = {
"backup_path": row.server_id.backup_path,
- "directories": row.directories,
+ "excluded_dirs": row.excluded_dirs,
"max_backups": row.max_backups,
"server_id": row.server_id.server_id
}
except IndexError:
conf = {
"backup_path": None,
- "directories": None,
+ "excluded_dirs": None,
"max_backups": 0,
"server_id": server_id
}
return conf
@staticmethod
- def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None):
+ def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None):
logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).count() != 0:
new_row = False
conf = {}
else:
conf = {
- "directories": None,
+ "excluded_dirs": None,
"max_backups": 0,
"server_id": server_id
}
new_row = True
if max_backups is not None:
conf['max_backups'] = max_backups
+ if excluded_dirs is not None:
+ dirs_to_exclude = ",".join(excluded_dirs)
+ conf['excluded_dirs'] = dirs_to_exclude
if not new_row:
with database.atomic():
if backup_path is not None:
@@ -355,5 +358,34 @@ class helpers_management:
Backups.create(**conf)
logger.debug("Creating new backup record.")
+ @staticmethod
+ def get_excluded_backup_dirs(server_id: int):
+ excluded_dirs = helpers_management.get_backup_config(server_id)['excluded_dirs']
+ if excluded_dirs is not None:
+ dir_list = excluded_dirs.split(",")
+ else:
+ dir_list = []
+ return dir_list
+
+ @staticmethod
+ def add_excluded_backup_dir(server_id: int, dir_to_add: str):
+ dir_list = management_helper.get_excluded_backup_dirs()
+ if dir_to_add not in dir_list:
+ dir_list.append(dir_to_add)
+ excluded_dirs = ",".join(dir_list)
+ management_helper.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
+ else:
+ logger.debug(f"Not adding {dir_to_add} to excluded directories - already in the excluded directory list for server ID {server_id}")
+
+ @staticmethod
+ def del_excluded_backup_dir(server_id: int, dir_to_del: str):
+ dir_list = management_helper.get_excluded_backup_dirs()
+ if dir_to_del in dir_list:
+ dir_list.remove(dir_to_del)
+ excluded_dirs = ",".join(dir_list)
+ management_helper.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
+ else:
+ logger.debug(f"Not removing {dir_to_del} from excluded directories - not in the excluded directory list for server ID {server_id}")
+
management_helper = helpers_management()
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index a4566df5..f21963d0 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -773,6 +773,12 @@ class Helpers:
websocket_helper.broadcast_user(user_id, 'send_temp_path',{
'path': tempDir
})
+ @staticmethod
+ def backup_select(path, user_id):
+ if user_id:
+ websocket_helper.broadcast_user(user_id, 'send_temp_path',{
+ 'path': path
+ })
@staticmethod
def unzip_backup_archive(backup_path, zip_name):
diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py
index 0e84472d..7b902296 100644
--- a/app/classes/shared/server.py
+++ b/app/classes/shared/server.py
@@ -8,6 +8,7 @@ import logging.config
import shutil
import subprocess
import html
+import tempfile
from apscheduler.schedulers.background import BackgroundScheduler
#TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
@@ -580,14 +581,36 @@ class Server:
backup_filename = f"{self.settings['backup_path']}/{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
logger.info(f"Creating backup of server '{self.settings['server_name']}'" +
f" (ID#{self.server_id}, path={self.server_path}) at '{backup_filename}'")
- shutil.make_archive(helper.get_os_understandable_path(backup_filename), 'zip', self.server_path)
+
+ tempDir = tempfile.mkdtemp()
+ # pylint: disable=unexpected-keyword-arg
+ shutil.copytree(self.server_path, tempDir, dirs_exist_ok=True)
+ excluded_dirs = management_helper.get_excluded_backup_dirs(self.server_id)
+ server_dir = helper.get_os_understandable_path(self.settings['path'])
+
+ for my_dir in excluded_dirs:
+ # Take the full path of the excluded dir and replace the server path with the temp path
+ # This is so that we're only deleting excluded dirs from the temp path and not the server path
+ excluded_dir = helper.get_os_understandable_path(my_dir).replace(server_dir, helper.get_os_understandable_path(tempDir))
+ # Next, check to see if it is a directory
+ if os.path.isdir(excluded_dir):
+ # If it is a directory, recursively delete the entire directory from the backup
+ shutil.rmtree(excluded_dir)
+ else:
+ # If not, just remove the file
+ os.remove(excluded_dir)
+
+ shutil.make_archive(helper.get_os_understandable_path(backup_filename), 'zip', tempDir)
+
while len(self.list_backups()) > conf["max_backups"] and conf["max_backups"] > 0:
backup_list = self.list_backups()
oldfile = backup_list[0]
oldfile_path = f"{conf['backup_path']}/{oldfile['path']}"
logger.info(f"Removing old backup '{oldfile['path']}'")
os.remove(helper.get_os_understandable_path(oldfile_path))
+
self.is_backingup = False
+ shutil.rmtree(tempDir)
logger.info(f"Backup of server: {self.name} completed")
return
except:
diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py
index e847cc61..6e8f4a85 100644
--- a/app/classes/web/ajax_handler.py
+++ b/app/classes/web/ajax_handler.py
@@ -99,6 +99,141 @@ class AjaxHandler(BaseHandler):
helper.generate_zip_dir(path))
self.finish()
+ elif page == "get_backup_tree":
+ server_id = self.get_argument('id', None)
+ folder = self.get_argument('path', None)
+
+ output = ""
+
+ file_list = os.listdir(folder)
+ file_list = sorted(file_list, key=str.casefold)
+ output += \
+ f"""
"""\
+
+ for raw_filename in file_list:
+ 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):
+ output += \
+ f"""
+ \n
+
+
+
+
+ {filename}
+
+
+ \n"""\
+
+ else:
+ output += f"""
+ {filename} """
+
+ else:
+ if os.path.isdir(rel):
+ output += \
+ f"""
+ \n
+
+
+
+
+ {filename}
+
+
+ \n"""\
+
+ else:
+ output += f"""
+ {filename} """
+ self.write(helper.get_os_understandable_path(folder) + '\n' +
+ output)
+ self.finish()
+
+ elif page == "get_backup_dir":
+ server_id = self.get_argument('id', None)
+ folder = self.get_argument('path', None)
+ output = ""
+
+ file_list = os.listdir(folder)
+ file_list = sorted(file_list, key=str.casefold)
+ output += \
+ f""""""\
+
+ for raw_filename in file_list:
+ 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):
+ output += \
+ f"""
+ \n
+
+
+
+
+ {filename}
+
+
"""\
+
+ else:
+ output += f"""
+ {filename} """
+
+ else:
+ if os.path.isdir(rel):
+ output += \
+ f"""
+ \n
+
+
+
+
+ {filename}
+
+
"""\
+
+ else:
+ output += f"""
+ {filename} """
+
+ self.write(helper.get_os_understandable_path(folder) + '\n' +
+ output)
+ self.finish()
+
+ elif page == "get_dir":
+ server_id = self.get_argument('id', None)
+ path = self.get_argument('path', None)
+
+ if not self.check_server_id(server_id, 'get_tree'):
+ return
+ else:
+ server_id = bleach.clean(server_id)
+
+ if helper.validate_traversal(self.controller.servers.get_server_data_by_id(server_id)['path'], path):
+ self.write(helper.get_os_understandable_path(path) + '\n' +
+ helper.generate_dir(path))
+ self.finish()
@tornado.web.authenticated
def post(self, page):
@@ -199,6 +334,11 @@ class AjaxHandler(BaseHandler):
helper.unzipServer(path, exec_user['user_id'])
return
+ elif page == "backup_select":
+ path = self.get_argument('path', None)
+ helper.backup_select(path, exec_user['user_id'])
+ return
+
@tornado.web.authenticated
def delete(self, page):
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index 5430e7ab..afccea7a 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -428,6 +428,7 @@ class PanelHandler(BaseHandler):
return
server_info = self.controller.servers.get_server_data_by_id(server_id)
page_data['backup_config'] = self.controller.management.get_backup_config(server_id)
+ page_data['exclusions'] = self.controller.management.get_excluded_backup_dirs(server_id)
self.controller.refresh_server_settings(server_id)
try:
page_data['backup_list'] = server.list_backups()
@@ -1027,6 +1028,7 @@ class PanelHandler(BaseHandler):
logger.debug(self.request.arguments)
server_id = self.get_argument('id', None)
server_obj = self.controller.servers.get_server_obj(server_id)
+ checked = self.get_body_arguments('root_path')
if superuser:
backup_path = bleach.clean(self.get_argument('backup_path', None))
if helper.is_os_windows():
@@ -1052,7 +1054,7 @@ class PanelHandler(BaseHandler):
server_obj = self.controller.servers.get_server_obj(server_id)
server_obj.backup_path = backup_path
self.controller.servers.update_server(server_obj)
- self.controller.management.set_backup_config(server_id, max_backups=max_backups)
+ self.controller.management.set_backup_config(server_id, max_backups=max_backups, excluded_dirs=checked)
self.controller.management.add_to_audit_log(exec_user['user_id'],
f"Edited server {server_id}: updated backups",
diff --git a/app/frontend/templates/panel/parts/details_stats.html b/app/frontend/templates/panel/parts/details_stats.html
index f05c4336..aa199340 100644
--- a/app/frontend/templates/panel/parts/details_stats.html
+++ b/app/frontend/templates/panel/parts/details_stats.html
@@ -136,15 +136,8 @@
/* TODO Update each element */
if (server.running){
- if (server['stats']['waiting_start']){
- server_status.setAttribute("class", "text-warning");
- server_status.innerHTML = `{{ translate('serverStats', 'starting', data['lang']) }}`;
- }
- else
- {
server_status.setAttribute("class", "text-success");
server_status.innerHTML = `{{ translate('serverStats', 'online', data['lang']) }}`;
- }
startedUTC = server.started;
startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss');
diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html
index 6507e15f..5c827d54 100644
--- a/app/frontend/templates/panel/server_backup.html
+++ b/app/frontend/templates/panel/server_backup.html
@@ -35,6 +35,8 @@
+
+
+
+ {{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} - {{ translate('serverBackups', 'excludedChoose', data['lang']) }}
+
+ {{ translate('serverBackups', 'clickExclude', data['lang']) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ translate('serverFiles', 'files', data['lang']) }}
+
+
+
+
+
+
+
+
{{ translate('serverBackups', 'save', data['lang']) }}
{{ translate('serverBackups', 'cancel', data['lang']) }}
@@ -61,15 +95,10 @@
-
-
-
{{ translate('serverBackups', 'currentBackups', data['lang']) }}
-
-
-
+ {{ translate('serverBackups', 'currentBackups', data['lang']) }}
{{ translate('serverBackups', 'options', data['lang']) }}
@@ -107,6 +136,20 @@
+
+
+
+
+
+
+ {% for item in data['exclusions'] %}
+ {{item}}
+
+ {% end %}
+
+
@@ -116,6 +159,44 @@
+
{% end %}
@@ -123,7 +204,7 @@
{% block js %}
-{% end %}
+{% end %}
\ No newline at end of file
diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html
index 731032b5..e700b1a1 100644
--- a/app/frontend/templates/server/bedrock_wizard.html
+++ b/app/frontend/templates/server/bedrock_wizard.html
@@ -310,9 +310,6 @@
message: ' Please wait while we gather your files...
',
closeButton: false
});
- setTimeout(function(){
- dialog.modal('hide');
-}, 5000);
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
@@ -433,9 +430,20 @@ function hide(event) {
}
if (webSocket) {
webSocket.on('send_temp_path', function (data) {
- document.getElementById('main-tree-input').setAttribute('value', data.path)
- getTreeView(data.path);
- show_file_tree();
+ setTimeout(function(){
+ var x = document.querySelector('.bootbox');
+ if (x) {
+ x.remove()
+ }
+ var x = document.querySelector('.modal-backdrop');
+ if (x) {
+ x.remove()
+ }
+ document.getElementById('main-tree-input').setAttribute('value', data.path)
+ getTreeView(data.path);
+ show_file_tree();
+
+ }, 5000);
});
}
diff --git a/app/frontend/templates/server/wizard.html b/app/frontend/templates/server/wizard.html
index 46b50a22..657ee769 100644
--- a/app/frontend/templates/server/wizard.html
+++ b/app/frontend/templates/server/wizard.html
@@ -437,9 +437,6 @@
message: ' Please wait while we gather your files...
',
closeButton: false
});
- setTimeout(function(){
- dialog.modal('hide');
-}, 5000);
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
@@ -587,9 +584,20 @@ function hide(event) {
}
if (webSocket) {
webSocket.on('send_temp_path', function (data) {
- document.getElementById('main-tree-input').setAttribute('value', data.path)
- getTreeView(data.path);
- show_file_tree();
+ setTimeout(function(){
+ var x = document.querySelector('.bootbox');
+ if (x) {
+ x.remove()
+ }
+ var x = document.querySelector('.modal-backdrop');
+ if (x) {
+ x.remove()
+ }
+ document.getElementById('main-tree-input').setAttribute('value', data.path)
+ getTreeView(data.path);
+ show_file_tree();
+
+ }, 5000);
});
}
diff --git a/app/migrations/20220227162446_backup_options.py b/app/migrations/20220227162446_backup_options.py
new file mode 100644
index 00000000..1239dd54
--- /dev/null
+++ b/app/migrations/20220227162446_backup_options.py
@@ -0,0 +1,8 @@
+# Generated by database migrator
+import peewee
+
+def migrate(migrator, db):
+ migrator.rename_column('backups', 'directories', 'excluded_dirs')
+
+def rollback(migrator, db):
+ migrator.rename_column('backups', 'excluded_dirs', 'directories')
\ No newline at end of file
diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json
index e7d5105d..1a9ec7d3 100644
--- a/app/translations/en_EN.json
+++ b/app/translations/en_EN.json
@@ -236,7 +236,11 @@
"options": "Options",
"restoring": "Restoring Backup. This may take a while. Please be patient.",
"restore": "Restore",
- "confirmRestore": "Are you sure you want to restore from this backup. All current server files will changed to backup state and will be unrecoverable."
+ "confirmRestore": "Are you sure you want to restore from this backup. All current server files will changed to backup state and will be unrecoverable.",
+ "excludedBackups": "Excluded Paths: ",
+ "excludedChoose": "Choose the paths you wish to exclude from your backups",
+ "clickExclude": "Click to select Exclusions",
+ "exclusionsTitle": "Backup Exclusions"
},
"serverFiles": {
"noscript": "The file manager does not work without JavaScript",