From e59b62402534e634db5fd02b6d06178d96c441ea Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Thu, 8 Feb 2024 20:25:32 -0500 Subject: [PATCH 001/155] Move backup path from servers to backup Add uuid field to backups --- app/classes/controllers/servers_controller.py | 2 -- app/classes/models/management.py | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 86e17802..a61c563b 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -48,7 +48,6 @@ class ServersController(metaclass=Singleton): name: str, server_uuid: str, server_dir: str, - backup_path: str, server_command: str, server_file: str, server_log_file: str, @@ -83,7 +82,6 @@ class ServersController(metaclass=Singleton): name, server_uuid, server_dir, - backup_path, server_command, server_file, server_log_file, diff --git a/app/classes/models/management.py b/app/classes/models/management.py index e86e3209..de5e3561 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -1,3 +1,4 @@ +import uuid import logging import datetime from peewee import ( @@ -9,6 +10,7 @@ from peewee import ( TextField, AutoField, BooleanField, + UUIDField, ) from playhouse.shortcuts import model_to_dict @@ -120,6 +122,9 @@ class Schedules(BaseModel): # Backups Class # ********************************************************************************** class Backups(BaseModel): + backup_id = UUIDField(primary_key=True, default=uuid.uuid4) + backup_name = CharField(default="New Backup") + backup_location = CharField(default="") excluded_dirs = CharField(null=True) max_backups = IntegerField() server_id = ForeignKeyField(Servers, backref="backups_server") From 548a439f14972a8a2db11953412add69df5b16e8 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 8 Mar 2024 22:06:33 -0500 Subject: [PATCH 002/155] Initial commit for backup migration. Kind of broken :/ --- app/classes/models/servers.py | 4 - app/migrations/20240308_multi-backup.py | 105 ++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 app/migrations/20240308_multi-backup.py diff --git a/app/classes/models/servers.py b/app/classes/models/servers.py index 13d9096a..e5d85c69 100644 --- a/app/classes/models/servers.py +++ b/app/classes/models/servers.py @@ -26,7 +26,6 @@ class Servers(BaseModel): created = DateTimeField(default=datetime.datetime.now) server_name = CharField(default="Server", index=True) path = CharField(default="") - backup_path = CharField(default="") executable = CharField(default="") log_path = CharField(default="") execution_command = CharField(default="") @@ -65,7 +64,6 @@ class HelperServers: server_id: str, name: str, server_dir: str, - backup_path: str, server_command: str, server_file: str, server_log_file: str, @@ -81,7 +79,6 @@ class HelperServers: name: The name of the server server_uuid: This is the UUID of the server server_dir: The directory where the server is located - backup_path: The path to the backup folder server_command: The command to start the server server_file: The name of the server file server_log_file: The path to the server log file @@ -111,7 +108,6 @@ class HelperServers: server_port=server_port, server_ip=server_host, stop_command=server_stop, - backup_path=backup_path, type=server_type, created_by=created_by, ).server_id diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py new file mode 100644 index 00000000..1c5694e8 --- /dev/null +++ b/app/migrations/20240308_multi-backup.py @@ -0,0 +1,105 @@ +import datetime +import uuid +import peewee +import logging + +from app.classes.shared.console import Console +from app.classes.shared.migration import Migrator, MigrateHistory +from app.classes.models.management import Backups + +logger = logging.getLogger(__name__) + + +def migrate(migrator: Migrator, database, **kwargs): + """ + Write your migrations here. + """ + print("pee pee") + db = database + + migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4)) + migrator.add_columns("backups", backup_name=peewee.CharField(default="Default")) + migrator.add_columns("backups", backup_location=peewee.CharField(default="")) + + class Servers(peewee.Model): + server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) + created = peewee.DateTimeField(default=datetime.datetime.now) + server_name = peewee.CharField(default="Server", index=True) + path = peewee.CharField(default="") + backup_path = peewee.CharField(default="") + executable = peewee.CharField(default="") + log_path = peewee.CharField(default="") + execution_command = peewee.CharField(default="") + auto_start = peewee.BooleanField(default=0) + auto_start_delay = peewee.IntegerField(default=10) + crash_detection = peewee.BooleanField(default=0) + stop_command = peewee.CharField(default="stop") + executable_update_url = peewee.CharField(default="") + server_ip = peewee.CharField(default="127.0.0.1") + server_port = peewee.IntegerField(default=25565) + logs_delete_after = peewee.IntegerField(default=0) + type = peewee.CharField(default="minecraft-java") + show_status = peewee.BooleanField(default=1) + created_by = peewee.IntegerField(default=-100) + shutdown_timeout = peewee.IntegerField(default=60) + ignored_exits = peewee.CharField(default="0") + + class Meta: + table_name = "servers" + database = db + + class NewBackups(peewee.Model): + backup_id = peewee.UUIDField(primary_key=True, default=uuid.uuid4) + backup_name = peewee.CharField(default="New Backup") + backup_location = peewee.CharField(default="") + excluded_dirs = peewee.CharField(null=True) + max_backups = peewee.IntegerField() + server_id = peewee.ForeignKeyField(Servers, backref="backups_server") + compress = peewee.BooleanField(default=False) + shutdown = peewee.BooleanField(default=False) + before = peewee.CharField(default="") + after = peewee.CharField(default="") + + class Meta: + table_name = "new_backups" + database = db + + migrator.create_table(NewBackups) + + migrator.run() + + # Copy data from the existing backups table to the new one + for backup in Backups.select(): + print(backup) + # Fetch the related server entry from the Servers table + server = Servers.get(Servers.server_id == backup.server_id) + + # Create a new backup entry with data from the old backup entry and related server + NewBackups.create( + backup_name="Default", + backup_location=server.backup_path, # Set backup_location equal to backup_path + excluded_dirs=backup.excluded_dirs, + max_backups=backup.max_backups, + server_id=server.server_id, + compress=backup.compress, + shutdown=backup.shutdown, + before=backup.before, + after=backup.after, + ) + + # Drop the existing backups table + migrator.drop_table("backups") + + # Rename the new table to backups + migrator.rename_table("new_backups", "backups") + migrator.drop_columns("servers", ["backup_path"]) + + +def rollback(migrator: Migrator, database, **kwargs): + """ + Write your rollback migrations here. + """ + db = database + + migrator.drop_columns("backups", ["name", "backup_id", "backup_location"]) + migrator.add_columns("servers", backup_path=peewee.CharField(default="")) From 1a8d351fbd4e2480c38963b4b8bf0e0497145368 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 8 Mar 2024 22:45:08 -0500 Subject: [PATCH 003/155] Remove print statements --- app/classes/models/users.py | 1 - app/migrations/20240308_multi-backup.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/classes/models/users.py b/app/classes/models/users.py index e44d06fb..1963bf3b 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -119,7 +119,6 @@ class HelperUsers: @staticmethod def get_user_total(): count = Users.select().where(Users.username != "system").count() - print(count) return count @staticmethod diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py index 1c5694e8..85106e16 100644 --- a/app/migrations/20240308_multi-backup.py +++ b/app/migrations/20240308_multi-backup.py @@ -14,7 +14,6 @@ def migrate(migrator: Migrator, database, **kwargs): """ Write your migrations here. """ - print("pee pee") db = database migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4)) @@ -70,7 +69,6 @@ def migrate(migrator: Migrator, database, **kwargs): # Copy data from the existing backups table to the new one for backup in Backups.select(): - print(backup) # Fetch the related server entry from the Servers table server = Servers.get(Servers.server_id == backup.server_id) From eec943211835bcf1a7d1ccfebf3fcc65b32f9ce2 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 8 Mar 2024 23:22:54 -0500 Subject: [PATCH 004/155] Update migration Add backup return function --- .../controllers/management_controller.py | 4 +++ app/classes/models/management.py | 26 ++++++++++++++++++- app/migrations/20240308_multi-backup.py | 3 +++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 7085b503..4a3f0aef 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -181,6 +181,10 @@ class ManagementController: def get_backup_config(server_id): return HelpersManagement.get_backup_config(server_id) + @staticmethod + def get_backups_by_server(server_id, model=False): + return HelpersManagement.get_backups_by_server(server_id, model) + def set_backup_config( self, server_id: int, diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 6bb94e28..52ea9e1e 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -132,6 +132,7 @@ class Backups(BaseModel): shutdown = BooleanField(default=False) before = CharField(default="") after = CharField(default="") + enabled = BooleanField(default=True) class Meta: table_name = "backups" @@ -390,7 +391,7 @@ class HelpersManagement: Backups.select().where(Backups.server_id == server_id).join(Servers)[0] ) conf = { - "backup_path": row.server_id.backup_path, + "backup_path": row.backup_location, "excluded_dirs": row.excluded_dirs, "max_backups": row.max_backups, "server_id": row.server_id_id, @@ -412,6 +413,29 @@ class HelpersManagement: } return conf + @staticmethod + def get_backups_by_server(server_id, model): + if not model: + data = {} + for backup in ( + Backups.select().where(Backups.server_id == server_id).execute() + ): + data[str(backup.backup_id)] = { + "backup_id": backup.backup_id, + "backup_name": backup.backup_name, + "backup_path": backup.backup_location, + "excluded_dirs": backup.excluded_dirs, + "max_backups": backup.max_backups, + "server_id": backup.server_id_id, + "compress": backup.compress, + "shutdown": backup.shutdown, + "before": backup.before, + "after": backup.after, + } + else: + data = Backups.select().where(Backups.server_id == server_id).execute() + return data + @staticmethod def remove_backup_config(server_id): Backups.delete().where(Backups.server_id == server_id).execute() diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py index 85106e16..49c7643c 100644 --- a/app/migrations/20240308_multi-backup.py +++ b/app/migrations/20240308_multi-backup.py @@ -19,6 +19,7 @@ def migrate(migrator: Migrator, database, **kwargs): migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4)) migrator.add_columns("backups", backup_name=peewee.CharField(default="Default")) migrator.add_columns("backups", backup_location=peewee.CharField(default="")) + migrator.add_columns("backups", enabled=peewee.BooleanField(default=True)) class Servers(peewee.Model): server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) @@ -58,6 +59,7 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown = peewee.BooleanField(default=False) before = peewee.CharField(default="") after = peewee.CharField(default="") + enabled = peewee.BooleanField(default=True) class Meta: table_name = "new_backups" @@ -83,6 +85,7 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown=backup.shutdown, before=backup.before, after=backup.after, + enabled=True, ) # Drop the existing backups table From 99ccaa925fe339302dd4f41686b0c5fa689cfbec Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Fri, 8 Mar 2024 23:23:36 -0500 Subject: [PATCH 005/155] Add backups to page --- app/classes/web/panel_handler.py | 9 +- app/frontend/static/assets/css/crafty.css | 10 ++ .../templates/panel/server_backup.html | 108 +++++++++++++++++- app/translations/en_EN.json | 8 +- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index d61e3c0e..5532fdf5 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -680,6 +680,11 @@ class PanelHandler(BaseHandler): page_data["backup_config"] = ( self.controller.management.get_backup_config(server_id) ) + page_data["backups"] = self.controller.management.get_backups_by_server( + server_id, model=True + ) + for backup in page_data["backups"]: + print(backup) exclusions = [] page_data["exclusions"] = ( self.controller.management.get_excluded_backup_dirs(server_id) @@ -706,7 +711,9 @@ class PanelHandler(BaseHandler): page_data["backup_list"] = server.list_backups() except: page_data["backup_list"] = [] - page_data["backup_path"] = Helpers.wtol_path(server_info["backup_path"]) + page_data["backup_path"] = Helpers.wtol_path( + page_data["backup_config"]["backup_path"] + ) if subpage == "metrics": try: diff --git a/app/frontend/static/assets/css/crafty.css b/app/frontend/static/assets/css/crafty.css index 43dd2e6a..8a80f6d8 100644 --- a/app/frontend/static/assets/css/crafty.css +++ b/app/frontend/static/assets/css/crafty.css @@ -12,6 +12,16 @@ nav.sidebar { position: fixed; } +td { + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ +} + +td::-webkit-scrollbar { + display: none; +} @media (min-width: 992px) { nav.sidebar { diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 2a9263ba..7d5216f2 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -39,7 +39,111 @@ {% include "parts/m_server_controls_list.html %} - +
+
+
+
+

{{ translate('backups', 'backups', data['lang']) }}

+ {% if data['user_data']['hints'] %} + + {% end %} + +
+
+ {% if len(data['backups']) == 0 %} +
+ {{ translate('backups', 'no-backup', data['lang']) }} {{ translate('backups', 'newbackup',data['lang']) }}. +
+ {% end %} + {% if len(data['backups']) > 0 %} +
+ + + + + + + + + + + + {% for backup in data['backups'] %} + + + + + + + + {% end %} + +
{{ translate('serverBackups', 'enabled', data['lang']) }}{{ translate('serverBackups', 'name', data['lang']) }} {{ translate('serverBackups', 'storageLocation', data['lang']) }}{{ translate('serverBackups', 'maxBackups', data['lang']) }}{{ translate('serverBackups', 'actions', data['lang']) }}
+ + +

{{backup.backup_name}}

+
+

{{backup.backup_location}}

+
+

{{backup.max_backups}}

+
+ + + +
+
+
+ + + + + + + + + + {% for backup in data['backups'] %} + + + + + + {% end %} + +
{{ translate('backups', 'enabled', + data['lang']) }}Name + {{ translate('backups', 'edit', data['lang']) + }}
+ + +

{{backup.backup_name}}

+
+ + + +
+
+ {% end %} +
+
+
+

@@ -69,7 +173,7 @@ class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }} {% end %}
diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 5c48b873..b0edd92e 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -323,7 +323,11 @@ "shutdown": "Shutdown server for duration of backup", "size": "Size", "storageLocation": "Storage Location", - "storageLocationDesc": "Where do you want to store backups?" + "storageLocationDesc": "Where do you want to store backups?", + "enabled": "Enabled", + "name": "Name", + "storage": "Storage Location", + "action": "Actions" }, "serverConfig": { "bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.", @@ -671,4 +675,4 @@ "webhook_body": "Webhook Body", "webhooks": "Webhooks" } -} +} \ No newline at end of file From 1381cf77ef8020aadec970560260738188f930bc Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 9 Mar 2024 12:49:51 -0500 Subject: [PATCH 006/155] Fix most translations --- app/frontend/templates/panel/server_backup.html | 14 +++++++------- app/translations/en_EN.json | 6 +++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 7d5216f2..0ffba3c4 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -43,16 +43,16 @@
-

{{ translate('backups', 'backups', data['lang']) }}

+

{{ translate('serverBackups', 'backups', data['lang']) }}

{% if data['user_data']['hints'] %} {% end %} - +
{% if len(data['backups']) == 0 %}
- {{ translate('backups', 'no-backup', data['lang']) }} {{ translate('backups', 'newbackup',data['lang']) }}. + {{ translate('serverBackups', 'no-backup', data['lang']) }} {{ translate('serverBackups', 'newBackup',data['lang']) }}.
{% end %} {% if len(data['backups']) > 0 %} @@ -91,7 +91,7 @@ - @@ -104,11 +104,11 @@ - - @@ -130,7 +130,7 @@ - diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index b0edd92e..7425208f 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -327,7 +327,11 @@ "enabled": "Enabled", "name": "Name", "storage": "Storage Location", - "action": "Actions" + "actions": "Actions", + "newBackup": "Create New Backup", + "edit": "Edit", + "run": "Run Backup", + "backups": "Server Backups" }, "serverConfig": { "bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.", From a4da773d254bf4f7e4d6c4f0ba36a586e6bd64a9 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 9 Mar 2024 12:49:59 -0500 Subject: [PATCH 007/155] Remove print statements --- app/classes/web/panel_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 5532fdf5..afb23fd2 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -683,8 +683,6 @@ class PanelHandler(BaseHandler): page_data["backups"] = self.controller.management.get_backups_by_server( server_id, model=True ) - for backup in page_data["backups"]: - print(backup) exclusions = [] page_data["exclusions"] = ( self.controller.management.get_excluded_backup_dirs(server_id) From b898595371b0bf5dad2006fb864b54e5975ade2e Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 20 Apr 2024 17:30:37 -0400 Subject: [PATCH 008/155] Refactor backup config methods to add/update --- .../controllers/management_controller.py | 12 +++-- app/classes/models/management.py | 52 ++++++++++++------- app/classes/shared/main_controller.py | 5 +- .../servers/server/backups/backup/index.py | 2 +- .../api/servers/server/backups/index.py | 34 ++---------- 5 files changed, 47 insertions(+), 58 deletions(-) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 4a3f0aef..42df3e11 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -185,18 +185,22 @@ class ManagementController: def get_backups_by_server(server_id, model=False): return HelpersManagement.get_backups_by_server(server_id, model) - def set_backup_config( + @staticmethod + def update_backup_config(backup_id, updates): + return HelpersManagement.update_backup_config(backup_id, updates) + + def add_backup_config( self, server_id: int, - backup_path: str = None, - max_backups: int = None, + backup_path: str = "", + max_backups: int = 0, excluded_dirs: list = None, compress: bool = False, shutdown: bool = False, before: str = "", after: str = "", ): - return self.management_helper.set_backup_config( + return self.management_helper.add_backup_config( server_id, backup_path, max_backups, diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 52ea9e1e..a8c69fa2 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -385,15 +385,14 @@ class HelpersManagement: # Backups Methods # ********************************************************************************** @staticmethod - def get_backup_config(server_id): + def get_backup_config(backup_id): try: - row = ( - Backups.select().where(Backups.server_id == server_id).join(Servers)[0] - ) + row = Backups.select().where(Backups.backup_id == backup_id) conf = { "backup_path": row.backup_location, "excluded_dirs": row.excluded_dirs, "max_backups": row.max_backups, + "backup_location": row.backup_location, "server_id": row.server_id_id, "compress": row.compress, "shutdown": row.shutdown, @@ -401,16 +400,7 @@ class HelpersManagement: "after": row.after, } except IndexError: - conf = { - "backup_path": None, - "excluded_dirs": None, - "max_backups": 0, - "server_id": server_id, - "compress": False, - "shutdown": False, - "before": "", - "after": "", - } + return None return conf @staticmethod @@ -440,7 +430,31 @@ class HelpersManagement: def remove_backup_config(server_id): Backups.delete().where(Backups.server_id == server_id).execute() - def set_backup_config( + 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, + } + Backups.create(**conf) + logger.debug("Creating new backup record.") + + def update_backup_config( self, server_id: int, backup_path: str = None, @@ -503,8 +517,8 @@ class HelpersManagement: logger.debug("Creating new backup record.") @staticmethod - def get_excluded_backup_dirs(server_id: int): - excluded_dirs = HelpersManagement.get_backup_config(server_id)["excluded_dirs"] + def get_excluded_backup_dirs(backup_id: int): + excluded_dirs = HelpersManagement.get_backup_config(backup_id)["excluded_dirs"] if excluded_dirs is not None and excluded_dirs != "": dir_list = excluded_dirs.split(",") else: @@ -516,7 +530,7 @@ class HelpersManagement: if dir_to_add not in dir_list: dir_list.append(dir_to_add) excluded_dirs = ",".join(dir_list) - self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) + self.update_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) else: logger.debug( f"Not adding {dir_to_add} to excluded directories - " @@ -528,7 +542,7 @@ class HelpersManagement: if dir_to_del in dir_list: dir_list.remove(dir_to_del) excluded_dirs = ",".join(dir_list) - self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) + self.update_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) else: logger.debug( f"Not removing {dir_to_del} from excluded directories - " diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 9c3219ff..9d99e4f8 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -552,7 +552,6 @@ class Controller: name=data["name"], server_uuid=server_fs_uuid, server_dir=new_server_path, - backup_path=backup_path, server_command=server_command, server_file=server_file, server_log_file=log_location, @@ -562,7 +561,7 @@ class Controller: server_host=monitoring_host, server_type=monitoring_type, ) - self.management.set_backup_config( + self.management.add_backup_config( new_server_id, backup_path, ) @@ -905,7 +904,6 @@ class Controller: name: str, server_uuid: str, server_dir: str, - backup_path: str, server_command: str, server_file: str, server_log_file: str, @@ -920,7 +918,6 @@ class Controller: name, server_uuid, server_dir, - backup_path, server_command, server_file, server_log_file, diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py index 70ceb2b2..5dc301bb 100644 --- a/app/classes/web/routes/api/servers/server/backups/backup/index.py +++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py @@ -189,7 +189,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 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( + self.controller.management.add_backup_config( new_server_id, new_server_obj.backup_path, backup_config["max_backups"], diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py index 9e47bcfc..0a95bff0 100644 --- a/app/classes/web/routes/api/servers/server/backups/index.py +++ b/app/classes/web/routes/api/servers/server/backups/index.py @@ -52,7 +52,7 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) self.finish_json(200, self.controller.management.get_backup_config(server_id)) - def patch(self, server_id: str): + def patch(self, backup_id: str): auth_data = self.authenticate_user() if not auth_data: return @@ -78,7 +78,8 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): "error_data": str(e), }, ) - + backup_conf = self.controller.management.get_backup_config(backup_id) + server_id = backup_conf["server_id"] if server_id not in [str(x["server_id"]) for x in auth_data[0]]: # if the user doesn't have access to the server, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -92,32 +93,5 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) - self.controller.management.set_backup_config( - server_id, - data.get( - "backup_path", - self.controller.management.get_backup_config(server_id)["backup_path"], - ), - data.get( - "max_backups", - self.controller.management.get_backup_config(server_id)["max_backups"], - ), - data.get("exclusions"), - data.get( - "compress", - self.controller.management.get_backup_config(server_id)["compress"], - ), - data.get( - "shutdown", - self.controller.management.get_backup_config(server_id)["shutdown"], - ), - data.get( - "backup_before", - self.controller.management.get_backup_config(server_id)["before"], - ), - data.get( - "backup_after", - self.controller.management.get_backup_config(server_id)["after"], - ), - ) + self.controller.management.update_backup_config(server_id, data) return self.finish_json(200, {"status": "ok"}) From 3bba043cf016a3399ef784e50a5d359de4a4b604 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 20 Apr 2024 18:14:45 -0400 Subject: [PATCH 009/155] Get backup configs --- app/classes/models/management.py | 84 +++----------------------------- 1 file changed, 7 insertions(+), 77 deletions(-) diff --git a/app/classes/models/management.py b/app/classes/models/management.py index a8c69fa2..5a3df8b1 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -386,22 +386,7 @@ class HelpersManagement: # ********************************************************************************** @staticmethod def get_backup_config(backup_id): - try: - row = Backups.select().where(Backups.backup_id == backup_id) - conf = { - "backup_path": row.backup_location, - "excluded_dirs": row.excluded_dirs, - "max_backups": row.max_backups, - "backup_location": row.backup_location, - "server_id": row.server_id_id, - "compress": row.compress, - "shutdown": row.shutdown, - "before": row.before, - "after": row.after, - } - except IndexError: - return None - return conf + return model_to_dict(Backups.get(Backups.backup_id == backup_id)) @staticmethod def get_backups_by_server(server_id, model): @@ -454,67 +439,12 @@ class HelpersManagement: Backups.create(**conf) logger.debug("Creating new backup record.") - def update_backup_config( - self, - server_id: int, - backup_path: str = None, - max_backups: int = None, - excluded_dirs: list = None, - compress: bool = False, - shutdown: bool = False, - before: str = "", - after: str = "", - ): - logger.debug(f"Updating server {server_id} backup config with {locals()}") - if Backups.select().where(Backups.server_id == server_id).exists(): - new_row = False - conf = {} - else: - conf = { - "excluded_dirs": None, - "max_backups": 0, - "server_id": server_id, - "compress": False, - "shutdown": False, - "before": "", - "after": "", - } - 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 - conf["compress"] = compress - conf["shutdown"] = shutdown - conf["before"] = before - conf["after"] = after - if not new_row: - with self.database.atomic(): - if backup_path is not None: - server_rows = ( - Servers.update(backup_path=backup_path) - .where(Servers.server_id == server_id) - .execute() - ) - else: - server_rows = 0 - backup_rows = ( - Backups.update(conf).where(Backups.server_id == server_id).execute() - ) - logger.debug( - f"Updating existing backup record. " - f"{server_rows}+{backup_rows} rows affected" - ) - else: - with self.database.atomic(): - conf["server_id"] = server_id - if backup_path is not None: - Servers.update(backup_path=backup_path).where( - Servers.server_id == server_id - ) - Backups.create(**conf) - logger.debug("Creating new backup record.") + @staticmethod + def update_backup_config(backup_id, data): + if "excluded_dirs" in data: + dirs_to_exclude = ",".join(data["excluded_dirs"]) + data["excluded_dirs"] = dirs_to_exclude + Backups.update(**data).where(Backups.backup_id == backup_id).execute() @staticmethod def get_excluded_backup_dirs(backup_id: int): From f2e00040bd5bb84aa4437004fd37086b32397b77 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 20 Apr 2024 18:15:06 -0400 Subject: [PATCH 010/155] Make backups list page load --- app/classes/web/panel_handler.py | 31 +- .../templates/panel/server_backup.html | 411 +++------ .../templates/panel/server_backup_edit.html | 820 ++++++++++++++++++ 3 files changed, 952 insertions(+), 310 deletions(-) create mode 100644 app/frontend/templates/panel/server_backup_edit.html diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index d436a72a..514ea494 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -677,16 +677,10 @@ class PanelHandler(BaseHandler): page_data["java_versions"] = page_java if subpage == "backup": 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["backups"] = self.controller.management.get_backups_by_server( server_id, model=True ) - exclusions = [] - page_data["exclusions"] = ( - self.controller.management.get_excluded_backup_dirs(server_id) - ) page_data["backing_up"] = ( self.controller.servers.get_server_instance_by_id( server_id @@ -698,20 +692,8 @@ class PanelHandler(BaseHandler): ).send_backup_status() ) # makes it so relative path is the only thing shown - for file in page_data["exclusions"]: - if Helpers.is_os_windows(): - exclusions.append(file.replace(server_info["path"] + "\\", "")) - else: - exclusions.append(file.replace(server_info["path"] + "/", "")) - page_data["exclusions"] = exclusions + self.controller.servers.refresh_server_settings(server_id) - try: - page_data["backup_list"] = server.list_backups() - except: - page_data["backup_list"] = [] - page_data["backup_path"] = Helpers.wtol_path( - page_data["backup_config"]["backup_path"] - ) if subpage == "metrics": try: @@ -1260,6 +1242,15 @@ class PanelHandler(BaseHandler): template = "panel/server_schedule_edit.html" + elif page == "edit_backup": + exclusions = [] + for file in page_data["exclusions"]: + if Helpers.is_os_windows(): + exclusions.append(file.replace(server_info["path"] + "\\", "")) + else: + exclusions.append(file.replace(server_info["path"] + "/", "")) + page_data["exclusions"] = exclusions + elif page == "edit_user": user_id = self.get_argument("id", None) role_servers = self.controller.servers.get_authorized_servers(user_id) diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 0ffba3c4..6a8098c4 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -43,35 +43,51 @@
-

{{ translate('serverBackups', 'backups', data['lang']) }}

+

{{ translate('serverBackups', 'backups', + data['lang']) }}

{% if data['user_data']['hints'] %} - + {% end %} - +
{% if len(data['backups']) == 0 %}
- {{ translate('serverBackups', 'no-backup', data['lang']) }} {{ translate('serverBackups', 'newBackup',data['lang']) }}. + {{ translate('serverBackups', 'no-backup', data['lang']) }} {{ + translate('serverBackups', 'newBackup',data['lang']) }}.
{% end %} {% if len(data['backups']) > 0 %}
-
{{ translate('backups', 'enabled', + {{ translate('serverBackups', 'enabled', data['lang']) }} Name {{ translate('backups', 'edit', data['lang']) + {{ translate('serverBackups', 'edit', data['lang']) }}
+
- - - - - + + + + + {% for backup in data['backups'] %} @@ -85,13 +101,17 @@

{{backup.max_backups}}

@@ -101,7 +121,8 @@
{{ translate('serverBackups', 'enabled', data['lang']) }}{{ translate('serverBackups', 'name', data['lang']) }} {{ translate('serverBackups', 'storageLocation', data['lang']) }}{{ translate('serverBackups', 'maxBackups', data['lang']) }}{{ translate('serverBackups', 'actions', data['lang']) }}{{ translate('serverBackups', 'enabled', + data['lang']) }}{{ translate('serverBackups', 'name', + data['lang']) }} {{ translate('serverBackups', + 'storageLocation', data['lang']) }}{{ translate('serverBackups', + 'maxBackups', data['lang']) }}{{ translate('serverBackups', 'actions', + data['lang']) }}
- - -
- +
@@ -124,13 +148,17 @@

{{backup.backup_name}}

@@ -144,207 +172,6 @@ -
-
-
-
- {% if data['backing_up'] %} -
-
{{ - data['backup_stats']['percent'] }}%
-
-

Backing up {{data['server_stats']['world_size']}}

- {% end %} - -
- {% if not data['backing_up'] %} -
- -
- {% end %} -
-
- {% if data['super_user'] %} - - - {% end %} -
- -
- - -
-
- - {% if data['backup_config']['compress'] %} - {{ translate('serverBackups', 'compress', data['lang']) }} - {% else %} - {{ - translate('serverBackups', 'compress', data['lang']) }} - {% end %} -
-
- - {% if data['backup_config']['shutdown'] %} - {{ translate('serverBackups', 'shutdown', data['lang']) }} - {% else %} - {{ - translate('serverBackups', 'shutdown', data['lang']) }} - {% end %} -
-
- - {% if data['backup_config']['before'] %} - {{ - translate('serverBackups', 'before', data['lang']) }} -
- - {% else %} - {{ - translate('serverBackups', 'before', data['lang']) }} -
- - {% end %} -
-
- - {% if data['backup_config']['after'] %} - {{ - translate('serverBackups', 'after', data['lang']) }} -
- - {% else %} - {{ - translate('serverBackups', 'after', data['lang']) }} -
- - {% end %} -
-
- -
- -
- - - - - -
- -
-
- -
{{ translate('serverBackups', 'enabled', @@ -116,7 +137,10 @@ {% for backup in data['backups'] %}
- - -
-

{{ translate('serverBackups', 'currentBackups', data['lang']) }}

- - - - - - - - - {% for backup in data['backup_list'] %} - - - - - - {% end %} - - -
{{ translate('serverBackups', 'options', data['lang']) }}{{ translate('serverBackups', 'path', data['lang']) }}{{ translate('serverBackups', 'size', data['lang']) }}
- - - {{ translate('serverBackups', 'download', data['lang']) }} - -
-
- - -
{{ backup['path'] }}{{ backup['size'] }}
- -
-
-
-
-
-
-
-

{{ translate('serverBackups', 'excludedBackups', - data['lang']) }}

-
-
-
    - {% for item in data['exclusions'] %} -
  • {{item}}
  • -
    - {% end %} -
-
@@ -414,15 +241,15 @@ async function backup_started() { const token = getCookie("_xsrf") let res = await fetch(`/api/v2/servers/${server_id}/action/backup_server`, { - method: 'POST', - headers: { - 'X-XSRFToken': token - } - }); - let responseData = await res.json(); - if (responseData.status === "ok") { - console.log(responseData); - $("#backup_button").html(`
+ method: 'POST', + headers: { + 'X-XSRFToken': token + } + }); + let responseData = await res.json(); + if (responseData.status === "ok") { + console.log(responseData); + $("#backup_button").html(`
{{ @@ -430,18 +257,18 @@

Backing up {{data['server_stats']['world_size']}}

`); - } else { + } else { - bootbox.alert({ - title: responseData.status, - message: responseData.error - }); - } + bootbox.alert({ + title: responseData.status, + message: responseData.error + }); + } return; } async function del_backup(filename, id) { const token = getCookie("_xsrf") - let contents = JSON.stringify({"filename": filename}) + let contents = JSON.stringify({ "filename": filename }) let res = await fetch(`/api/v2/servers/${id}/backups/backup/`, { method: 'DELETE', headers: { @@ -452,15 +279,17 @@ let responseData = await res.json(); if (responseData.status === "ok") { window.location.reload(); - }else{ - bootbox.alert({"title": responseData.status, - "message": responseData.error}) + } else { + bootbox.alert({ + "title": responseData.status, + "message": responseData.error + }) } } async function restore_backup(filename, id) { const token = getCookie("_xsrf") - let contents = JSON.stringify({"filename": filename}) + let contents = JSON.stringify({ "filename": filename }) var dialog = bootbox.dialog({ message: " {{ translate('serverBackups', 'restoring', data['lang']) }}", closeButton: false @@ -475,9 +304,11 @@ let responseData = await res.json(); if (responseData.status === "ok") { window.location.href = "/panel/dashboard"; - }else{ - bootbox.alert({"title": responseData.status, - "message": responseData.error}) + } else { + bootbox.alert({ + "title": responseData.status, + "message": responseData.error + }) } } @@ -529,7 +360,7 @@ $('input.excluded:checkbox:checked').each(function () { excluded.push($(this).val()); }); - if ($("#root_files_button").hasClass("clicked")){ + if ($("#root_files_button").hasClass("clicked")) { formDataObject.exclusions = excluded; } delete formDataObject.root_path @@ -711,54 +542,54 @@ }); } - function getDirView(event){ + function getDirView(event) { let path = event.target.parentElement.getAttribute("data-path"); if (document.getElementById(path).classList.contains('clicked')) { return; - }else{ + } else { getTreeView(path); } } - async function getTreeView(path){ + async function getTreeView(path) { console.log(path) const token = getCookie("_xsrf"); let res = await fetch(`/api/v2/servers/${server_id}/files`, { - method: 'POST', - headers: { - 'X-XSRFToken': token - }, - body: JSON.stringify({"page": "backups", "path": path}), + method: 'POST', + headers: { + 'X-XSRFToken': token + }, + body: JSON.stringify({ "page": "backups", "path": path }), + }); + let responseData = await res.json(); + if (responseData.status === "ok") { + console.log(responseData); + process_tree_response(responseData); + + } else { + + bootbox.alert({ + title: responseData.status, + message: responseData.error }); - let responseData = await res.json(); - if (responseData.status === "ok") { - console.log(responseData); - process_tree_response(responseData); - - } else { - - bootbox.alert({ - title: responseData.status, - message: responseData.error - }); - } + } } function process_tree_response(response) { let path = response.data.root_path.path; let text = `
    `; - Object.entries(response.data).forEach(([key, value]) => { - if (key === "root_path" || key === "db_stats"){ - //continue is not valid in for each. Return acts as a continue. - return; - } + Object.entries(response.data).forEach(([key, value]) => { + if (key === "root_path" || key === "db_stats") { + //continue is not valid in for each. Return acts as a continue. + return; + } let checked = "" let dpath = value.path; let filename = key; - if (value.excluded){ + if (value.excluded) { checked = "checked" } - if (value.dir){ + if (value.dir) { text += `
  • \n
    @@ -768,7 +599,7 @@ ${filename}
  • ` - }else{ + } else { text += `
  • `; - if(response.data.root_path.top){ + if (response.data.root_path.top) { try { - document.getElementById('main-tree-div').innerHTML += text; - document.getElementById('main-tree').parentElement.classList.add("clicked"); - } catch { - document.getElementById('files-tree').innerHTML = text; - } - }else{ + document.getElementById('main-tree-div').innerHTML += text; + document.getElementById('main-tree').parentElement.classList.add("clicked"); + } catch { + document.getElementById('files-tree').innerHTML = text; + } + } else { try { - document.getElementById(path + "span").classList.add('tree-caret-down'); - document.getElementById(path).innerHTML += text; - document.getElementById(path).classList.add("clicked"); - } catch { - console.log("Bad") - } + document.getElementById(path + "span").classList.add('tree-caret-down'); + document.getElementById(path).innerHTML += text; + document.getElementById(path).classList.add("clicked"); + } catch { + console.log("Bad") + } - var toggler = document.getElementById(path + "span"); + var toggler = document.getElementById(path + "span"); - if (toggler.classList.contains('files-tree-title')) { - document.getElementById(path + "span").addEventListener("click", function caretListener() { - document.getElementById(path + "ul").classList.toggle("d-block"); - document.getElementById(path + "span").classList.toggle("tree-caret-down"); - }); - } + if (toggler.classList.contains('files-tree-title')) { + document.getElementById(path + "span").addEventListener("click", function caretListener() { + document.getElementById(path + "ul").classList.toggle("d-block"); + document.getElementById(path + "span").classList.toggle("tree-caret-down"); + }); + } } } diff --git a/app/frontend/templates/panel/server_backup_edit.html b/app/frontend/templates/panel/server_backup_edit.html new file mode 100644 index 00000000..0ffba3c4 --- /dev/null +++ b/app/frontend/templates/panel/server_backup_edit.html @@ -0,0 +1,820 @@ +{% extends ../base.html %} + +{% block meta %} +{% end %} + +{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %} + +{% block content %} + +
    + + +
    +
    + +
    + +
    + + + {% include "parts/details_stats.html %} + +
    + +
    +
    +
    + + + {% include "parts/server_controls_list.html %} + + + {% include "parts/m_server_controls_list.html %} + +
    +
    +
    +
    +

    {{ translate('serverBackups', 'backups', data['lang']) }}

    + {% if data['user_data']['hints'] %} + + {% end %} + +
    +
    + {% if len(data['backups']) == 0 %} +
    + {{ translate('serverBackups', 'no-backup', data['lang']) }} {{ translate('serverBackups', 'newBackup',data['lang']) }}. +
    + {% end %} + {% if len(data['backups']) > 0 %} +
    + + + + + + + + + + + + {% for backup in data['backups'] %} + + + + + + + + {% end %} + +
    {{ translate('serverBackups', 'enabled', data['lang']) }}{{ translate('serverBackups', 'name', data['lang']) }} {{ translate('serverBackups', 'storageLocation', data['lang']) }}{{ translate('serverBackups', 'maxBackups', data['lang']) }}{{ translate('serverBackups', 'actions', data['lang']) }}
    + + +

    {{backup.backup_name}}

    +
    +

    {{backup.backup_location}}

    +
    +

    {{backup.max_backups}}

    +
    + + + +
    +
    +
    + + + + + + + + + + {% for backup in data['backups'] %} + + + + + + {% end %} + +
    {{ translate('serverBackups', 'enabled', + data['lang']) }}Name + {{ translate('serverBackups', 'edit', data['lang']) + }}
    + + +

    {{backup.backup_name}}

    +
    + + + +
    +
    + {% end %} +
    +
    +
    +
    +
    +
    +
    +
    + {% if data['backing_up'] %} +
    +
    {{ + data['backup_stats']['percent'] }}%
    +
    +

    Backing up {{data['server_stats']['world_size']}}

    + {% end %} + +
    + {% if not data['backing_up'] %} +
    + +
    + {% end %} +
    +
    + {% if data['super_user'] %} + + + {% end %} +
    + +
    + + +
    +
    + + {% if data['backup_config']['compress'] %} + {{ translate('serverBackups', 'compress', data['lang']) }} + {% else %} + {{ + translate('serverBackups', 'compress', data['lang']) }} + {% end %} +
    +
    + + {% if data['backup_config']['shutdown'] %} + {{ translate('serverBackups', 'shutdown', data['lang']) }} + {% else %} + {{ + translate('serverBackups', 'shutdown', data['lang']) }} + {% end %} +
    +
    + + {% if data['backup_config']['before'] %} + {{ + translate('serverBackups', 'before', data['lang']) }} +
    + + {% else %} + {{ + translate('serverBackups', 'before', data['lang']) }} +
    + + {% end %} +
    +
    + + {% if data['backup_config']['after'] %} + {{ + translate('serverBackups', 'after', data['lang']) }} +
    + + {% else %} + {{ + translate('serverBackups', 'after', data['lang']) }} +
    + + {% end %} +
    +
    + +
    + +
    + + + + +
    +
    + +
    +
    + + +

    {{ translate('serverBackups', 'currentBackups', data['lang']) }}

    + + + + + + + + + {% for backup in data['backup_list'] %} + + + + + + {% end %} + + +
    {{ translate('serverBackups', 'options', data['lang']) }}{{ translate('serverBackups', 'path', data['lang']) }}{{ translate('serverBackups', 'size', data['lang']) }}
    + + + {{ translate('serverBackups', 'download', data['lang']) }} + +
    +
    + + +
    {{ backup['path'] }}{{ backup['size'] }}
    + +
    +
    +
    +
    +
    +
    +
    +

    {{ translate('serverBackups', 'excludedBackups', + data['lang']) }}

    +
    +
    +
      + {% for item in data['exclusions'] %} +
    • {{item}}
    • +
      + {% end %} +
    +
    + +
    +
    +
    +
    + + + +
    + + + +{% end %} + +{% block js %} + + +{% end %} \ No newline at end of file From eebf68a37603d6a923f5101db335c9e5f565b581 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 21 Apr 2024 11:26:16 -0400 Subject: [PATCH 011/155] Front end loading of backup edit page --- .../controllers/management_controller.py | 8 +- app/classes/models/management.py | 4 +- app/classes/web/panel_handler.py | 65 +++++ .../templates/panel/server_backup.html | 14 +- .../templates/panel/server_backup_edit.html | 263 ++++++------------ 5 files changed, 161 insertions(+), 193 deletions(-) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 42df3e11..7fea8a34 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -178,8 +178,8 @@ class ManagementController: # Backups Methods # ********************************************************************************** @staticmethod - def get_backup_config(server_id): - return HelpersManagement.get_backup_config(server_id) + def get_backup_config(backup_id): + return HelpersManagement.get_backup_config(backup_id) @staticmethod def get_backups_by_server(server_id, model=False): @@ -212,8 +212,8 @@ class ManagementController: ) @staticmethod - def get_excluded_backup_dirs(server_id: int): - return HelpersManagement.get_excluded_backup_dirs(server_id) + def get_excluded_backup_dirs(backup_id: int): + return HelpersManagement.get_excluded_backup_dirs(backup_id) def add_excluded_backup_dir(self, server_id: int, dir_to_add: str): self.management_helper.add_excluded_backup_dir(server_id, dir_to_add) diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 5a3df8b1..dc754c72 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -455,7 +455,7 @@ class HelpersManagement: dir_list = [] return dir_list - def add_excluded_backup_dir(self, server_id: int, dir_to_add: str): + def add_excluded_backup_dir(self, server_id: str, dir_to_add: str): dir_list = self.get_excluded_backup_dirs(server_id) if dir_to_add not in dir_list: dir_list.append(dir_to_add) @@ -467,7 +467,7 @@ class HelpersManagement: f"already in the excluded directory list for server ID {server_id}" ) - def del_excluded_backup_dir(self, server_id: int, dir_to_del: str): + def del_excluded_backup_dir(self, server_id: str, dir_to_del: str): dir_list = self.get_excluded_backup_dirs(server_id) if dir_to_del in dir_list: dir_list.remove(dir_to_del) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 514ea494..ac5a8b41 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -1243,7 +1243,66 @@ class PanelHandler(BaseHandler): template = "panel/server_schedule_edit.html" elif page == "edit_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"] = self.controller.management.get_backup_config( + backup_id + ) + page_data["backups"] = self.controller.management.get_backups_by_server( + server_id, model=True + ) exclusions = [] + page_data["backing_up"] = self.controller.servers.get_server_instance_by_id( + server_id + ).is_backingup + 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) + try: + page_data["backup_list"] = server.list_backups() + except: + 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"] = ( + self.controller.management.get_excluded_backup_dirs(backup_id) + ) + # Make exclusion paths relative for page for file in page_data["exclusions"]: if Helpers.is_os_windows(): exclusions.append(file.replace(server_info["path"] + "\\", "")) @@ -1251,6 +1310,12 @@ class PanelHandler(BaseHandler): exclusions.append(file.replace(server_info["path"] + "/", "")) page_data["exclusions"] = 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) diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 6a8098c4..c5c0cdaf 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -85,7 +85,8 @@ - - diff --git a/app/frontend/templates/panel/server_backup_edit.html b/app/frontend/templates/panel/server_backup_edit.html index 0ffba3c4..c68d521f 100644 --- a/app/frontend/templates/panel/server_backup_edit.html +++ b/app/frontend/templates/panel/server_backup_edit.html @@ -39,111 +39,6 @@ {% include "parts/m_server_controls_list.html %} -
    -
    -
    -
    -

    {{ translate('serverBackups', 'backups', data['lang']) }}

    - {% if data['user_data']['hints'] %} - - {% end %} - -
    -
    - {% if len(data['backups']) == 0 %} -
    - {{ translate('serverBackups', 'no-backup', data['lang']) }} {{ translate('serverBackups', 'newBackup',data['lang']) }}. -
    - {% end %} - {% if len(data['backups']) > 0 %} -
    - - - - - - - - - - - - {% for backup in data['backups'] %} - - - - - - - - {% end %} - -
    {{ translate('serverBackups', 'enabled', data['lang']) }}{{ translate('serverBackups', 'name', data['lang']) }} {{ translate('serverBackups', 'storageLocation', data['lang']) }}{{ translate('serverBackups', 'maxBackups', data['lang']) }}{{ translate('serverBackups', 'actions', data['lang']) }}
    - - -

    {{backup.backup_name}}

    -
    -

    {{backup.backup_location}}

    -
    -

    {{backup.max_backups}}

    -
    - - - -
    -
    -
    - - - - - - - - - - {% for backup in data['backups'] %} - - - - - - {% end %} - -
    {{ translate('serverBackups', 'enabled', - data['lang']) }}Name - {{ translate('serverBackups', 'edit', data['lang']) - }}
    - - -

    {{backup.backup_name}}

    -
    - - - -
    -
    - {% end %} -
    -
    -
    -

    @@ -173,7 +68,7 @@ class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }} {% end %}
    @@ -272,8 +167,10 @@
@@ -309,7 +206,7 @@

- -

{{backup.backup_name}}

@@ -126,8 +115,6 @@ width="100%" style="table-layout:fixed;"> - {{ translate('serverBackups', 'enabled', - data['lang']) }} Name {{ translate('serverBackups', 'edit', data['lang']) @@ -137,15 +124,6 @@ {% for backup in data['backups'] %} - - -

{{backup.backup_name}}

diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py index 49c7643c..85106e16 100644 --- a/app/migrations/20240308_multi-backup.py +++ b/app/migrations/20240308_multi-backup.py @@ -19,7 +19,6 @@ def migrate(migrator: Migrator, database, **kwargs): migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4)) migrator.add_columns("backups", backup_name=peewee.CharField(default="Default")) migrator.add_columns("backups", backup_location=peewee.CharField(default="")) - migrator.add_columns("backups", enabled=peewee.BooleanField(default=True)) class Servers(peewee.Model): server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) @@ -59,7 +58,6 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown = peewee.BooleanField(default=False) before = peewee.CharField(default="") after = peewee.CharField(default="") - enabled = peewee.BooleanField(default=True) class Meta: table_name = "new_backups" @@ -85,7 +83,6 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown=backup.shutdown, before=backup.before, after=backup.after, - enabled=True, ) # Drop the existing backups table From f845f546547636bd53a60d7f452852d8b7abac55 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 18 May 2024 20:32:27 -0400 Subject: [PATCH 015/155] Add info note to default creds file --- main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index fd84328b..617fe5f4 100644 --- a/main.py +++ b/main.py @@ -367,7 +367,15 @@ if __name__ == "__main__": encoding="utf-8", ) as cred_file: cred_file.write( - json.dumps({"username": "admin", "password": PASSWORD}, indent=4) + json.dumps( + { + "username": "admin", + "password": PASSWORD, + "info": "This is NOT where you change your password." + " This file is only a means to give you a default password.", + }, + indent=4, + ) ) os.chmod( os.path.join(APPLICATION_PATH, "app", "config", "default-creds.txt"), 0o600 From 5d53a1fa46b73be5687152087e153200e776af82 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Thu, 23 May 2024 21:54:35 +0100 Subject: [PATCH 016/155] Pin sonar-scanner-cli to 5.0.1 Latest Broken --- .gitlab/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/lint.yml b/.gitlab/lint.yml index 37649e1a..d64cb921 100644 --- a/.gitlab/lint.yml +++ b/.gitlab/lint.yml @@ -66,7 +66,7 @@ pylint: sonarcloud-check: stage: lint image: - name: sonarsource/sonar-scanner-cli:latest + name: sonarsource/sonar-scanner-cli:5.0.1 entrypoint: [""] tags: - saas-linux-medium-amd64 From 2a6c0ca75133a72062e1afbb985663f406f2dad0 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Thu, 23 May 2024 23:40:03 +0100 Subject: [PATCH 017/155] Revert pinned sonarq version, They've moved to a rootless image and cache was retaining files with root permissions, solution is to clear cache --- .gitlab/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/lint.yml b/.gitlab/lint.yml index d64cb921..37649e1a 100644 --- a/.gitlab/lint.yml +++ b/.gitlab/lint.yml @@ -66,7 +66,7 @@ pylint: sonarcloud-check: stage: lint image: - name: sonarsource/sonar-scanner-cli:5.0.1 + name: sonarsource/sonar-scanner-cli:latest entrypoint: [""] tags: - saas-linux-medium-amd64 From 895ba2d2f595cc228eadff38429b6098129e1096 Mon Sep 17 00:00:00 2001 From: Analicia Abernathy Date: Mon, 20 May 2024 10:20:17 -0500 Subject: [PATCH 018/155] updates for the big bucket release --- app/translations/th_TH.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/translations/th_TH.json b/app/translations/th_TH.json index 8c9d014e..28322a72 100644 --- a/app/translations/th_TH.json +++ b/app/translations/th_TH.json @@ -184,6 +184,8 @@ "error": { "agree": "ยอมรับ", "bedrockError": "การดาวน์โหลดเวอร์ชั่น Bedrock ไม่พร้อมใช้งาน โปรดตรวจสอบ", + "bigBucket1": "การตรวจสอบสุขภาพ Big Bucket ล้มเหลว โปรดตรวจสอบ", + "bigBucket2": "สำหรับข้อมูลล่าสุด", "cancel": "ยกเลิก", "contact": "ติดต่อฝ่ายสนับสนุน Crafty Control ผ่านดิสคอร์ด", "craftyStatus": "หน้าสถานะของ Crafty", @@ -206,6 +208,7 @@ "portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล", "privMsg": "และ ", "return": "ย้อนกลับไปยังแผงควบคุม", + "selfHost": "หากคุณโฮสต์ repo นี้ด้วยตนเอง โปรดตรวจสอบที่อยู่ของคุณ หรือศึกษาคู่มือแก้ปัญหาของเรา", "serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ", "serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด", "start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}", @@ -602,6 +605,7 @@ }, "startup": { "almost": "เสร็จสิ้นการทำงาน. รอซักครู่...", + "cache": "กำลังรีเฟรชไฟล์แคช Big Bucket", "internals": "กำหนดค่าและเริ่มการทำงานภายในของ Crafty", "internet": "กำลังตรวจสอบการเชื่อมต่ออินเทอร์เน็ต", "server": "กำลังเริ่มต้นการทำงาน ", From fdd1d2fca3d4559f85ec42a87197101c4a44ff91 Mon Sep 17 00:00:00 2001 From: Analicia Abernathy Date: Thu, 23 May 2024 13:21:55 -0500 Subject: [PATCH 019/155] added big bucket translations --- app/translations/he_IL.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/translations/he_IL.json b/app/translations/he_IL.json index 865222cd..9dfe860b 100644 --- a/app/translations/he_IL.json +++ b/app/translations/he_IL.json @@ -184,6 +184,8 @@ "error": { "agree": "מסכים", "bedrockError": "הורדות Bedrock אינן זמינות. אנא בדוק", + "bigBucket1": "בדיקת הבריאות של Big Bucket נכשלה. אנא בדוק", + "bigBucket2": "כדי לקבל את המידע המעודכן ביותר.", "cancel": "בטל", "contact": "בבקשה צרו קשר עם תמיכת פאנל קראפטי באמצעות דיסקורד", "craftyStatus": "דף המצב של Crafty", @@ -206,6 +208,7 @@ "portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.", "privMsg": "וה", "return": "חזרה לפאנל", + "selfHost": "אם אתה מארח בעצמך את הריפו הזה, אנא בדוק את הכתובת שלך או התייעץ עם מדריך פתרון הבעיות שלנו.", "serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק", "serverJars2": "למידע מעודכן ביותר.", "start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}", @@ -603,6 +606,7 @@ }, "startup": { "almost": "מסיימים. תחזיקו חזק...", + "cache": "מרענן את קובץ המטמון של Big Bucket", "internals": "הגדרה והפעלה של הרכיבים הפנימיים של Crafty", "internet": "בודק את חיבור האינטרנט", "server": "אתחול ", From f8626633cfcaf2c31304cb18371b768e09506a49 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 13:51:40 -0400 Subject: [PATCH 020/155] Add action ID option to schedules --- .../controllers/management_controller.py | 2 + app/classes/models/management.py | 4 ++ app/classes/shared/tasks.py | 1 + app/migrations/20240308_multi-backup.py | 58 ++++++++++++++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index fc661e84..6f4da3cb 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -123,6 +123,7 @@ class ManagementController: cron_string="* * * * *", parent=None, delay=0, + action_id=None, ): return HelpersManagement.create_scheduled_task( server_id, @@ -137,6 +138,7 @@ class ManagementController: cron_string, parent, delay, + action_id, ) @staticmethod diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 3390e428..24505471 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -89,6 +89,7 @@ class Schedules(BaseModel): interval_type = CharField() start_time = CharField(null=True) command = CharField(null=True) + action_id = CharField(null=True) name = CharField() one_time = BooleanField(default=False) cron_string = CharField(default="") @@ -114,6 +115,7 @@ class Backups(BaseModel): shutdown = BooleanField(default=False) before = CharField(default="") after = CharField(default="") + enabled = BooleanField(default=True) class Meta: table_name = "backups" @@ -268,6 +270,7 @@ class HelpersManagement: cron_string="* * * * *", parent=None, delay=0, + action_id=None, ): sch_id = Schedules.insert( { @@ -278,6 +281,7 @@ class HelpersManagement: Schedules.interval_type: interval_type, Schedules.start_time: start_time, Schedules.command: command, + Schedules.action_id: action_id, Schedules.name: name, Schedules.one_time: one_time, Schedules.cron_string: cron_string, diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index b9513441..57e3c4d0 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -337,6 +337,7 @@ class TasksManager: job_data["cron_string"], job_data["parent"], job_data["delay"], + job_data.get("action_id", None), ) # Checks to make sure some doofus didn't actually make the newly diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py index 85106e16..4aa854b9 100644 --- a/app/migrations/20240308_multi-backup.py +++ b/app/migrations/20240308_multi-backup.py @@ -5,7 +5,7 @@ import logging from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory -from app.classes.models.management import Backups +from app.classes.models.management import Backups, Schedules logger = logging.getLogger(__name__) @@ -19,6 +19,10 @@ def migrate(migrator: Migrator, database, **kwargs): migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4)) migrator.add_columns("backups", backup_name=peewee.CharField(default="Default")) migrator.add_columns("backups", backup_location=peewee.CharField(default="")) + migrator.add_columns("backups", enabled=peewee.BooleanField(default=True)) + migrator.add_columns( + "schedules", action_id=peewee.CharField(null=True, default=None) + ) class Servers(peewee.Model): server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) @@ -58,12 +62,34 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown = peewee.BooleanField(default=False) before = peewee.CharField(default="") after = peewee.CharField(default="") + enabled = peewee.BooleanField(default=True) class Meta: table_name = "new_backups" database = db + class NewSchedules(peewee.Model): + schedule_id = peewee.IntegerField(unique=True, primary_key=True) + server_id = peewee.ForeignKeyField(Servers, backref="schedule_server") + enabled = peewee.BooleanField() + action = peewee.CharField() + interval = peewee.IntegerField() + interval_type = peewee.CharField() + start_time = peewee.CharField(null=True) + command = peewee.CharField(null=True) + action_id = peewee.CharField(null=True) + name = peewee.CharField() + one_time = peewee.BooleanField(default=False) + cron_string = peewee.CharField(default="") + parent = peewee.IntegerField(null=True) + delay = peewee.IntegerField(default=0) + next_run = peewee.CharField(default="") + + class Meta: + table_name = "new_schedules" + migrator.create_table(NewBackups) + migrator.create_table(NewSchedules) migrator.run() @@ -83,6 +109,7 @@ def migrate(migrator: Migrator, database, **kwargs): shutdown=backup.shutdown, before=backup.before, after=backup.after, + enabled=True, ) # Drop the existing backups table @@ -92,6 +119,35 @@ def migrate(migrator: Migrator, database, **kwargs): migrator.rename_table("new_backups", "backups") migrator.drop_columns("servers", ["backup_path"]) + for schedule in Schedules.select(): + action_id = None + if schedule.command == "backup_server": + backup = NewBackups.get(NewBackups.server_id == schedule.server_id) + action_id = backup.backup_id + NewSchedules.create( + schedule_id=schedule.schedule_id, + server_id=schedule.server_id, + enabled=schedule.enabled, + action=schedule.action, + interval=schedule.interval, + interval_type=schedule.interval_type, + start_time=schedule.start_time, + command=schedule.command, + action_id=action_id, + name=schedule.name, + one_time=schedule.one_time, + cron_string=schedule.cron_string, + parent=schedule.parent, + delay=schedule.delay, + next_run=schedule.next_run, + ) + + # Drop the existing backups table + migrator.drop_table("schedules") + + # Rename the new table to backups + migrator.rename_table("new_schedules", "schedules") + def rollback(migrator: Migrator, database, **kwargs): """ From 60d3ee1aa8daca53273a2ebd4afbc57ae4b381d9 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 14:40:14 -0400 Subject: [PATCH 021/155] Working default backup --- .../controllers/management_controller.py | 9 ++- app/classes/shared/server.py | 75 ++++++++----------- app/classes/shared/tasks.py | 2 +- app/classes/web/routes/api/api_handlers.py | 2 +- .../web/routes/api/servers/server/action.py | 5 +- .../templates/panel/server_backup.html | 60 ++------------- 6 files changed, 49 insertions(+), 104 deletions(-) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 6f4da3cb..057ffd5e 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -75,7 +75,7 @@ class ManagementController: # Commands Methods # ********************************************************************************** - def send_command(self, user_id, server_id, remote_ip, command): + def send_command(self, user_id, server_id, remote_ip, command, action_id=None): server_name = HelperServers.get_server_friendly_name(server_id) # Example: Admin issued command start_server for server Survival @@ -86,7 +86,12 @@ class ManagementController: remote_ip, ) self.queue_command( - {"server_id": server_id, "user_id": user_id, "command": command} + { + "server_id": server_id, + "user_id": user_id, + "command": command, + "action_id": action_id, + } ) def queue_command(self, command_data): diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index a6c98b89..0302b803 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -207,9 +207,6 @@ class ServerInstance: self.server_scheduler.start() self.dir_scheduler.start() self.start_dir_calc_task() - self.backup_thread = threading.Thread( - target=self.backup_server, daemon=True, name=f"backup_{self.name}" - ) self.is_backingup = False # Reset crash and update at initialization self.stats_helper.server_crash_reset() @@ -940,8 +937,7 @@ class ServerInstance: WebSocketManager().broadcast_user(user, "send_start_reload", {}) def restart_threaded_server(self, user_id): - bu_conf = HelpersManagement.get_backup_config(self.server_id) - if self.is_backingup and bu_conf["shutdown"]: + if self.is_backingup: logger.info( "Restart command detected. Supressing - server has" " backup shutdown enabled and server is currently backing up." @@ -1111,12 +1107,12 @@ class ServerInstance: f.write("eula=true") self.run_threaded_server(user_id) - def a_backup_server(self): - if self.settings["backup_path"] == "": - logger.critical("Backup path is None. Canceling Backup!") - return + def a_backup_server(self, backup_id): backup_thread = threading.Thread( - target=self.backup_server, daemon=True, name=f"backup_{self.name}" + target=self.backup_server, + daemon=True, + name=f"backup_{self.name}", + args=[backup_id], ) logger.info( f"Starting Backup Thread for server {self.settings['server_name']}." @@ -1144,7 +1140,7 @@ class ServerInstance: logger.info(f"Backup Thread started for server {self.settings['server_name']}.") @callback - def backup_server(self): + def backup_server(self, backup_id): 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) @@ -1157,7 +1153,12 @@ class ServerInstance: ).format(self.name), ) time.sleep(3) - conf = HelpersManagement.get_backup_config(self.server_id) + conf = HelpersManagement.get_backup_config(backup_id) + backup_location = conf["backup_location"] + if not backup_location: + Console.critical("No backup path found. Canceling") + self.is_backingup = False + return None if conf["before"]: if self.check_running(): logger.debug( @@ -1177,10 +1178,10 @@ class ServerInstance: self.stop_server() was_server_running = True - self.helper.ensure_dir_exists(self.settings["backup_path"]) + self.helper.ensure_dir_exists(backup_location) try: backup_filename = ( - f"{self.settings['backup_path']}/" + f"{backup_location}/" f"{datetime.datetime.now().astimezone(self.tz).strftime('%Y-%m-%d_%H-%M-%S')}" # pylint: disable=line-too-long ) logger.info( @@ -1188,36 +1189,24 @@ class ServerInstance: f" (ID#{self.server_id}, path={self.server_path}) " f"at '{backup_filename}'" ) - excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id) + excluded_dirs = HelpersManagement.get_excluded_backup_dirs(backup_id) server_dir = Helpers.get_os_understandable_path(self.settings["path"]) - if conf["compress"]: - logger.debug( - "Found compress backup to be true. Calling compressed archive" - ) - self.file_helper.make_compressed_backup( - Helpers.get_os_understandable_path(backup_filename), - server_dir, - excluded_dirs, - self.server_id, - ) - else: - logger.debug( - "Found compress backup to be false. Calling NON-compressed archive" - ) - self.file_helper.make_backup( - Helpers.get_os_understandable_path(backup_filename), - server_dir, - excluded_dirs, - self.server_id, - ) + + self.file_helper.make_backup( + Helpers.get_os_understandable_path(backup_filename), + server_dir, + excluded_dirs, + self.server_id, + conf["compress"], + ) while ( - len(self.list_backups()) > conf["max_backups"] + len(self.list_backups(backup_location)) > 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']}" + oldfile_path = f"{backup_location}/{oldfile['path']}" logger.info(f"Removing old backup '{oldfile['path']}'") os.remove(Helpers.get_os_understandable_path(oldfile_path)) @@ -1297,28 +1286,26 @@ class ServerInstance: except: return {"percent": 0, "total_files": 0} - def list_backups(self): - if not self.settings["backup_path"]: + def list_backups(self, backup_location): + if not backup_location: logger.info( f"Error putting backup file list for server with ID: {self.server_id}" ) return [] if not Helpers.check_path_exists( - Helpers.get_os_understandable_path(self.settings["backup_path"]) + Helpers.get_os_understandable_path(backup_location) ): return [] files = Helpers.get_human_readable_files_sizes( Helpers.list_dir_by_date( - Helpers.get_os_understandable_path(self.settings["backup_path"]) + Helpers.get_os_understandable_path(backup_location) ) ) return [ { "path": os.path.relpath( f["path"], - start=Helpers.get_os_understandable_path( - self.settings["backup_path"] - ), + start=Helpers.get_os_understandable_path(backup_location), ), "size": f["size"], } diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 57e3c4d0..af3d8227 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -140,7 +140,7 @@ class TasksManager: ) elif command == "backup_server": - svr.a_backup_server() + svr.a_backup_server(cmd["action_id"]) elif command == "update_executable": svr.jar_update() diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index a30350a5..20586b1a 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -273,7 +273,7 @@ def api_handlers(handler_args): handler_args, ), ( - r"/api/v2/servers/([a-z0-9-]+)/action/([a-z_]+)/?", + r"/api/v2/servers/([a-z0-9-]+)/action/([a-z_]+)/([a-z0-9-]+)/?", ApiServersServerActionHandler, handler_args, ), diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py index aba06da3..3608058f 100644 --- a/app/classes/web/routes/api/servers/server/action.py +++ b/app/classes/web/routes/api/servers/server/action.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) class ApiServersServerActionHandler(BaseApiHandler): - def post(self, server_id: str, action: str): + def post(self, server_id: str, action: str, action_id=None): auth_data = self.authenticate_user() if not auth_data: return @@ -54,7 +54,7 @@ class ApiServersServerActionHandler(BaseApiHandler): return self._agree_eula(server_id, auth_data[4]["user_id"]) self.controller.management.send_command( - auth_data[4]["user_id"], server_id, self.get_remote_ip(), action + auth_data[4]["user_id"], server_id, self.get_remote_ip(), action, action_id ) self.finish_json( @@ -93,7 +93,6 @@ class ApiServersServerActionHandler(BaseApiHandler): new_server_name, new_server_id, new_server_path, - new_backup_path, new_server_command, server_data.get("executable"), new_server_log_path, diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 63cfaca2..2a34902c 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -101,8 +101,8 @@ @@ -218,9 +218,10 @@ return r ? r[1] : undefined; } - async function backup_started() { + async function backup_started(backup_id) { const token = getCookie("_xsrf") - let res = await fetch(`/api/v2/servers/${server_id}/action/backup_server`, { + console.log(backup_id) + let res = await fetch(`/api/v2/servers/${server_id}/action/backup_server/${backup_id}/`, { method: 'POST', headers: { 'X-XSRFToken': token @@ -322,53 +323,6 @@ } $(document).ready(function () { - $("#backup-form").on("submit", async function (e) { - e.preventDefault(); - const token = getCookie("_xsrf") - let backupForm = document.getElementById("backup-form"); - - let formData = new FormData(backupForm); - //Remove checks that we don't need in form data. - formData.delete("after-check"); - formData.delete("before-check"); - //Create an object from the form data entries - let formDataObject = Object.fromEntries(formData.entries()); - //We need to make sure these are sent regardless of whether or not they're checked - formDataObject.compress = $("#compress").prop('checked'); - formDataObject.shutdown = $("#shutdown").prop('checked'); - let excluded = []; - $('input.excluded:checkbox:checked').each(function () { - excluded.push($(this).val()); - }); - if ($("#root_files_button").hasClass("clicked")) { - formDataObject.exclusions = excluded; - } - delete formDataObject.root_path - console.log(excluded); - console.log(formDataObject); - // Format the plain form data as JSON - let formDataJsonString = JSON.stringify(formDataObject, replacer); - - console.log(formDataJsonString); - - let res = await fetch(`/api/v2/servers/${server_id}/backups/`, { - method: 'PATCH', - headers: { - 'X-XSRFToken': token - }, - body: formDataJsonString, - }); - let responseData = await res.json(); - if (responseData.status === "ok") { - window.location.reload(); - } else { - - bootbox.alert({ - title: responseData.error, - message: responseData.error_data - }); - } - }); try { if ($('#backup_path').val() == '') { @@ -461,8 +415,8 @@ } }); }); - $("#backup_now_button").click(function () { - backup_started(); + $(".backup_now_button").click(function () { + backup_started($(this).data('backup')); }); }); From d55e7c9e64e252b05b9fa025a4c28a0bd58a19c3 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 14:40:21 -0400 Subject: [PATCH 022/155] Remove repeated code --- app/classes/shared/file_helpers.py | 78 ++++-------------------------- 1 file changed, 10 insertions(+), 68 deletions(-) diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 90d8e65c..d6c1199f 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -229,74 +229,14 @@ class FileHelpers: return True - def make_compressed_backup( - self, path_to_destination, path_to_zip, excluded_dirs, server_id, comment="" - ): - # create a ZipFile object - path_to_destination += ".zip" - ex_replace = [p.replace("\\", "/") for p in excluded_dirs] - total_bytes = 0 - dir_bytes = Helpers.get_dir_size(path_to_zip) - results = { - "percent": 0, - "total_files": self.helper.human_readable_file_size(dir_bytes), - } - WebSocketManager().broadcast_page_params( - "/panel/server_detail", - {"id": str(server_id)}, - "backup_status", - results, - ) - with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file: - zip_file.comment = bytes( - comment, "utf-8" - ) # comments over 65535 bytes will be truncated - for root, dirs, files in os.walk(path_to_zip, topdown=True): - for l_dir in dirs: - if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace: - dirs.remove(l_dir) - ziproot = path_to_zip - for file in files: - if ( - str(os.path.join(root, file)).replace("\\", "/") - not in ex_replace - and file != "crafty.sqlite" - ): - try: - logger.info(f"backing up: {os.path.join(root, file)}") - if os.name == "nt": - zip_file.write( - os.path.join(root, file), - os.path.join(root.replace(ziproot, ""), file), - ) - else: - zip_file.write( - os.path.join(root, file), - os.path.join(root.replace(ziproot, "/"), file), - ) - - except Exception as e: - logger.warning( - f"Error backing up: {os.path.join(root, file)}!" - f" - Error was: {e}" - ) - total_bytes += os.path.getsize(os.path.join(root, file)) - percent = round((total_bytes / dir_bytes) * 100, 2) - results = { - "percent": percent, - "total_files": self.helper.human_readable_file_size(dir_bytes), - } - WebSocketManager().broadcast_page_params( - "/panel/server_detail", - {"id": str(server_id)}, - "backup_status", - results, - ) - - return True - def make_backup( - self, path_to_destination, path_to_zip, excluded_dirs, server_id, comment="" + self, + path_to_destination, + path_to_zip, + excluded_dirs, + server_id, + comment="", + compressed=None, ): # create a ZipFile object path_to_destination += ".zip" @@ -313,7 +253,9 @@ class FileHelpers: "backup_status", results, ) - with ZipFile(path_to_destination, "w") as zip_file: + # Set the compression mode based on the `compressed` parameter + compression_mode = ZIP_DEFLATED if compressed else None + with ZipFile(path_to_destination, "w", compression_mode) as zip_file: zip_file.comment = bytes( comment, "utf-8" ) # comments over 65535 bytes will be truncated From c037f1d1afdee99fc6f2cd722b966e60e771a4c5 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 15:11:46 -0400 Subject: [PATCH 023/155] Lint --- app/migrations/20240308_multi-backup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/migrations/20240308_multi-backup.py b/app/migrations/20240308_multi-backup.py index 4aa854b9..dd80ba77 100644 --- a/app/migrations/20240308_multi-backup.py +++ b/app/migrations/20240308_multi-backup.py @@ -98,10 +98,12 @@ def migrate(migrator: Migrator, database, **kwargs): # Fetch the related server entry from the Servers table server = Servers.get(Servers.server_id == backup.server_id) - # Create a new backup entry with data from the old backup entry and related server + # Create a new backup entry with data from the + # old backup entry and related server NewBackups.create( backup_name="Default", - backup_location=server.backup_path, # Set backup_location equal to backup_path + # Set backup_location equal to backup_path + backup_location=server.backup_path, excluded_dirs=backup.excluded_dirs, max_backups=backup.max_backups, server_id=server.server_id, From 41147266ad52fc2f2c5f10e1ba0c63eb5b8c53b8 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 15:11:58 -0400 Subject: [PATCH 024/155] Fix issue with backup compression --- app/classes/shared/file_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index d6c1199f..3c14447c 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -4,7 +4,7 @@ import logging import pathlib import tempfile import zipfile -from zipfile import ZipFile, ZIP_DEFLATED +from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED import urllib.request import ssl import time @@ -254,7 +254,7 @@ class FileHelpers: results, ) # Set the compression mode based on the `compressed` parameter - compression_mode = ZIP_DEFLATED if compressed else None + compression_mode = ZIP_DEFLATED if compressed else ZIP_STORED with ZipFile(path_to_destination, "w", compression_mode) as zip_file: zip_file.comment = bytes( comment, "utf-8" From 97de58f31d0c9732171155056ff018c48b7d2167 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 15:12:28 -0400 Subject: [PATCH 025/155] Add action ID to tasks --- app/classes/shared/tasks.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index af3d8227..2f37e274 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -240,6 +240,7 @@ class TasksManager: "system" ), "command": schedule.command, + "action_id": schedule.action_id, } ], ) @@ -268,6 +269,7 @@ class TasksManager: "system" ), "command": schedule.command, + "action_id": schedule.action_id, } ], ) @@ -284,6 +286,7 @@ class TasksManager: "system" ), "command": schedule.command, + "action_id": schedule.action_id, } ], ) @@ -303,6 +306,7 @@ class TasksManager: "system" ), "command": schedule.command, + "action_id": schedule.action_id, } ], ) @@ -337,7 +341,7 @@ class TasksManager: job_data["cron_string"], job_data["parent"], job_data["delay"], - job_data.get("action_id", None), + job_data["action_id"], ) # Checks to make sure some doofus didn't actually make the newly @@ -368,6 +372,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -394,6 +399,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -410,6 +416,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -429,6 +436,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -521,6 +529,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -544,6 +553,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -560,6 +570,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -579,6 +590,7 @@ class TasksManager: "system" ), "command": job_data["command"], + "action_id": job_data["action_id"], } ], ) @@ -654,6 +666,7 @@ class TasksManager: "system" ), "command": schedule.command, + "action_id": schedule.action_id, } ], ) From 334d4b69c8979b34c7c80ae5ebf47e4b8dddad02 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 15:12:46 -0400 Subject: [PATCH 026/155] Allow three arguments on server actions --- app/classes/web/routes/api/api_handlers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 20586b1a..3bdf27ef 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -273,7 +273,8 @@ def api_handlers(handler_args): handler_args, ), ( - r"/api/v2/servers/([a-z0-9-]+)/action/([a-z_]+)/([a-z0-9-]+)/?", + # optional third argument when we need a action ID + r"/api/v2/servers/([a-z0-9-]+)/action/([a-z_]+)(?:/([a-z0-9-]+))?/?", ApiServersServerActionHandler, handler_args, ), From b061ebf5e523ca511cd06ec745e3b17cb95d804b Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 15:12:55 -0400 Subject: [PATCH 027/155] Use zip note --- app/classes/shared/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 0302b803..0182afc5 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -1197,6 +1197,7 @@ class ServerInstance: server_dir, excluded_dirs, self.server_id, + conf["backup_name"], conf["compress"], ) From 3cf4ebf0734b6d08ecacf6a85b4992dc1a78fe37 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 25 May 2024 16:33:28 -0400 Subject: [PATCH 028/155] Backups are editable! --- app/classes/models/management.py | 2 +- app/classes/shared/file_helpers.py | 2 + app/classes/shared/server.py | 28 +-- app/classes/web/panel_handler.py | 4 +- app/classes/web/routes/api/api_handlers.py | 2 +- .../servers/server/backups/backup/index.py | 170 ++++++++++++++++-- .../api/servers/server/backups/index.py | 18 +- .../templates/panel/server_backup_edit.html | 45 +++-- 8 files changed, 213 insertions(+), 58 deletions(-) diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 24505471..495d3e38 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -357,7 +357,7 @@ class HelpersManagement: data[str(backup.backup_id)] = { "backup_id": backup.backup_id, "backup_name": backup.backup_name, - "backup_path": backup.backup_location, + "backup_location": backup.backup_location, "excluded_dirs": backup.excluded_dirs, "max_backups": backup.max_backups, "server_id": backup.server_id_id, diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 3c14447c..a7e27555 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -235,6 +235,7 @@ class FileHelpers: path_to_zip, excluded_dirs, server_id, + backup_id, comment="", compressed=None, ): @@ -306,6 +307,7 @@ class FileHelpers: results = { "percent": percent, "total_files": self.helper.human_readable_file_size(dir_bytes), + "backup_id": backup_id, } # send status results to page. WebSocketManager().broadcast_page_params( diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 0182afc5..e265b0ca 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -1197,6 +1197,7 @@ class ServerInstance: server_dir, excluded_dirs, self.server_id, + backup_id, conf["backup_name"], conf["compress"], ) @@ -1205,7 +1206,7 @@ class ServerInstance: len(self.list_backups(backup_location)) > conf["max_backups"] and conf["max_backups"] > 0 ): - backup_list = self.list_backups() + backup_list = self.list_backups(conf["backup_location"]) oldfile = backup_list[0] oldfile_path = f"{backup_location}/{oldfile['path']}" logger.info(f"Removing old backup '{oldfile['path']}'") @@ -1213,7 +1214,12 @@ class ServerInstance: self.is_backingup = False logger.info(f"Backup of server: {self.name} completed") - results = {"percent": 100, "total_files": 0, "current_file": 0} + results = { + "percent": 100, + "total_files": 0, + "current_file": 0, + "backup_id": backup_id, + } if len(WebSocketManager().clients) > 0: WebSocketManager().broadcast_page_params( "/panel/server_detail", @@ -1251,7 +1257,12 @@ class ServerInstance: logger.exception( f"Failed to create backup of server {self.name} (ID {self.server_id})" ) - results = {"percent": 100, "total_files": 0, "current_file": 0} + results = { + "percent": 100, + "total_files": 0, + "current_file": 0, + "backup_id": backup_id, + } if len(WebSocketManager().clients) > 0: WebSocketManager().broadcast_page_params( "/panel/server_detail", @@ -1267,17 +1278,6 @@ class ServerInstance: self.run_threaded_server(HelperUsers.get_user_id_by_name("system")) self.last_backup_failed = True - def backup_status(self, source_path, dest_path): - results = Helpers.calc_percent(source_path, dest_path) - self.backup_stats = results - if len(WebSocketManager().clients) > 0: - WebSocketManager().broadcast_page_params( - "/panel/server_detail", - {"id": str(self.server_id)}, - "backup_status", - results, - ) - def last_backup_status(self): return self.last_backup_failed diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 962a5abb..4461f0d3 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -1286,7 +1286,9 @@ class PanelHandler(BaseHandler): ) self.controller.servers.refresh_server_settings(server_id) try: - page_data["backup_list"] = server.list_backups() + page_data["backup_list"] = server.list_backups( + page_data["backup_config"]["backup_location"] + ) except: page_data["backup_list"] = [] page_data["backup_path"] = Helpers.wtol_path( diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 3bdf27ef..78df3ed5 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -218,7 +218,7 @@ def api_handlers(handler_args): handler_args, ), ( - r"/api/v2/servers/([a-z0-9-]+)/backups/backup/?", + r"/api/v2/servers/([a-z0-9-]+)/backups/backup/([a-z0-9-]+)/?", ApiServersServerBackupsBackupIndexHandler, handler_args, ), diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py index cfe8f4b1..fba7bad8 100644 --- a/app/classes/web/routes/api/servers/server/backups/backup/index.py +++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py @@ -11,7 +11,7 @@ from app.classes.shared.helpers import Helpers logger = logging.getLogger(__name__) -backup_schema = { +BACKUP_SCHEMA = { "type": "object", "properties": { "filename": {"type": "string", "minLength": 5}, @@ -19,11 +19,40 @@ backup_schema = { "additionalProperties": False, "minProperties": 1, } +BACKUP_PATCH_SCHEMA = { + "type": "object", + "properties": { + "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"}, + }, + "additionalProperties": False, + "minProperties": 1, +} + +BASIC_BACKUP_PATCH_SCHEMA = { + "type": "object", + "properties": { + "max_backups": {"type": "integer"}, + "compress": {"type": "boolean"}, + "shutdown": {"type": "boolean"}, + "before": {"type": "string"}, + "after": {"type": "string"}, + "exclusions": {"type": "array"}, + }, + "additionalProperties": False, + "minProperties": 1, +} class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): - def get(self, server_id: str): + def get(self, server_id: str, backup_id: str): auth_data = self.authenticate_user() + backup_conf = self.controller.management.get_backup_config(backup_id) if not auth_data: return mask = self.controller.server_perms.get_lowest_api_perm_mask( @@ -32,15 +61,40 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): ), auth_data[5], ) + 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", + }, + ) 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"}) - self.finish_json(200, self.controller.management.get_backup_config(server_id)) + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "error_data": "Authorization Error", + }, + ) + self.finish_json(200, backup_conf) - def delete(self, server_id: str): + def delete(self, server_id: str, backup_id: str): auth_data = self.authenticate_user() - backup_conf = self.controller.management.get_backup_config(server_id) + 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( @@ -52,7 +106,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 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"}) + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "error_data": "Authorization Error", + }, + ) try: data = json.loads(self.request.body) @@ -61,7 +122,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) try: - validate(data, backup_schema) + validate(data, BACKUP_SCHEMA) except ValidationError as e: return self.finish_json( 400, @@ -74,7 +135,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): try: FileHelpers.del_file( - os.path.join(backup_conf["backup_path"], data["filename"]) + os.path.join(backup_conf["backup_location"], data["filename"]) ) except Exception as e: return self.finish_json( @@ -89,7 +150,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): return self.finish_json(200, {"status": "ok"}) - def post(self, server_id: str): + def post(self, server_id: str, backup_id: str): auth_data = self.authenticate_user() if not auth_data: return @@ -102,7 +163,24 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 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"}) + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "error_data": "Authorization Error", + }, + ) + backup_config = self.controller.management.get_backup_config(backup_id) + if backup_config["server_id"] != server_id: + return self.finish_json( + 400, + { + "status": "error", + "error": "ID_MISMATCH", + "error_data": "Server ID backup server ID different", + }, + ) try: data = json.loads(self.request.body) @@ -111,7 +189,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) try: - validate(data, backup_schema) + validate(data, BACKUP_SCHEMA) except ValidationError as e: return self.finish_json( 400, @@ -184,7 +262,6 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 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) @@ -221,3 +298,70 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): ) return self.finish_json(200, {"status": "ok"}) + + def patch(self, server_id: str, backup_id: str): + auth_data = self.authenticate_user() + if not auth_data: + return + + 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: + if auth_data[4]["superuser"]: + validate(data, BACKUP_PATCH_SCHEMA) + else: + validate(data, BASIC_BACKUP_PATCH_SCHEMA) + except ValidationError as e: + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID_JSON_SCHEMA", + "error_data": str(e), + }, + ) + backup_conf = self.controller.management.get_backup_config(backup_id) + if server_id not in [str(x["server_id"]) for x in auth_data[0]]: + # if the user doesn't have access to the server, return an error + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "error_data": "Authorization Error", + }, + ) + 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", + }, + ) + 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", + }, + ) + + self.controller.management.update_backup_config(backup_id, data) + return self.finish_json(200, {"status": "ok"}) diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py index 55744ea1..51b69473 100644 --- a/app/classes/web/routes/api/servers/server/backups/index.py +++ b/app/classes/web/routes/api/servers/server/backups/index.py @@ -10,12 +10,12 @@ logger = logging.getLogger(__name__) backup_patch_schema = { "type": "object", "properties": { - "backup_path": {"type": "string", "minLength": 1}, + "backup_location": {"type": "string", "minLength": 1}, "max_backups": {"type": "integer"}, "compress": {"type": "boolean"}, "shutdown": {"type": "boolean"}, - "backup_before": {"type": "string"}, - "backup_after": {"type": "string"}, + "before": {"type": "string"}, + "after": {"type": "string"}, "exclusions": {"type": "array"}, }, "additionalProperties": False, @@ -28,8 +28,8 @@ basic_backup_patch_schema = { "max_backups": {"type": "integer"}, "compress": {"type": "boolean"}, "shutdown": {"type": "boolean"}, - "backup_before": {"type": "string"}, - "backup_after": {"type": "string"}, + "before": {"type": "string"}, + "after": {"type": "string"}, "exclusions": {"type": "array"}, }, "additionalProperties": False, @@ -52,9 +52,11 @@ 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.finish_json(200, self.controller.management.get_backup_config(server_id)) + self.finish_json( + 200, self.controller.management.get_backups_by_server(server_id) + ) - def patch(self, backup_id: str): + def post(self, server_id: str): auth_data = self.authenticate_user() if not auth_data: return @@ -80,8 +82,6 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): "error_data": str(e), }, ) - backup_conf = self.controller.management.get_backup_config(backup_id) - server_id = backup_conf["server_id"] if server_id not in [str(x["server_id"]) for x in auth_data[0]]: # if the user doesn't have access to the server, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/frontend/templates/panel/server_backup_edit.html b/app/frontend/templates/panel/server_backup_edit.html index c68d521f..1ec54d48 100644 --- a/app/frontend/templates/panel/server_backup_edit.html +++ b/app/frontend/templates/panel/server_backup_edit.html @@ -67,7 +67,7 @@ - {% end %} @@ -107,14 +107,14 @@ {{ translate('serverBackups', 'before', data['lang']) }}
- {% else %} {{ translate('serverBackups', 'before', data['lang']) }}
- {% end %} @@ -124,14 +124,14 @@ {{ translate('serverBackups', 'after', data['lang']) }}
- {% else %} {{ translate('serverBackups', 'after', data['lang']) }}
- {% end %} @@ -206,7 +206,8 @@

- @@ -523,61 +523,8 @@ {% end %} {% block js%} + From a9c9598ad06ecb25f658fac9f2c08f4f18f66703 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 26 May 2024 23:38:59 -0400 Subject: [PATCH 051/155] Fix error on upload --- app/frontend/static/assets/js/shared/upload.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/frontend/static/assets/js/shared/upload.js b/app/frontend/static/assets/js/shared/upload.js index d6877570..0606e03e 100644 --- a/app/frontend/static/assets/js/shared/upload.js +++ b/app/frontend/static/assets/js/shared/upload.js @@ -55,8 +55,15 @@ async function uploadFile(type) { .then(data => { if (data.status === "completed") { $("#upload_input").html(`
🔒
`); - document.getElementById("lower_half").style.visibility = "visible"; - document.getElementById("lower_half").hidden = false; + if (type === "import") { + document.getElementById("lower_half").style.visibility = "visible"; + document.getElementById("lower_half").hidden = false; + } else if (type === "background") { + setTimeout(function () { + location.href = `/panel/custom_login` + }, 2000) + + } } else if (data.status !== "partial") { throw new Error(data.message); } @@ -71,7 +78,7 @@ async function uploadFile(type) { try { await Promise.all(uploadPromises); } catch (error) { - alert("Error uploading file: " + error.message); + bootbox.alert("Error uploading file: " + error.message); } } From 3b7a463184b42936d61b4ef4ce9c0fb1bff49804 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 26 May 2024 23:42:25 -0400 Subject: [PATCH 052/155] Remove stream_size_GB option --- app/classes/shared/helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 55a588fc..1dd7be80 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -508,7 +508,6 @@ class Helpers: "max_log_lines": 700, "max_audit_entries": 300, "disabled_language_files": [], - "stream_size_GB": 1, "keywords": ["help", "chunk"], "allow_nsfw_profile_pictures": False, "enable_user_self_delete": False, From 9b7ddbfe1e3c41053f000125206c6f5c97270ab9 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Mon, 27 May 2024 15:19:23 -0400 Subject: [PATCH 053/155] Check for server dir on server_upload --- .../web/routes/api/crafty/upload/index.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/classes/web/routes/api/crafty/upload/index.py b/app/classes/web/routes/api/crafty/upload/index.py index 8513f719..a289befd 100644 --- a/app/classes/web/routes/api/crafty/upload/index.py +++ b/app/classes/web/routes/api/crafty/upload/index.py @@ -36,6 +36,7 @@ class ApiFilesUploadHandler(BaseApiHandler): return self.finish_json( 400, {"status": "error", "error": "NOT_AUTHORIZED"} ) + u_type = "server_upload" elif auth_data[4]["superuser"] and upload_type == "background": u_type = "admin_config" @@ -89,11 +90,29 @@ class ApiFilesUploadHandler(BaseApiHandler): self.upload_dir = self.request.headers.get("location", None) self.temp_dir = os.path.join(self.controller.project_root, "temp", self.file_id) + if u_type == "server_upload": + full_path = os.path.join(self.upload_dir, self.filename) + + if not self.helper.is_subdir( + full_path, + Helpers.get_os_understandable_path( + self.controller.servers.get_server_data_by_id(server_id)["path"] + ), + ): + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT AUTHORIZED", + "data": {"message": "Traversal detected"}, + }, + ) + _total, _used, free = shutil.disk_usage(self.upload_dir) # Check to see if we have enough space if free <= file_size: - self.finish_json( + return self.finish_json( 507, { "status": "error", From d7bee5a7b8cb402072e04dcd03dd7db6bb3a3696 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Mon, 27 May 2024 19:12:31 -0400 Subject: [PATCH 054/155] Refactor uploads to same JS file --- .../static/assets/js/shared/upload.js | 243 +++++++++++++----- .../templates/panel/server_files.html | 147 ++--------- 2 files changed, 195 insertions(+), 195 deletions(-) diff --git a/app/frontend/static/assets/js/shared/upload.js b/app/frontend/static/assets/js/shared/upload.js index 0606e03e..7ea043af 100644 --- a/app/frontend/static/assets/js/shared/upload.js +++ b/app/frontend/static/assets/js/shared/upload.js @@ -1,90 +1,207 @@ -async function uploadFile(type) { - file = $("#file")[0].files[0] +async function uploadFile(type, file = null, path = null, file_num = 0, _onProgress) { + if (file == null) { + try { + file = $("#file")[0].files[0]; + } catch { + bootbox.alert("Please select a file first.") + return; + } + + } const fileId = uuidv4(); - const token = getCookie("_xsrf") - document.getElementById("upload_input").innerHTML = '
 
' - if (!file) { - alert("Please select a file first."); - return; + const token = getCookie("_xsrf"); + if (type !== "server_upload") { + document.getElementById("upload_input").innerHTML = '
 
'; } + let url = `` + if (type === "server_upload") { + url = `/api/v2/servers/${serverId}/files/upload/`; + } else if (type === "background") { + url = `/api/v2/crafty/admin/upload/` + } else if (type === "import") { + url = `/api/v2/servers/import/upload/` + } + console.log(url) const chunkSize = 1024 * 1024; // 1MB const totalChunks = Math.ceil(file.size / chunkSize); + const file_hash = await calculateFileHash(file); const uploadPromises = []; - let res = await fetch(`/api/v2/servers/import/upload/`, { - method: 'POST', - headers: { - 'X-XSRFToken': token, - 'chunked': true, - 'fileSize': file.size, - 'type': type, - 'total_chunks': totalChunks, - 'filename': file.name, - 'fileId': fileId, - }, - body: null, - }); - - let responseData = await res.json(); - - let file_id = "" - if (responseData.status === "ok") { - file_id = responseData.data["file-id"] - } - for (let i = 0; i < totalChunks; i++) { - const start = i * chunkSize; - const end = Math.min(start + chunkSize, file.size); - const chunk = file.slice(start, end); - - const uploadPromise = fetch(`/api/v2/servers/import/upload/`, { + let errors = []; // Array to store errors + try { + let res = await fetch(url, { method: 'POST', - body: chunk, headers: { - 'Content-Range': `bytes ${start}-${end - 1}/${file.size}`, - 'Content-Length': chunk.size, - 'fileSize': file.size, + 'X-XSRFToken': token, 'chunked': true, + 'fileHash': file_hash, + 'fileSize': file.size, 'type': type, 'total_chunks': totalChunks, 'filename': file.name, + 'location': path, 'fileId': fileId, - 'chunkId': i, }, - }).then(response => response.json()) - .then(data => { - if (data.status === "completed") { - $("#upload_input").html(`
🔒
`); - if (type === "import") { - document.getElementById("lower_half").style.visibility = "visible"; - document.getElementById("lower_half").hidden = false; - } else if (type === "background") { - setTimeout(function () { - location.href = `/panel/custom_login` - }, 2000) + body: null, + }); + if (!res.ok) { + let errorResponse = await res.json(); + throw new Error(JSON.stringify(errorResponse)); + } + + let responseData = await res.json(); + + if (responseData.status !== "ok") { + throw new Error(JSON.stringify(responseData)); + } + + for (let i = 0; i < totalChunks; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, file.size); + const chunk = file.slice(start, end); + const chunk_hash = await calculateFileHash(chunk); + + const uploadPromise = fetch(url, { + method: 'POST', + body: chunk, + headers: { + 'Content-Range': `bytes ${start}-${end - 1}/${file.size}`, + 'Content-Length': chunk.size, + 'fileSize': file.size, + 'fileHash': file_hash, + 'chunkHash': chunk_hash, + 'chunked': true, + 'type': type, + 'total_chunks': totalChunks, + 'filename': file.name, + 'location': path, + 'fileId': fileId, + 'chunkId': i, + }, + }) + .then(async response => { + if (!response.ok) { + const errorData = await response.json(); + throw new Error(JSON.stringify(errorData) || 'Unknown error occurred'); } - } else if (data.status !== "partial") { - throw new Error(data.message); - } - // Update progress bar - const progress = (i + 1) / totalChunks * 100; - updateProgressBar(Math.round(progress)); - }); + return response.json(); // Return the JSON data + }) + .then(data => { + if (data.status !== "completed" && data.status !== "partial") { + throw new Error(data.message || 'Unknown error occurred'); + } + // Update progress bar + const progress = (i + 1) / totalChunks * 100; + updateProgressBar(Math.round(progress), type, file_num); + }) + .catch(error => { + errors.push(error); // Store the error + }); - uploadPromises.push(uploadPromise); - } + uploadPromises.push(uploadPromise); + } - try { await Promise.all(uploadPromises); } catch (error) { - bootbox.alert("Error uploading file: " + error.message); + errors.push(error); // Store the error + } + + if (errors.length > 0) { + const errorMessage = errors.map(error => JSON.parse(error.message).data.message || 'Unknown error occurred').join('
'); + console.log(errorMessage) + bootbox.alert({ + title: 'Error', + message: errorMessage, + callback: function () { + window.location.reload(); + }, + }); + } else { + if (type !== "server_upload") { + // All promises resolved successfully + $("#upload_input").html(`
🔒
`); + if (type === "import") { + document.getElementById("lower_half").style.visibility = "visible"; + document.getElementById("lower_half").hidden = false; + } else if (type === "background") { + setTimeout(function () { + location.href = `/panel/custom_login`; + }, 2000); + } + } else { + let caught = false; + let expanded = false; + try { + expanded = document.getElementById(path).classList.contains("clicked"); + } catch { } + + let par_el; + let items; + try { + par_el = document.getElementById(path + "ul"); + items = par_el.children; + } catch (err) { + console.log(err); + caught = true; + par_el = document.getElementById("files-tree"); + items = par_el.children; + } + + let name = file.name; + let full_path = path + '/' + name; + let flag = false; + + for (let k = 0; k < items.length; ++k) { + if ($(items[k]).attr("data-name") == name) { + flag = true; + } + } + + if (!flag) { + if (caught && !expanded) { + $(par_el).append(`
  • ${name}
  • `); + } else if (expanded) { + $(par_el).append(`
  • ${name}
  • `); + } + setTreeViewContext(); + } + + $(`#upload-progress-bar-${file_num + 1}`).removeClass("progress-bar-striped"); + $(`#upload-progress-bar-${file_num + 1}`).addClass("bg-success"); + $(`#upload-progress-bar-${file_num + 1}`).html(''); + } } } -function updateProgressBar(progress) { - $(`#upload-progress-bar`).css('width', progress + '%'); - $(`#upload-progress-bar`).html(progress + '%'); +async function calculateFileHash(file) { + const arrayBuffer = await file.arrayBuffer(); + const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; +} + +function updateProgressBar(progress, type, i) { + if (type !== "server_upload") { + if (progress === 100) { + $(`#upload-progress-bar`).removeClass("progress-bar-striped") + + $(`#upload-progress-bar`).removeClass("progress-bar-animated") + } + $(`#upload-progress-bar`).css('width', progress + '%'); + $(`#upload-progress-bar`).html(progress + '%'); + } else { + if (progress === 100) { + $(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-striped") + + $(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-animated") + } + $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%'); + $(`#upload-progress-bar-${i + 1}`).html(progress + '%'); + } } function uuidv4() { diff --git a/app/frontend/templates/panel/server_files.html b/app/frontend/templates/panel/server_files.html index 7dae4934..ce7d2a9d 100644 --- a/app/frontend/templates/panel/server_files.html +++ b/app/frontend/templates/panel/server_files.html @@ -723,131 +723,7 @@ } } - async function uploadFile(file, path, file_num, onProgress) { - const fileId = uuidv4(); - const token = getCookie("_xsrf") - if (!file) { - alert("Please select a file first."); - return; - } - - const chunkSize = 1024 * 1024; // 1MB - const totalChunks = Math.ceil(file.size / chunkSize); - - const uploadPromises = []; - let res = await fetch(`/api/v2/servers/${serverId}/files/upload/`, { - method: 'POST', - headers: { - 'X-XSRFToken': token, - 'chunked': true, - 'type': "server_upload", - 'fileSize': file.size, - 'total_chunks': totalChunks, - 'location': path, - 'filename': file.name, - 'fileId': fileId, - }, - body: null, - }); - - let responseData = await res.json(); - - let file_id = "" - if (responseData.status === "ok") { - file_id = responseData.data["file-id"] - } - for (let i = 0; i < totalChunks; i++) { - const start = i * chunkSize; - const end = Math.min(start + chunkSize, file.size); - const chunk = file.slice(start, end); - - const uploadPromise = fetch(`/api/v2/servers/${serverId}/files/upload/`, { - method: 'POST', - body: chunk, - headers: { - 'Content-Range': `bytes ${start}-${end - 1}/${file.size}`, - 'Content-Length': chunk.size, - 'chunked': true, - 'type': "server_upload", - 'fileSize': file.size, - 'total_chunks': totalChunks, - 'filename': file.name, - 'location': path, - 'fileId': fileId, - 'chunkId': i, - }, - }).then(response => response.json()) - .then(data => { - if (data.status === "completed") { - let caught = false; - try { - if (document.getElementById(path).classList.contains("clicked")) { - var expanded = true; - } - } catch { - var expanded = false; - } - - try { - var par_el = document.getElementById(path + "ul"); - var items = par_el.children; - } catch (err) { - console.log(err) - caught = true; - var par_el = document.getElementById("files-tree"); - var items = par_el.children; - } - let name = file.name; - console.log(par_el) - let full_path = path + '/' + name - let flag = false; - for (var k = 0; k < items.length; ++k) { - if ($(items[k]).attr("data-name") == name) { - flag = true; - } - } - if (!flag) { - if (caught && expanded == false) { - $(par_el).append('
  • ' + name + '
  • '); - } else if (expanded == true) { - $(par_el).append('
  • ' + name + '
  • '); - } - setTreeViewContext(); - } - $(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-striped"); - $(`#upload-progress-bar-${i + 1}`).addClass("bg-success"); - $(`#upload-progress-bar-${i + 1}`).html('') - } else if (data.status !== "partial") { - throw new Error(data.message); - } - // Update progress bar - const progress = (i + 1) / totalChunks * 100; - updateProgressBar(Math.round(progress), file_num); - }); - - uploadPromises.push(uploadPromise); - } - - try { - await Promise.all(uploadPromises); - } catch (error) { - alert("Error uploading file: " + error.message); - } - } - function updateProgressBar(progress, i) { - $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%'); - $(`#upload-progress-bar-${i + 1}`).html(progress + '%'); - } - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0, - v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - let uploadWaitDialog; - let doUpload = true; async function uploadFilesE(event) { path = event.target.parentElement.getAttribute('data-path'); @@ -907,19 +783,17 @@ $('#upload-progress-bar-parent').append(progressHtml); - const uploadPromise = uploadFile(file, path, i, (progress) => { + const uploadPromise = uploadFile("server_upload", file, path, i, (progress) => { $(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress) $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%'); }); uploadPromises.push(uploadPromise); } - try { - await Promise.all(uploadPromises); - hideUploadBox(); - } catch (error) { - alert("Error uploading file: " + error.message); - } + + await Promise.all(uploadPromises); + hideUploadBox(); + } } } @@ -927,6 +801,15 @@ }); } + async function calculateFileHash(file) { + const arrayBuffer = await file.arrayBuffer(); + const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; + } + function getDirView(event) { let path = event.target.parentElement.getAttribute("data-path"); if (document.getElementById(path).classList.contains('clicked')) { @@ -1232,5 +1115,5 @@ - + {% end %} \ No newline at end of file From 3fd763eebd4ecd117501ad6dd4ea7afcfc7c9979 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Mon, 27 May 2024 19:12:39 -0400 Subject: [PATCH 055/155] Check hashes --- app/classes/shared/file_helpers.py | 21 ++++ .../web/routes/api/crafty/upload/index.py | 106 ++++++++++++++++-- 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 574fe8dc..2406c127 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -4,6 +4,8 @@ import logging import pathlib import tempfile import zipfile +import hashlib +import mimetypes from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED import urllib.request import ssl @@ -22,6 +24,7 @@ class FileHelpers: def __init__(self, helper): self.helper: Helpers = helper + self.mime_types = mimetypes.MimeTypes() @staticmethod def ssl_get_file( @@ -142,6 +145,24 @@ class FileHelpers: logger.error(f"Path specified is not a file or does not exist. {path}") return e + def check_mime_types(self, file_path): + m_type, _value = self.mime_types.guess_type(file_path) + return m_type + + @staticmethod + def calculate_file_hash(file_path): + sha256_hash = hashlib.sha256() + with open(file_path, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return sha256_hash.hexdigest() + + @staticmethod + def calculate_buffer_hash(buffer): + sha256_hash = hashlib.sha256() + sha256_hash.update(buffer) + return sha256_hash.hexdigest() + @staticmethod def copy_dir(src_path, dest_path, dirs_exist_ok=False): # pylint: disable=unexpected-keyword-arg diff --git a/app/classes/web/routes/api/crafty/upload/index.py b/app/classes/web/routes/api/crafty/upload/index.py index a289befd..e9b95fa4 100644 --- a/app/classes/web/routes/api/crafty/upload/index.py +++ b/app/classes/web/routes/api/crafty/upload/index.py @@ -9,6 +9,32 @@ from app.classes.shared.helpers import Helpers from app.classes.shared.main_controller import WebSocketManager, Controller from app.classes.web.base_api_handler import BaseApiHandler +IMAGE_MIME_TYPES = [ + "image/bmp", + "image/cis-cod", + "image/gif", + "image/ief", + "image/jpeg", + "image/pipeg", + "image/svg+xml", + "image/tiff", + "image/x-cmu-raster", + "image/x-cmx", + "image/x-icon", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", + "image/x-rgb", + "image/x-xbitmap", + "image/x-xpixmap", + "image/x-xwindowdump", + "image/png", + "image/webp", +] + +ARCHIVE_MIME_TYPES = ["application/zip"] + class ApiFilesUploadHandler(BaseApiHandler): async def post(self, server_id=None): @@ -17,6 +43,7 @@ class ApiFilesUploadHandler(BaseApiHandler): return upload_type = self.request.headers.get("type") + accepted_types = [] if server_id: if server_id not in [str(x["server_id"]) for x in auth_data[0]]: @@ -44,6 +71,7 @@ class ApiFilesUploadHandler(BaseApiHandler): self.controller.project_root, "app/frontend/static/assets/images/auth/custom", ) + accepted_types = IMAGE_MIME_TYPES elif upload_type == "import": if ( not self.controller.crafty_perms.can_create_server( @@ -63,6 +91,7 @@ class ApiFilesUploadHandler(BaseApiHandler): self.controller.project_root, "import", "upload" ) u_type = "server_import" + accepted_types = ARCHIVE_MIME_TYPES else: return self.finish_json( 400, @@ -73,8 +102,8 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) # Get the headers from the request - fileHash = self.request.headers.get("fileHash", 0) - chunkHash = self.request.headers.get("chunk-hash", 0) + self.file_hash = self.request.headers.get("fileHash", 0) + self.chunk_hash = self.request.headers.get("chunkHash", 0) self.file_id = self.request.headers.get("fileId") self.chunked = self.request.headers.get("chunked", True) self.filename = self.request.headers.get("filename", None) @@ -108,6 +137,20 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) + if ( + self.file_helper.check_mime_types(self.filename) not in accepted_types + and u_type != "server_upload" + ): + return self.finish_json( + 422, + { + "status": "error", + "error": "INVALID FILE TYPE", + "data": { + "message": f"Invalid File Type only accepts {accepted_types}" + }, + }, + ) _total, _used, free = shutil.disk_usage(self.upload_dir) # Check to see if we have enough space @@ -134,6 +177,24 @@ class ApiFilesUploadHandler(BaseApiHandler): if not chunk: break file.write(chunk) + if ( + self.file_helper.calculate_file_hash( + os.path.join(self.upload_dir, self.filename) + ) + != self.file_hash + ): + os.remove(os.path.join(self.upload_dir, self.filename)) + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID HASH", + "data": { + "message": "Hash recieved does not" + " match reported sent hash.", + }, + }, + ) self.finish_json( 200, { @@ -171,6 +232,20 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) + calculated_hash = self.file_helper.calculate_buffer_hash(self.request.body) + if str(self.chunk_hash) != str(calculated_hash): + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID_HASH", + "data": { + "message": "Hash recieved does not match reported sent hash.", + "chunk_id": self.chunk_index, + }, + }, + ) + # File paths file_path = os.path.join(self.upload_dir, self.filename) chunk_path = os.path.join( @@ -194,6 +269,20 @@ class ApiFilesUploadHandler(BaseApiHandler): with open(chunk_file, "rb") as infile: outfile.write(infile.read()) os.remove(chunk_file) + if self.file_helper.calculate_file_hash(file_path) != self.file_hash: + os.remove(file_path) + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID HASH", + "data": { + "message": "Hash recieved does not" + " match reported sent hash.", + "chunk_id": self.file_id, + }, + }, + ) self.finish_json( 200, @@ -203,11 +292,10 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) else: - self.write( - json.dumps( - { - "status": "partial", - "message": f"Chunk {self.chunk_index} received", - } - ) + self.finish_json( + 200, + { + "status": "partial", + "data": {"message": f"Chunk {self.chunk_index} received"}, + }, ) From 407ca4c0bbb8e5f1335f9b0a64ecf09aba001f86 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Mon, 27 May 2024 19:48:35 -0400 Subject: [PATCH 056/155] Add logging --- .../web/routes/api/crafty/upload/index.py | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/app/classes/web/routes/api/crafty/upload/index.py b/app/classes/web/routes/api/crafty/upload/index.py index e9b95fa4..a1d0790e 100644 --- a/app/classes/web/routes/api/crafty/upload/index.py +++ b/app/classes/web/routes/api/crafty/upload/index.py @@ -1,14 +1,11 @@ import os import logging -import json import shutil -from jsonschema import validate -from jsonschema.exceptions import ValidationError from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.shared.helpers import Helpers -from app.classes.shared.main_controller import WebSocketManager, Controller from app.classes.web.base_api_handler import BaseApiHandler +logger = logging.getLogger(__name__) IMAGE_MIME_TYPES = [ "image/bmp", "image/cis-cod", @@ -184,6 +181,10 @@ class ApiFilesUploadHandler(BaseApiHandler): != self.file_hash ): os.remove(os.path.join(self.upload_dir, self.filename)) + logger.error( + f"File upload failed. Filename: {self.filename}" + f"Type: {u_type} Error: INVALID HASH" + ) return self.finish_json( 400, { @@ -195,7 +196,10 @@ class ApiFilesUploadHandler(BaseApiHandler): }, }, ) - self.finish_json( + logger.info( + f"File upload completed. Filename: {self.filename}" f" Type: {u_type}" + ) + return self.finish_json( 200, { "status": "completed", @@ -210,6 +214,10 @@ class ApiFilesUploadHandler(BaseApiHandler): # Read headers and query parameters content_length = int(self.request.headers.get("Content-Length")) if content_length <= 0: + logger.error( + f"File upload failed. Filename: {self.filename}" + f"Type: {u_type} Error: INVALID CONTENT LENGTH" + ) return self.finish_json( 400, { @@ -220,6 +228,10 @@ class ApiFilesUploadHandler(BaseApiHandler): ) if not self.filename or self.chunk_index is None or total_chunks is None: + logger.error( + f"File upload failed. Filename: {self.filename}" + f"Type: {u_type} Error: CHUNK INDEX NOT FOUND" + ) return self.finish_json( 400, { @@ -234,6 +246,10 @@ class ApiFilesUploadHandler(BaseApiHandler): calculated_hash = self.file_helper.calculate_buffer_hash(self.request.body) if str(self.chunk_hash) != str(calculated_hash): + logger.error( + f"File upload failed. Filename: {self.filename}" + f"Type: {u_type} Error: INVALID HASH" + ) return self.finish_json( 400, { @@ -283,7 +299,16 @@ class ApiFilesUploadHandler(BaseApiHandler): }, }, ) - + logger.info( + f"File upload completed. Filename: {self.filename}" + f" Path: {file_path} Type: {u_type}" + ) + self.controller.management.add_to_audit_log( + auth_data[4]["user_id"], + f"Uploaded file {self.filename}", + server_id, + self.request.remote_ip, + ) self.finish_json( 200, { From 0aae82448b3de419774d87ce8d4cd73a586e3514 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 28 May 2024 15:39:44 -0400 Subject: [PATCH 057/155] Add comments --- app/classes/shared/file_helpers.py | 13 +++++++-- .../web/routes/api/crafty/upload/index.py | 28 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 2406c127..23bf01dd 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -5,6 +5,7 @@ import pathlib import tempfile import zipfile import hashlib +from typing import BinaryIO import mimetypes from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED import urllib.request @@ -150,7 +151,11 @@ class FileHelpers: return m_type @staticmethod - def calculate_file_hash(file_path): + def calculate_file_hash(file_path: str) -> str: + """ + Takes one parameter of file path. + It will generate a SHA256 hash for the path and return it. + """ sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): @@ -158,7 +163,11 @@ class FileHelpers: return sha256_hash.hexdigest() @staticmethod - def calculate_buffer_hash(buffer): + def calculate_buffer_hash(buffer: BinaryIO) -> str: + """ + Takes one argument of a stream buffer. Will return a + sha256 hash of the buffer + """ sha256_hash = hashlib.sha256() sha256_hash.update(buffer) return sha256_hash.hexdigest() diff --git a/app/classes/web/routes/api/crafty/upload/index.py b/app/classes/web/routes/api/crafty/upload/index.py index a1d0790e..036df6f8 100644 --- a/app/classes/web/routes/api/crafty/upload/index.py +++ b/app/classes/web/routes/api/crafty/upload/index.py @@ -43,6 +43,7 @@ class ApiFilesUploadHandler(BaseApiHandler): accepted_types = [] if server_id: + # Check to make sure user is authorized for the server if server_id not in [str(x["server_id"]) for x in auth_data[0]]: # if the user doesn't have access to the server, return an error return self.finish_json( @@ -54,6 +55,7 @@ class ApiFilesUploadHandler(BaseApiHandler): ), auth_data[5], ) + # Make sure user has file access for the server server_permissions = self.controller.server_perms.get_permissions(mask) if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error @@ -62,6 +64,7 @@ class ApiFilesUploadHandler(BaseApiHandler): ) u_type = "server_upload" + # Make sure user is a super user if they're changing panel settings elif auth_data[4]["superuser"] and upload_type == "background": u_type = "admin_config" self.upload_dir = os.path.join( @@ -70,6 +73,7 @@ class ApiFilesUploadHandler(BaseApiHandler): ) accepted_types = IMAGE_MIME_TYPES elif upload_type == "import": + # Check that user can make servers if ( not self.controller.crafty_perms.can_create_server( auth_data[4]["user_id"] @@ -84,6 +88,7 @@ class ApiFilesUploadHandler(BaseApiHandler): "data": {"message": ""}, }, ) + # Set directory to upload import dir self.upload_dir = os.path.join( self.controller.project_root, "import", "upload" ) @@ -117,8 +122,11 @@ class ApiFilesUploadHandler(BaseApiHandler): self.temp_dir = os.path.join(self.controller.project_root, "temp", self.file_id) if u_type == "server_upload": + # If this is an upload from a server the path will be what + # Is requested full_path = os.path.join(self.upload_dir, self.filename) + # Check to make sure the requested path is inside the server's directory if not self.helper.is_subdir( full_path, Helpers.get_os_understandable_path( @@ -133,7 +141,7 @@ class ApiFilesUploadHandler(BaseApiHandler): "data": {"message": "Traversal detected"}, }, ) - + # Check to make sure the file type we're being sent is what we're expecting if ( self.file_helper.check_mime_types(self.filename) not in accepted_types and u_type != "server_upload" @@ -166,20 +174,29 @@ class ApiFilesUploadHandler(BaseApiHandler): return self.finish_json( 200, {"status": "ok", "data": {"file-id": self.file_id}} ) + # Create the upload and temp directories if they don't exist + os.makedirs(self.upload_dir, exist_ok=True) + # Check for chunked header. We will handle this request differently + # if it doesn't exist if not self.chunked: + # Write the file directly to the upload dir with open(os.path.join(self.upload_dir, self.filename), "wb") as file: while True: chunk = self.request.body if not chunk: break file.write(chunk) + # We'll check the file hash against the sent hash once the file is + # written. We cannot check this buffer. if ( self.file_helper.calculate_file_hash( os.path.join(self.upload_dir, self.filename) ) != self.file_hash ): + # If the hash is bad we'll delete the malformed file and send + # a warning os.remove(os.path.join(self.upload_dir, self.filename)) logger.error( f"File upload failed. Filename: {self.filename}" @@ -206,9 +223,7 @@ class ApiFilesUploadHandler(BaseApiHandler): "data": {"message": "File uploaded successfully"}, }, ) - - # Create the upload and temp directories if they don't exist - os.makedirs(self.upload_dir, exist_ok=True) + # Since this is a chunked upload we'll create the temp dir for parts. os.makedirs(self.temp_dir, exist_ok=True) # Read headers and query parameters @@ -227,6 +242,8 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) + # At this point filename, chunk index and total chunks are required + # in the request if not self.filename or self.chunk_index is None or total_chunks is None: logger.error( f"File upload failed. Filename: {self.filename}" @@ -244,6 +261,7 @@ class ApiFilesUploadHandler(BaseApiHandler): }, ) + # Calculate the hash of the buffer and compare it against the expected hash calculated_hash = self.file_helper.calculate_buffer_hash(self.request.body) if str(self.chunk_hash) != str(calculated_hash): logger.error( @@ -278,6 +296,8 @@ class ApiFilesUploadHandler(BaseApiHandler): for f in os.listdir(self.temp_dir) if f.startswith(f"{self.filename}.part") ] + # When we've reached the total chunks we'll + # Compare the hash and write the file if len(received_chunks) == total_chunks: with open(file_path, "wb") as outfile: for i in range(total_chunks): From d8ad8f5e09a999e07e90aedc5364821fb70c6da8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 28 May 2024 17:14:05 -0400 Subject: [PATCH 058/155] Fix role selection on user creation. Security improvements --- app/classes/web/routes/api/roles/index.py | 17 ++++++++++---- .../web/routes/api/roles/role/index.py | 22 ++++++++++++++----- app/classes/web/routes/api/users/index.py | 6 ++++- .../web/routes/api/users/user/index.py | 22 ++++++++++++++++--- .../templates/panel/panel_edit_role.html | 7 ++++-- .../templates/panel/panel_edit_user.html | 4 ++-- 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py index a8612c75..e7cda520 100644 --- a/app/classes/web/routes/api/roles/index.py +++ b/app/classes/web/routes/api/roles/index.py @@ -2,6 +2,7 @@ import typing as t from jsonschema import ValidationError, validate import orjson from playhouse.shortcuts import model_to_dict +from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.web.base_api_handler import BaseApiHandler create_role_schema = { @@ -71,7 +72,7 @@ class ApiRolesIndexHandler(BaseApiHandler): return ( _, - _, + exec_user_permissions_crafty, _, superuser, _, @@ -81,7 +82,10 @@ class ApiRolesIndexHandler(BaseApiHandler): # GET /api/v2/roles?ids=true get_only_ids = self.get_query_argument("ids", None) == "true" - if not superuser: + if ( + not superuser + and not EnumPermissionsCrafty.ROLES_CONFIG in exec_user_permissions_crafty + ): return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) self.finish_json( @@ -104,14 +108,17 @@ class ApiRolesIndexHandler(BaseApiHandler): return ( _, - _, + exec_user_permissions_crafty, _, superuser, user, _, ) = auth_data - if not superuser: + if ( + not superuser + and not EnumPermissionsCrafty.ROLES_CONFIG in exec_user_permissions_crafty + ): return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -138,6 +145,8 @@ class ApiRolesIndexHandler(BaseApiHandler): role_name = data["name"] manager = data.get("manager", None) + if not superuser and not manager: + manager = auth_data[4]["user_id"] if manager == self.controller.users.get_id_by_name("SYSTEM") or manager == 0: manager = None diff --git a/app/classes/web/routes/api/roles/role/index.py b/app/classes/web/routes/api/roles/role/index.py index 73fd9ff3..a4f49911 100644 --- a/app/classes/web/routes/api/roles/role/index.py +++ b/app/classes/web/routes/api/roles/role/index.py @@ -1,6 +1,7 @@ from jsonschema import ValidationError, validate import orjson -from peewee import DoesNotExist +from peewee import DoesNotExist, IntegrityError +from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.web.base_api_handler import BaseApiHandler modify_role_schema = { @@ -70,14 +71,17 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): return ( _, - _, + exec_user_permissions_crafty, _, superuser, _, _, ) = auth_data - if not superuser: + if ( + not superuser + and not EnumPermissionsCrafty.ROLES_CONFIG in exec_user_permissions_crafty + ): return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -100,8 +104,11 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): user, _, ) = auth_data - - if not superuser: + role = self.controller.roles.get_role(role_id) + if ( + str(role.get("manager", "no manager found")) != str(auth_data[4]["user_id"]) + and not superuser + ): return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) self.controller.roles.remove_role(role_id) @@ -179,7 +186,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): ) except DoesNotExist: return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"}) - + except IntegrityError: + return self.finish_json( + 404, {"status": "error", "error": "ROLE_NAME_EXISTS"} + ) self.controller.management.add_to_audit_log( user["user_id"], f"modified role with ID {role_id}", diff --git a/app/classes/web/routes/api/users/index.py b/app/classes/web/routes/api/users/index.py index dbdb1ac0..07c85f3a 100644 --- a/app/classes/web/routes/api/users/index.py +++ b/app/classes/web/routes/api/users/index.py @@ -153,7 +153,11 @@ class ApiUsersIndexHandler(BaseApiHandler): for role in roles: role = self.controller.roles.get_role(role) - if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser: + if ( + str(role.get("manager", "no manager found")) + != str(auth_data[4]["user_id"]) + and not superuser + ): return self.finish_json( 400, {"status": "error", "error": "INVALID_ROLES_CREATE"} ) diff --git a/app/classes/web/routes/api/users/user/index.py b/app/classes/web/routes/api/users/user/index.py index 9fa46200..b05e4ac3 100644 --- a/app/classes/web/routes/api/users/user/index.py +++ b/app/classes/web/routes/api/users/user/index.py @@ -132,7 +132,6 @@ class ApiUsersUserIndexHandler(BaseApiHandler): return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) - try: validate(data, user_patch_schema) except ValidationError as e: @@ -144,10 +143,8 @@ class ApiUsersUserIndexHandler(BaseApiHandler): "error_data": str(e), }, ) - if user_id == "@me": user_id = user["user_id"] - if ( EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions and str(user["user_id"]) != str(user_id) @@ -215,6 +212,25 @@ class ApiUsersUserIndexHandler(BaseApiHandler): return self.finish_json( 400, {"status": "error", "error": "INVALID_ROLES_MODIFY"} ) + user_modify = self.controller.users.get_user_roles_id(user_id) + + for role in data["roles"]: + # Check if user is not a super user and that the exec user is the role + # manager or that the role already exists in the user's list + if not superuser and ( + str( + self.controller.roles.get_role(role).get( + "manager", "no manager found" + ) + ) + != str(auth_data[4]["user_id"]) + and role not in user_modify + ): + for item in user_modify: + print(type(role), type(item)) + return self.finish_json( + 400, {"status": "error", "error": "INVALID_ROLES_MODIFY"} + ) user_obj = HelperUsers.get_user_model(user_id) if "password" in data and str(user["user_id"]) != str(user_id): diff --git a/app/frontend/templates/panel/panel_edit_role.html b/app/frontend/templates/panel/panel_edit_role.html index df065bf9..b72d3a2a 100644 --- a/app/frontend/templates/panel/panel_edit_role.html +++ b/app/frontend/templates/panel/panel_edit_role.html @@ -428,10 +428,13 @@ if (responseData.status === "ok") { window.location.href = "/panel/panel_config"; } else { - + let errordata = responseData.error; + if (responseData.error_data){ + errordata = responseData.error + } bootbox.alert({ title: responseData.error, - message: responseData.error_data + message: errordata }); } }); diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html index 87631219..9821ec26 100644 --- a/app/frontend/templates/panel/panel_edit_user.html +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -393,6 +393,7 @@ data['lang']) }}{% end %} } function replacer(key, value) { if (typeof value == "boolean" || key === "email" || key === "permissions" || key === "roles") { + console.log(key) return value } else { console.log(key, value) @@ -433,6 +434,7 @@ data['lang']) }}{% end %} let disabled_flag = false; let roles = null; if (superuser || userId != edit_id){ + console.log("ROLES") roles = $('.role_check').map(function() { if ($(this).attr("disabled")){ disabled_flag = true; @@ -457,9 +459,7 @@ data['lang']) }}{% end %} delete formDataObject.username } if (superuser || userId != edit_id){ - if (!disabled_flag){ formDataObject.roles = roles; - } if ($("#permissions").length){ formDataObject.permissions = permissions; } From a9856a8a2c1559544fbf4933a7f5899b849e0dc0 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 28 May 2024 19:30:12 -0400 Subject: [PATCH 059/155] Fix java server zip creation --- .../static/assets/js/shared/root-dir.js | 16 ++++++++--- app/frontend/templates/server/wizard.html | 28 +++++++++++-------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/frontend/static/assets/js/shared/root-dir.js b/app/frontend/static/assets/js/shared/root-dir.js index 6882b577..b32fd2df 100644 --- a/app/frontend/static/assets/js/shared/root-dir.js +++ b/app/frontend/static/assets/js/shared/root-dir.js @@ -41,7 +41,7 @@ async function getTreeView(path, unzip = false, upload = false) { let responseData = await res.json(); if (responseData.status === "ok") { console.log(responseData); - process_tree_response(responseData); + process_tree_response(responseData, unzip); let x = document.querySelector('.bootbox'); if (x) { x.remove() @@ -61,7 +61,7 @@ async function getTreeView(path, unzip = false, upload = false) { } } -function process_tree_response(response) { +function process_tree_response(response, unzip) { const styles = window.getComputedStyle(document.getElementById("lower_half")); //If this value is still hidden we know the user is executing a zip import and not an upload if (styles.visibility === "hidden") { @@ -70,7 +70,9 @@ function process_tree_response(response) { document.getElementById('upload_submit').disabled = false; } let path = response.data.root_path.path; - $(".root-input").val(response.data.root_path.path); + if (unzip) { + $(".root-input").val(response.data.root_path.path); + } let text = `