diff --git a/CHANGELOG.md b/CHANGELOG.md index 98e55169..e8ae3dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # Changelog ## --- [4.0.11] - 2022/TBD ### New features -TBD +- Add server import status indicators ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/433)) +- Users can now be assigned as manager of other users/roles ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434)) +- Add variable shutdown timeouts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/435)) ### Bug fixes -TBD +- Fix creation quota not refilling after server delete ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434)) ### Tweaks -TBD +- Make imports threaded ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/433)) +- Add 'Created By' Field to servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/434)) ### Lang TBD

diff --git a/app/classes/controllers/crafty_perms_controller.py b/app/classes/controllers/crafty_perms_controller.py index 9c79c33a..111e3971 100644 --- a/app/classes/controllers/crafty_perms_controller.py +++ b/app/classes/controllers/crafty_perms_controller.py @@ -60,26 +60,6 @@ class CraftyPermsController: permissions_list = PermissionsCrafty.get_permissions(permissions_mask) return permissions_list - @staticmethod - def add_server_creation(user_id): - """Increase the "Server Creation" counter for this user - - Args: - user_id (int): The modifiable user's ID - - Returns: - int: The new count of servers created by this user - """ - return PermissionsCrafty.add_server_creation(user_id) - - @staticmethod - def add_user_creation(user_id): - return PermissionsCrafty.add_user_creation(user_id) - - @staticmethod - def add_role_creation(user_id): - return PermissionsCrafty.add_role_creation(user_id) - @staticmethod def get_api_key_permissions_list(key: ApiKeys): return PermissionsCrafty.get_api_key_permissions_list(key) diff --git a/app/classes/controllers/roles_controller.py b/app/classes/controllers/roles_controller.py index 5e7925a3..ab5dcd5a 100644 --- a/app/classes/controllers/roles_controller.py +++ b/app/classes/controllers/roles_controller.py @@ -64,8 +64,8 @@ class RolesController: HelperRoles.update_role(role_id, up_data) @staticmethod - def add_role(role_name): - return HelperRoles.add_role(role_name) + def add_role(role_name, manager): + return HelperRoles.add_role(role_name, manager) class RoleServerJsonType(t.TypedDict): server_id: t.Union[str, int] @@ -92,6 +92,7 @@ class RolesController: def add_role_advanced( name: str, servers: t.Iterable[RoleServerJsonType], + manager: int, ) -> int: """Add a role with a name and a list of servers @@ -102,7 +103,7 @@ class RolesController: Returns: int: The new role's ID """ - role_id: t.Final[int] = HelperRoles.add_role(name) + role_id: t.Final[int] = HelperRoles.add_role(name, manager) for server in servers: PermissionsServers.get_or_create( role_id, server["server_id"], server["permissions"] @@ -114,6 +115,7 @@ class RolesController: role_id: t.Union[str, int], role_name: t.Optional[str], servers: t.Optional[t.Iterable[RoleServerJsonType]], + manager: int, ) -> None: """Update a role with a name and a list of servers @@ -152,6 +154,7 @@ class RolesController: up_data = { "role_name": role_name, "last_update": Helpers.get_time_as_string(), + "manager": manager, } # TODO: do the last_update on the db side HelperRoles.update_role(role_id, up_data) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 593e24d4..a0948769 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -52,6 +52,7 @@ class ServersController(metaclass=Singleton): server_log_file: str, server_stop: str, server_type: str, + created_by: int, server_port: int = 25565, server_host: str = "127.0.0.1", ) -> int: @@ -86,6 +87,7 @@ class ServersController(metaclass=Singleton): server_log_file, server_stop, server_type, + created_by, server_port, server_host, ) @@ -116,19 +118,19 @@ class ServersController(metaclass=Singleton): return ret @staticmethod - def set_download(server_id): + def set_import(server_id): srv = ServersController().get_server_instance_by_id(server_id) - return srv.stats_helper.set_download() + return srv.stats_helper.set_import() @staticmethod - def finish_download(server_id): + def finish_import(server_id): srv = ServersController().get_server_instance_by_id(server_id) - return srv.stats_helper.finish_download() + return srv.stats_helper.finish_import() @staticmethod - def get_download_status(server_id): + def get_import_status(server_id): server = ServersController().get_server_instance_by_id(server_id) - return server.stats_helper.get_download_status() + return server.stats_helper.get_import_status() def remove_server(self, server_id): roles_list = PermissionsServers.get_roles_from_server(server_id) diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py index c3c90b2f..13b8fb4f 100644 --- a/app/classes/controllers/users_controller.py +++ b/app/classes/controllers/users_controller.py @@ -1,7 +1,9 @@ import logging import typing as t +from app.classes.models.servers import HelperServers from app.classes.models.users import HelperUsers +from app.classes.models.roles import HelperRoles from app.classes.models.crafty_permissions import ( PermissionsCrafty, EnumPermissionsCrafty, @@ -132,6 +134,18 @@ class UsersController: def set_support_path(user_id, support_path): HelperUsers.set_support_path(user_id, support_path) + @staticmethod + def get_managed_users(exec_user_id): + return HelperUsers.get_managed_users(exec_user_id) + + @staticmethod + def get_managed_roles(exec_user_id): + return HelperUsers.get_managed_roles(exec_user_id) + + @staticmethod + def get_created_servers(exec_user_id): + return HelperServers.get_total_owned_servers(exec_user_id) + def update_user(self, user_id: str, user_data=None, user_crafty_data=None): if user_crafty_data is None: user_crafty_data = {} @@ -206,6 +220,7 @@ class UsersController: def add_user( self, username, + manager, password, email="default@example.com", enabled: bool = True, @@ -213,6 +228,7 @@ class UsersController: ): return self.users_helper.add_user( username, + manager, password=password, email=email, enabled=enabled, @@ -236,6 +252,10 @@ class UsersController: ) def remove_user(self, user_id): + for user in self.get_managed_users(user_id): + self.update_user(user.user_id, {"manager": None}) + for role in HelperUsers.get_managed_roles(user_id): + HelperRoles.update_role(role.role_id, {"manager": None}) return self.users_helper.remove_user(user_id) @staticmethod diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index a5eb11ba..1ecfc0f1 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -175,7 +175,7 @@ class ServerJars: # we submit a db update for it's stats. while True: try: - ServersController.set_download(server_id) + ServersController.set_import(server_id) for user in server_users: self.helper.websocket_helper.broadcast_user( user, "send_start_reload", {} @@ -190,7 +190,7 @@ class ServerJars: try: with open(path, "wb") as output: shutil.copyfileobj(r.raw, output) - ServersController.finish_download(server_id) + ServersController.finish_import(server_id) for user in server_users: self.helper.websocket_helper.broadcast_user( @@ -203,7 +203,7 @@ class ServerJars: return True except Exception as e: logger.error(f"Unable to save jar to {path} due to error:{e}") - ServersController.finish_download(server_id) + ServersController.finish_import(server_id) server_users = PermissionsServers.get_server_user_list(server_id) for user in server_users: self.helper.websocket_helper.broadcast_user( diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py index 9b99bfb0..22383408 100644 --- a/app/classes/models/crafty_permissions.py +++ b/app/classes/models/crafty_permissions.py @@ -9,6 +9,7 @@ from peewee import ( ) from app.classes.models.base_model import BaseModel +from app.classes.models.servers import HelperServers from app.classes.models.users import Users, ApiKeys, HelperUsers from app.classes.shared.permission_helper import PermissionHelper @@ -23,9 +24,6 @@ class UserCrafty(BaseModel): limit_server_creation = IntegerField(default=-1) limit_user_creation = IntegerField(default=0) limit_role_creation = IntegerField(default=0) - created_server = IntegerField(default=0) - created_user = IntegerField(default=0) - created_role = IntegerField(default=0) class Meta: table_name = "user_crafty" @@ -107,9 +105,6 @@ class PermissionsCrafty: UserCrafty.limit_server_creation: 0, UserCrafty.limit_user_creation: 0, UserCrafty.limit_role_creation: 0, - UserCrafty.created_server: 0, - UserCrafty.created_user: 0, - UserCrafty.created_role: 0, } ).execute() user_crafty = PermissionsCrafty.get_user_crafty(user_id) @@ -159,11 +154,16 @@ class PermissionsCrafty: @staticmethod def get_created_quantity_list(user_id): - user_crafty = PermissionsCrafty.get_user_crafty(user_id) quantity_list = { - EnumPermissionsCrafty.SERVER_CREATION.name: user_crafty.created_server, - EnumPermissionsCrafty.USER_CONFIG.name: user_crafty.created_user, - EnumPermissionsCrafty.ROLES_CONFIG.name: user_crafty.created_role, + EnumPermissionsCrafty.SERVER_CREATION.name: HelperServers.get_total_owned_servers( # pylint: disable=line-too-long + user_id + ), + EnumPermissionsCrafty.USER_CONFIG.name: HelperUsers.get_managed_users( + user_id + ).count(), + EnumPermissionsCrafty.ROLES_CONFIG.name: HelperUsers.get_managed_roles( + user_id + ).count(), } return quantity_list @@ -183,31 +183,6 @@ class PermissionsCrafty: or limit_list[permission.name] == -1 ) - @staticmethod - def add_server_creation(user_id: int): - """Increase the "Server Creation" counter for this user - - Args: - user_id (int): The modifiable user's ID - """ - UserCrafty.update(created_server=UserCrafty.created_server + 1).where( - UserCrafty.user_id == user_id - ).execute() - - @staticmethod - def add_user_creation(user_id): - user_crafty = PermissionsCrafty.get_user_crafty(user_id) - user_crafty.created_user += 1 - UserCrafty.save(user_crafty) - return user_crafty.created_user - - @staticmethod - def add_role_creation(user_id): - user_crafty = PermissionsCrafty.get_user_crafty(user_id) - user_crafty.created_role += 1 - UserCrafty.save(user_crafty) - return user_crafty.created_role - @staticmethod def get_api_key_permissions_list(key: ApiKeys): user = HelperUsers.get_user(key.user_id) diff --git a/app/classes/models/roles.py b/app/classes/models/roles.py index 4d61e051..541f67e8 100644 --- a/app/classes/models/roles.py +++ b/app/classes/models/roles.py @@ -6,6 +6,7 @@ from peewee import ( DoesNotExist, AutoField, DateTimeField, + IntegerField, ) from playhouse.shortcuts import model_to_dict @@ -22,6 +23,7 @@ class Roles(BaseModel): created = DateTimeField(default=datetime.datetime.now) last_update = DateTimeField(default=datetime.datetime.now) role_name = CharField(default="", unique=True, index=True) + manager = IntegerField(null=True) class Meta: table_name = "roles" @@ -71,11 +73,12 @@ class HelperRoles: ) @staticmethod - def add_role(role_name): + def add_role(role_name, manager): role_id = Roles.insert( { Roles.role_name: role_name.lower(), Roles.created: Helpers.get_time_as_string(), + Roles.manager: manager, } ).execute() return role_id diff --git a/app/classes/models/server_stats.py b/app/classes/models/server_stats.py index 4c7091aa..6e589ffc 100644 --- a/app/classes/models/server_stats.py +++ b/app/classes/models/server_stats.py @@ -53,7 +53,7 @@ class ServerStats(Model): waiting_start = BooleanField(default=False) first_run = BooleanField(default=True) crashed = BooleanField(default=False) - downloading = BooleanField(default=False) + importing = BooleanField(default=False) class Meta: table_name = "server_stats" @@ -215,26 +215,26 @@ class HelperServerStats: ServerStats.server_id == self.server_id ).execute(self.database) - def set_download(self): + def set_import(self): # self.select_database(self.server_id) - ServerStats.update(downloading=True).where( + ServerStats.update(importing=True).where( ServerStats.server_id == self.server_id ).execute(self.database) - def finish_download(self): + def finish_import(self): # self.select_database(self.server_id) - ServerStats.update(downloading=False).where( + ServerStats.update(importing=False).where( ServerStats.server_id == self.server_id ).execute(self.database) - def get_download_status(self): + def get_import_status(self): # self.select_database(self.server_id) - download_status = ( + import_status = ( ServerStats.select() .where(ServerStats.server_id == self.server_id) .get(self.database) ) - return download_status.downloading + return import_status.importing def server_crash_reset(self): if self.server_id is None: @@ -257,7 +257,6 @@ class HelperServerStats: def set_update(self, value): if self.server_id is None: return - # self.select_database(self.server_id) try: # Checks if server even exists diff --git a/app/classes/models/servers.py b/app/classes/models/servers.py index 71ca4851..69d05866 100644 --- a/app/classes/models/servers.py +++ b/app/classes/models/servers.py @@ -38,6 +38,8 @@ class Servers(BaseModel): logs_delete_after = IntegerField(default=0) type = CharField(default="minecraft-java") show_status = BooleanField(default=1) + created_by = IntegerField(default=-100) + shutdown_timeout = IntegerField(default=60) class Meta: table_name = "servers" @@ -64,6 +66,7 @@ class HelperServers: server_log_file: str, server_stop: str, server_type: str, + created_by: int, server_port: int = 25565, server_host: str = "127.0.0.1", ) -> int: @@ -105,6 +108,7 @@ class HelperServers: Servers.stop_command: server_stop, Servers.backup_path: backup_path, Servers.type: server_type, + Servers.created_by: created_by, } ).execute() @@ -112,6 +116,10 @@ class HelperServers: def get_server_obj(server_id): return Servers.get_by_id(server_id) + @staticmethod + def get_total_owned_servers(user_id): + return Servers.select().where(Servers.created_by == user_id).count() + @staticmethod def get_server_type_by_id(server_id): server_type = Servers.select().where(Servers.server_id == server_id).get() diff --git a/app/classes/models/users.py b/app/classes/models/users.py index ac204e3c..0d8e596b 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -6,6 +6,7 @@ from peewee import ( ForeignKeyField, CharField, AutoField, + IntegerField, DateTimeField, BooleanField, CompositeKey, @@ -40,6 +41,7 @@ class Users(BaseModel): server_order = CharField(default="") preparing = BooleanField(default=False) hints = BooleanField(default=True) + manager = IntegerField(default=None, null=True) class Meta: table_name = "users" @@ -138,6 +140,16 @@ class HelperUsers: user_query = Users.select().where(Users.user_id == user_id) return user_query + @staticmethod + def get_managed_users(exec_user_id): + user_query = Users.select().where(Users.manager == exec_user_id) + return user_query + + @staticmethod + def get_managed_roles(exec_user_id): + roles_query = Roles.select().where(Roles.manager == exec_user_id) + return roles_query + @staticmethod def get_user(user_id): if user_id == 0: @@ -192,6 +204,7 @@ class HelperUsers: def add_user( self, username: str, + manager: str, password: str = None, email: t.Optional[str] = None, enabled: bool = True, @@ -209,6 +222,7 @@ class HelperUsers: Users.enabled: enabled, Users.superuser: superuser, Users.created: Helpers.get_time_as_string(), + Users.manager: manager, } ).execute() return user_id @@ -229,6 +243,7 @@ class HelperUsers: Users.enabled: enabled, Users.superuser: superuser, Users.created: Helpers.get_time_as_string(), + Users.manager: None, } ).execute() return user_id diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 5cd38bbf..f9cde55a 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -27,10 +27,15 @@ class FileHelpers: FileHelpers.del_dirs(sub) else: # Delete file if it is a file: - sub.unlink() - - # This removes the top-level folder: - path.rmdir() + try: + sub.unlink() + except: + logger.error(f"Unable to delete file {sub}") + try: + # This removes the top-level folder: + path.rmdir() + except: + logger.error("Unable to remove top level") return True @staticmethod diff --git a/app/classes/shared/import_helper.py b/app/classes/shared/import_helper.py new file mode 100644 index 00000000..769ebc3a --- /dev/null +++ b/app/classes/shared/import_helper.py @@ -0,0 +1,216 @@ +import os +import time +import shutil +import logging +import threading + +from app.classes.controllers.server_perms_controller import PermissionsServers +from app.classes.controllers.servers_controller import ServersController +from app.classes.shared.helpers import Helpers +from app.classes.shared.file_helpers import FileHelpers + +logger = logging.getLogger(__name__) + + +class ImportHelpers: + allowed_quotes = ['"', "'", "`"] + + def __init__(self, helper, file_helper): + self.file_helper: FileHelpers = file_helper + self.helper: Helpers = helper + + def import_jar_server(self, server_path, new_server_dir, port, new_id): + import_thread = threading.Thread( + target=self.import_threaded_jar_server, + daemon=True, + args=(server_path, new_server_dir, port, new_id), + name=f"{new_id}_import", + ) + import_thread.start() + + def import_threaded_jar_server(self, server_path, new_server_dir, port, new_id): + for item in os.listdir(server_path): + if not item == "db_stats": + try: + if os.path.isdir(os.path.join(server_path, item)): + FileHelpers.copy_dir( + os.path.join(server_path, item), + os.path.join(new_server_dir, item), + ) + else: + FileHelpers.copy_file( + os.path.join(server_path, item), + os.path.join(new_server_dir, item), + ) + except shutil.Error as ex: + logger.error(f"Server import failed with error: {ex}") + + has_properties = False + for item in os.listdir(new_server_dir): + if str(item) == "server.properties": + has_properties = True + if not has_properties: + logger.info( + f"No server.properties found on zip file import. " + f"Creating one with port selection of {str(port)}" + ) + with open( + os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" + ) as file: + file.write(f"server-port={port}") + file.close() + time.sleep(5) + ServersController.finish_import(new_id) + server_users = PermissionsServers.get_server_user_list(new_id) + for user in server_users: + self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + + def import_java_zip_server(self, temp_dir, new_server_dir, port, new_id): + import_thread = threading.Thread( + target=self.import_threaded_java_zip_server, + daemon=True, + args=(temp_dir, new_server_dir, port, new_id), + name=f"{new_id}_java_zip_import", + ) + import_thread.start() + + def import_threaded_java_zip_server(self, temp_dir, new_server_dir, port, new_id): + has_properties = False + # extracts archive to temp directory + for item in os.listdir(temp_dir): + if str(item) == "server.properties": + has_properties = True + try: + if not os.path.isdir(os.path.join(temp_dir, item)): + FileHelpers.move_file( + os.path.join(temp_dir, item), os.path.join(new_server_dir, item) + ) + else: + if item != "db_stats": + FileHelpers.move_dir( + os.path.join(temp_dir, item), + os.path.join(new_server_dir, item), + ) + except Exception as ex: + logger.error(f"ERROR IN ZIP IMPORT: {ex}") + if not has_properties: + logger.info( + f"No server.properties found on zip file import. " + f"Creating one with port selection of {str(port)}" + ) + with open( + os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" + ) as file: + file.write(f"server-port={port}") + file.close() + + server_users = PermissionsServers.get_server_user_list(new_id) + ServersController.finish_import(new_id) + for user in server_users: + self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + # deletes temp dir + FileHelpers.del_dirs(temp_dir) + + def import_bedrock_server( + self, server_path, new_server_dir, port, full_jar_path, new_id + ): + import_thread = threading.Thread( + target=self.import_threaded_bedrock_server, + daemon=True, + args=(server_path, new_server_dir, port, full_jar_path, new_id), + name=f"{new_id}_bedrock_import", + ) + import_thread.start() + + def import_threaded_bedrock_server( + self, server_path, new_server_dir, port, full_jar_path, new_id + ): + for item in os.listdir(server_path): + if not item == "db_stats": + try: + if os.path.isdir(os.path.join(server_path, item)): + FileHelpers.copy_dir( + os.path.join(server_path, item), + os.path.join(new_server_dir, item), + ) + else: + FileHelpers.copy_file( + os.path.join(server_path, item), + os.path.join(new_server_dir, item), + ) + except shutil.Error as ex: + logger.error(f"Server import failed with error: {ex}") + + has_properties = False + for item in os.listdir(new_server_dir): + if str(item) == "server.properties": + has_properties = True + if not has_properties: + logger.info( + f"No server.properties found on zip file import. " + f"Creating one with port selection of {str(port)}" + ) + with open( + os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" + ) as file: + file.write(f"server-port={port}") + file.close() + if os.name != "nt": + if Helpers.check_file_exists(full_jar_path): + os.chmod(full_jar_path, 0o2760) + ServersController.finish_import(new_id) + server_users = PermissionsServers.get_server_user_list(new_id) + for user in server_users: + self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + + def import_bedrock_zip_server( + self, temp_dir, new_server_dir, full_jar_path, port, new_id + ): + import_thread = threading.Thread( + target=self.import_threaded_bedrock_zip_server, + daemon=True, + args=(temp_dir, new_server_dir, full_jar_path, port, new_id), + name=f"{new_id}_bedrock_import", + ) + import_thread.start() + + def import_threaded_bedrock_zip_server( + self, temp_dir, new_server_dir, full_jar_path, port, new_id + ): + has_properties = False + # extracts archive to temp directory + for item in os.listdir(temp_dir): + if str(item) == "server.properties": + has_properties = True + try: + if not os.path.isdir(os.path.join(temp_dir, item)): + FileHelpers.move_file( + os.path.join(temp_dir, item), os.path.join(new_server_dir, item) + ) + else: + if item != "db_stats": + FileHelpers.move_dir( + os.path.join(temp_dir, item), + os.path.join(new_server_dir, item), + ) + except Exception as ex: + logger.error(f"ERROR IN ZIP IMPORT: {ex}") + if not has_properties: + logger.info( + f"No server.properties found on zip file import. " + f"Creating one with port selection of {str(port)}" + ) + with open( + os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" + ) as file: + file.write(f"server-port={port}") + file.close() + ServersController.finish_import(new_id) + server_users = PermissionsServers.get_server_user_list(new_id) + for user in server_users: + self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + if os.name != "nt": + if Helpers.check_file_exists(full_jar_path): + os.chmod(full_jar_path, 0o2760) + # deletes temp dir + FileHelpers.del_dirs(temp_dir) diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index bca24fe8..d52ef5e5 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -28,15 +28,17 @@ from app.classes.shared.authentication import Authentication from app.classes.shared.console import Console from app.classes.shared.helpers import Helpers from app.classes.shared.file_helpers import FileHelpers +from app.classes.shared.import_helper import ImportHelpers from app.classes.minecraft.serverjars import ServerJars logger = logging.getLogger(__name__) class Controller: - def __init__(self, database, helper, file_helper): + def __init__(self, database, helper, file_helper, import_helper): self.helper: Helpers = helper self.file_helper: FileHelpers = file_helper + self.import_helper: ImportHelpers = import_helper self.server_jars: ServerJars = ServerJars(helper) self.users_helper: HelperUsers = HelperUsers(database, self.helper) self.roles_helper: HelperRoles = HelperRoles(database) @@ -244,7 +246,7 @@ class Controller: except: return {"percent": 0, "total_files": 0} - def create_api_server(self, data: dict): + def create_api_server(self, data: dict, user_id): server_fs_uuid = Helpers.create_uuid() new_server_path = os.path.join(self.helper.servers_dir, server_fs_uuid) backup_path = os.path.join(self.helper.backup_path, server_fs_uuid) @@ -307,7 +309,9 @@ class Controller: # TODO: Copy files from the zip file to the new server directory server_file = create_data["jarfile"] raise Exception("Not yet implemented") - _create_server_properties_if_needed(create_data["server_properties_port"]) + _create_server_properties_if_needed( + create_data["server_properties_port"], + ) min_mem = create_data["mem_min"] max_mem = create_data["mem_max"] @@ -403,6 +407,7 @@ class Controller: server_log_file=log_location, server_stop=stop_command, server_port=monitoring_port, + created_by=user_id, server_host=monitoring_host, server_type=monitoring_type, ) @@ -429,6 +434,7 @@ class Controller: min_mem: int, max_mem: int, port: int, + user_id: int, ): server_id = Helpers.create_uuid() server_dir = os.path.join(self.helper.servers_dir, server_id) @@ -489,6 +495,7 @@ class Controller: server_log_file, server_stop, port, + user_id, server_type="minecraft-java", ) @@ -524,6 +531,7 @@ class Controller: min_mem: int, max_mem: int, port: int, + user_id: int, ): server_id = Helpers.create_uuid() new_server_dir = os.path.join(self.helper.servers_dir, server_id) @@ -537,25 +545,6 @@ class Controller: Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(backup_path) server_path = Helpers.get_os_understandable_path(server_path) - try: - FileHelpers.copy_dir(server_path, new_server_dir, True) - except shutil.Error as ex: - logger.error(f"Server import failed with error: {ex}") - - has_properties = False - for item in os.listdir(new_server_dir): - if str(item) == "server.properties": - has_properties = True - if not has_properties: - logger.info( - f"No server.properties found on zip file import. " - f"Creating one with port selection of {str(port)}" - ) - with open( - os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" - ) as file: - file.write(f"server-port={port}") - file.close() full_jar_path = os.path.join(new_server_dir, server_jar) @@ -584,8 +573,11 @@ class Controller: server_log_file, server_stop, port, + user_id, server_type="minecraft-java", ) + ServersController.set_import(new_id) + self.import_helper.import_jar_server(server_path, new_server_dir, port, new_id) return new_id def import_zip_server( @@ -596,6 +588,7 @@ class Controller: min_mem: int, max_mem: int, port: int, + user_id: int, ): server_id = Helpers.create_uuid() new_server_dir = os.path.join(self.helper.servers_dir, server_id) @@ -609,32 +602,6 @@ class Controller: temp_dir = Helpers.get_os_understandable_path(zip_path) Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(backup_path) - has_properties = False - # extracts archive to temp directory - for item in os.listdir(temp_dir): - if str(item) == "server.properties": - has_properties = True - try: - if not os.path.isdir(os.path.join(temp_dir, item)): - FileHelpers.move_file( - os.path.join(temp_dir, item), os.path.join(new_server_dir, item) - ) - else: - FileHelpers.move_dir( - os.path.join(temp_dir, item), os.path.join(new_server_dir, item) - ) - except Exception as ex: - logger.error(f"ERROR IN ZIP IMPORT: {ex}") - if not has_properties: - logger.info( - f"No server.properties found on zip file import. " - f"Creating one with port selection of {str(port)}" - ) - with open( - os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" - ) as file: - file.write(f"server-port={port}") - file.close() full_jar_path = os.path.join(new_server_dir, server_jar) @@ -664,8 +631,13 @@ class Controller: server_log_file, server_stop, port, + user_id, server_type="minecraft-java", ) + ServersController.set_import(new_id) + self.import_helper.import_java_zip_server( + temp_dir, new_server_dir, port, new_id + ) return new_id # ********************************************************************************** @@ -673,7 +645,12 @@ class Controller: # ********************************************************************************** def import_bedrock_server( - self, server_name: str, server_path: str, server_exe: str, port: int + self, + server_name: str, + server_path: str, + server_exe: str, + port: int, + user_id: int, ): server_id = Helpers.create_uuid() new_server_dir = os.path.join(self.helper.servers_dir, server_id) @@ -687,25 +664,6 @@ class Controller: Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(backup_path) server_path = Helpers.get_os_understandable_path(server_path) - try: - FileHelpers.copy_dir(server_path, new_server_dir, True) - except shutil.Error as ex: - logger.error(f"Server import failed with error: {ex}") - - has_properties = False - for item in os.listdir(new_server_dir): - if str(item) == "server.properties": - has_properties = True - if not has_properties: - logger.info( - f"No server.properties found on zip file import. " - f"Creating one with port selection of {str(port)}" - ) - with open( - os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" - ) as file: - file.write(f"server-port={port}") - file.close() full_jar_path = os.path.join(new_server_dir, server_exe) @@ -727,15 +685,22 @@ class Controller: server_log_file, server_stop, port, + user_id, server_type="minecraft-bedrock", ) - if os.name != "nt": - if Helpers.check_file_exists(full_jar_path): - os.chmod(full_jar_path, 0o2760) + ServersController.set_import(new_id) + self.import_helper.import_bedrock_server( + server_path, new_server_dir, port, full_jar_path, new_id + ) return new_id def import_bedrock_zip_server( - self, server_name: str, zip_path: str, server_exe: str, port: int + self, + server_name: str, + zip_path: str, + server_exe: str, + port: int, + user_id: int, ): server_id = Helpers.create_uuid() new_server_dir = os.path.join(self.helper.servers_dir, server_id) @@ -749,32 +714,6 @@ class Controller: temp_dir = Helpers.get_os_understandable_path(zip_path) Helpers.ensure_dir_exists(new_server_dir) Helpers.ensure_dir_exists(backup_path) - has_properties = False - # extracts archive to temp directory - for item in os.listdir(temp_dir): - if str(item) == "server.properties": - has_properties = True - try: - if not os.path.isdir(os.path.join(temp_dir, item)): - FileHelpers.move_file( - os.path.join(temp_dir, item), os.path.join(new_server_dir, item) - ) - else: - FileHelpers.move_dir( - os.path.join(temp_dir, item), os.path.join(new_server_dir, item) - ) - except Exception as ex: - logger.error(f"ERROR IN ZIP IMPORT: {ex}") - if not has_properties: - logger.info( - f"No server.properties found on zip file import. " - f"Creating one with port selection of {str(port)}" - ) - with open( - os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8" - ) as file: - file.write(f"server-port={port}") - file.close() full_jar_path = os.path.join(new_server_dir, server_exe) @@ -796,8 +735,12 @@ class Controller: server_log_file, server_stop, port, + user_id, server_type="minecraft-bedrock", ) + self.import_helper.import_bedrock_zip_server( + temp_dir, new_server_dir, full_jar_path, port, new_id + ) if os.name != "nt": if Helpers.check_file_exists(full_jar_path): os.chmod(full_jar_path, 0o2760) @@ -838,6 +781,7 @@ class Controller: server_log_file: str, server_stop: str, server_port: int, + created_by: int, server_type: str, server_host: str = "127.0.0.1", ): @@ -852,6 +796,7 @@ class Controller: server_log_file, server_stop, server_type, + created_by, server_port, server_host, ) diff --git a/app/classes/shared/main_models.py b/app/classes/shared/main_models.py index 73d1d484..7c43a131 100644 --- a/app/classes/shared/main_models.py +++ b/app/classes/shared/main_models.py @@ -26,6 +26,7 @@ class DatabaseBuilder: password=password, email="default@example.com", superuser=True, + manager=None, ) def is_fresh_install(self): diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 74e93ba7..0b2c5b96 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -293,7 +293,7 @@ class ServerInstance: else: user_lang = HelperUsers.get_user_lang_by_id(user_id) - if self.stats_helper.get_download_status(): + if self.stats_helper.get_import_status(): if user_id: self.helper.websocket_helper.broadcast_user( user_id, @@ -612,21 +612,25 @@ class ServerInstance: # caching the name and pid number server_name = self.name server_pid = self.process.pid + self.shutdown_timeout = self.settings["shutdown_timeout"] while running: i += 1 - logstr = ( - f"Server {server_name} is still running " - f"- waiting 2s to see if it stops ({int(60-(i*2))} " - f"seconds until force close)" - ) - logger.info(logstr) - Console.info(logstr) + ttk = int(self.shutdown_timeout - (i * 2)) + if i <= self.shutdown_timeout / 2: + logstr = ( + f"Server {server_name} is still running " + "- waiting 2s to see if it stops" + f"({ttk} " + f"seconds until force close)" + ) + logger.info(logstr) + Console.info(logstr) running = self.check_running() time.sleep(2) # if we haven't closed in 60 seconds, let's just slam down on the PID - if i >= 30: + if i >= round(self.shutdown_timeout / 2, 0): logger.info( f"Server {server_name} is still running - Forcing the process down" ) diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index 9aa41d28..bd8f9424 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -394,6 +394,7 @@ class AjaxHandler(BaseHandler): "1", "2", server_data["server_port"], + server_data["created_by"], ) new_server_id = new_server new_server = self.controller.servers.get_server_data(new_server) @@ -416,6 +417,7 @@ class AjaxHandler(BaseHandler): temp_dir, server_data["executable"], server_data["server_port"], + server_data["created_by"], ) new_server_id = new_server new_server = self.controller.servers.get_server_data(new_server) diff --git a/app/classes/web/api_handler.py b/app/classes/web/api_handler.py index 43af4ae8..34b09ee8 100644 --- a/app/classes/web/api_handler.py +++ b/app/classes/web/api_handler.py @@ -340,10 +340,11 @@ class CreateUser(ApiHandler): new_username = self.get_argument("username").lower() new_pass = self.get_argument("password") + manager = int(user_obj["user_id"]) if new_username: self.controller.users.add_user( - new_username, new_pass, "default@example.com", True, False + new_username, manager, new_pass, "default@example.com", True, False ) self.return_response( diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index e8592cdd..a8bac6e2 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -453,8 +453,8 @@ class PanelHandler(BaseHandler): for server in un_used_servers[:]: if flag == 0: server["stats"][ - "downloading" - ] = self.controller.servers.get_download_status( + "importing" + ] = self.controller.servers.get_import_status( str(server["stats"]["server_id"]["server_id"]) ) server["stats"]["crashed"] = self.controller.servers.is_crashed( @@ -572,11 +572,11 @@ class PanelHandler(BaseHandler): "started": "False", } if not self.failed_server: - page_data["downloading"] = self.controller.servers.get_download_status( + page_data["importing"] = self.controller.servers.get_import_status( server_id ) else: - page_data["downloading"] = False + page_data["importing"] = False page_data["server_id"] = server_id try: page_data["waiting_start"] = self.controller.servers.get_waiting_start( @@ -866,6 +866,18 @@ class PanelHandler(BaseHandler): page_data["users"] = self.controller.users.get_all_users() page_data["roles"] = self.controller.roles.get_all_roles() page_data["auth-servers"][user.user_id] = super_auth_servers + page_data["managed_users"] = [] + else: + page_data["managed_users"] = self.controller.users.get_managed_users( + exec_user["user_id"] + ) + page_data["assigned_roles"] = [] + for item in page_data["roles"]: + page_data["assigned_roles"].append(item.role_id) + + page_data["managed_roles"] = self.controller.users.get_managed_roles( + exec_user["user_id"] + ) template = "panel/panel_config.html" @@ -891,7 +903,7 @@ class PanelHandler(BaseHandler): ) return - page_data["roles_all"] = self.controller.roles.get_all_roles() + page_data["roles"] = self.controller.roles.get_all_roles() page_data["servers"] = [] page_data["servers_all"] = self.controller.servers.get_all_defined_servers() page_data["role-servers"] = [] @@ -910,8 +922,16 @@ class PanelHandler(BaseHandler): ) if superuser: page_data["super-disabled"] = "" + page_data["users"] = self.controller.users.get_all_users() else: page_data["super-disabled"] = "disabled" + + page_data["exec_user"] = exec_user["user_id"] + + page_data["manager"] = { + "user_id": -100, + "username": "None", + } for file in sorted( os.listdir(os.path.join(self.helper.root_dir, "app", "translations")) ): @@ -1080,9 +1100,21 @@ class PanelHandler(BaseHandler): page_data["user"] = self.controller.users.get_user_by_id(user_id) page_data["servers"] = set() page_data["role-servers"] = page_role_servers - page_data["roles_all"] = self.controller.roles.get_all_roles() + page_data["roles"] = self.controller.roles.get_all_roles() + page_data["exec_user"] = exec_user["user_id"] page_data["servers_all"] = self.controller.servers.get_all_defined_servers() page_data["superuser"] = superuser + if page_data["user"]["manager"] is not None: + page_data["manager"] = self.controller.users.get_user_by_id( + page_data["user"]["manager"] + ) + else: + page_data["manager"] = { + "user_id": -100, + "username": "None", + } + if exec_user["superuser"]: + page_data["users"] = self.controller.users.get_all_users() page_data[ "permissions_all" ] = self.controller.crafty_perms.list_defined_crafty_permissions() @@ -1121,6 +1153,17 @@ class PanelHandler(BaseHandler): "/panel/error?error=Unauthorized access: not a user editor" ) return + if ( + ( + self.controller.users.get_user_by_id(user_id)["manager"] + != exec_user["user_id"] + ) + and not exec_user["superuser"] + and str(exec_user["user_id"]) != str(user_id) + ): + self.redirect( + "/panel/error?error=Unauthorized access: you cannot edit this user" + ) page_data["servers"] = [] page_data["role-servers"] = [] @@ -1218,6 +1261,11 @@ class PanelHandler(BaseHandler): defined_servers = self.controller.servers.get_authorized_servers( exec_user["user_id"] ) + + page_data["role_manager"] = { + "user_id": -100, + "username": "None", + } page_servers = [] for server in defined_servers: if server not in page_servers: @@ -1235,6 +1283,7 @@ class PanelHandler(BaseHandler): user_roles = self.get_user_roles() page_data["new_role"] = False role_id = self.get_argument("id", None) + role = self.controller.roles.get_role(role_id) page_data["role"] = self.controller.roles.get_role_with_servers(role_id) if exec_user["superuser"]: defined_servers = self.controller.servers.list_defined_servers() @@ -1258,7 +1307,21 @@ class PanelHandler(BaseHandler): page_data["user-roles"] = user_roles page_data["users"] = self.controller.users.get_all_users() - if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: + if page_data["role"]["manager"] is not None: + page_data["role_manager"] = self.controller.users.get_user_by_id( + page_data["role"]["manager"] + ) + else: + page_data["role_manager"] = { + "user_id": -100, + "username": "None", + } + + if ( + EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions + or exec_user["user_id"] != role["manager"] + and not exec_user["superuser"] + ): self.redirect( "/panel/error?error=Unauthorized access: not a role editor" ) @@ -1272,8 +1335,15 @@ class PanelHandler(BaseHandler): elif page == "remove_role": role_id = bleach.clean(self.get_argument("id", None)) - if not superuser: - self.redirect("/panel/error?error=Unauthorized access: not superuser") + if ( + not superuser + and self.controller.roles.get_role(role_id)["manager"] + != exec_user["user_id"] + ): + self.redirect( + "/panel/error?error=Unauthorized access: not superuser not" + " role manager" + ) return if role_id is None: self.redirect("/panel/error?error=Invalid Role ID") @@ -1424,6 +1494,7 @@ class PanelHandler(BaseHandler): return server_name = self.get_argument("server_name", None) server_obj = self.controller.servers.get_server_obj(server_id) + shutdown_timeout = self.get_argument("shutdown_timeout", 60) if superuser: server_path = self.get_argument("server_path", None) if Helpers.is_os_windows(): @@ -1504,6 +1575,7 @@ class PanelHandler(BaseHandler): ) server_obj.server_name = server_name + server_obj.shutdown_timeout = shutdown_timeout if superuser: if Helpers.validate_traversal( self.helper.get_servers_root_dir(), server_path @@ -1936,6 +2008,7 @@ class PanelHandler(BaseHandler): "system user is not editable" ) user_id = bleach.clean(self.get_argument("id", None)) + user = self.controller.users.get_user_by_id(user_id) username = bleach.clean(self.get_argument("username", None).lower()) if ( username != self.controller.users.get_user_by_id(user_id)["username"] @@ -1968,7 +2041,19 @@ class PanelHandler(BaseHandler): else: superuser = 0 - if not exec_user["superuser"]: + if exec_user["superuser"]: + manager = self.get_argument("manager") + if manager == "": + manager = None + else: + manager = int(manager) + else: + manager = user["manager"] + + if ( + not exec_user["superuser"] + and int(exec_user["user_id"]) != user["manager"] + ): if username is None or username == "": self.redirect("/panel/error?error=Invalid username") return @@ -2019,6 +2104,7 @@ class PanelHandler(BaseHandler): user_data = { "username": username, + "manager": manager, "password": password0, "email": email, "enabled": enabled, @@ -2035,13 +2121,13 @@ class PanelHandler(BaseHandler): user_id, user_data=user_data, user_crafty_data=user_crafty_data ) - self.controller.management.add_to_audit_log( - exec_user["user_id"], - f"Edited user {username} (UID:{user_id}) with roles {roles} " - f"and permissions {permissions_mask}", - server_id=0, - source_ip=self.get_remote_ip(), - ) + self.controller.management.add_to_audit_log( + exec_user["user_id"], + f"Edited user {username} (UID:{user_id}) with roles {roles} " + f"and permissions {permissions_mask}", + server_id=0, + source_ip=self.get_remote_ip(), + ) self.redirect("/panel/panel_config") elif page == "edit_user_apikeys": @@ -2164,6 +2250,15 @@ class PanelHandler(BaseHandler): if username is None or username == "": self.redirect("/panel/error?error=Invalid username") return + + if exec_user["superuser"]: + manager = self.get_argument("manager") + if manager == "": + manager = None + else: + manager = int(manager) + else: + manager = int(exec_user["user_id"]) # does this user id exist? if self.controller.users.get_id_by_name(username) is not None: self.redirect("/panel/error?error=User exists") @@ -2178,6 +2273,7 @@ class PanelHandler(BaseHandler): user_id = self.controller.users.add_user( username, + manager=manager, password=password0, email=email, enabled=enabled, @@ -2204,14 +2300,19 @@ class PanelHandler(BaseHandler): server_id=0, source_ip=self.get_remote_ip(), ) - self.controller.crafty_perms.add_user_creation(exec_user["user_id"]) self.redirect("/panel/panel_config") elif page == "edit_role": role_id = bleach.clean(self.get_argument("id", None)) role_name = bleach.clean(self.get_argument("role_name", None)) - if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: + role = self.controller.roles.get_role(role_id) + + if ( + EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions + and exec_user["user_id"] != role["manager"] + and not exec_user["superuser"] + ): self.redirect( "/panel/error?error=Unauthorized access: not a role editor" ) @@ -2227,9 +2328,18 @@ class PanelHandler(BaseHandler): self.redirect("/panel/error?error=Invalid Role ID") return + if exec_user["superuser"]: + manager = self.get_argument("manager", None) + if manager == "": + manager = None + else: + manager = role["manager"] + servers = self.get_role_servers() - self.controller.roles.update_role_advanced(role_id, role_name, servers) + self.controller.roles.update_role_advanced( + role_id, role_name, servers, manager + ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -2241,6 +2351,12 @@ class PanelHandler(BaseHandler): elif page == "add_role": role_name = bleach.clean(self.get_argument("role_name", None)) + if exec_user["superuser"]: + manager = self.get_argument("manager", None) + if manager == "": + manager = None + else: + manager = exec_user["user_id"] if EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_crafty_permissions: self.redirect( @@ -2265,7 +2381,9 @@ class PanelHandler(BaseHandler): servers = self.get_role_servers() - role_id = self.controller.roles.add_role_advanced(role_name, servers) + role_id = self.controller.roles.add_role_advanced( + role_name, servers, manager + ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -2273,7 +2391,6 @@ class PanelHandler(BaseHandler): server_id=0, source_ip=self.get_remote_ip(), ) - self.controller.crafty_perms.add_role_creation(exec_user["user_id"]) self.redirect("/panel/panel_config") else: diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py index 2ca1baf3..150bff0c 100644 --- a/app/classes/web/routes/api/roles/index.py +++ b/app/classes/web/routes/api/roles/index.py @@ -116,7 +116,9 @@ class ApiRolesIndexHandler(BaseApiHandler): 400, {"status": "error", "error": "ROLE_NAME_ALREADY_EXISTS"} ) - role_id = self.controller.roles.add_role_advanced(role_name, servers) + role_id = self.controller.roles.add_role_advanced( + role_name, servers, user["user_id"] + ) self.controller.management.add_to_audit_log( user["user_id"], diff --git a/app/classes/web/routes/api/servers/index.py b/app/classes/web/routes/api/servers/index.py index 7db12f45..b94b4c01 100644 --- a/app/classes/web/routes/api/servers/index.py +++ b/app/classes/web/routes/api/servers/index.py @@ -665,10 +665,9 @@ class ApiServersIndexHandler(BaseApiHandler): }, ) - new_server_id, new_server_uuid = self.controller.create_api_server(data) - - # Increase the server creation counter - self.controller.crafty_perms.add_server_creation(user["user_id"]) + new_server_id, new_server_uuid = self.controller.create_api_server( + data, user["user_id"] + ) self.controller.servers.stats.record_stats() diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py index cf9163b9..e5b3ae23 100644 --- a/app/classes/web/routes/api/servers/server/action.py +++ b/app/classes/web/routes/api/servers/server/action.py @@ -84,6 +84,7 @@ class ApiServersServerActionHandler(BaseApiHandler): new_server_log_file, server_data.get("stop_command"), server_data.get("type"), + user_id, server_data.get("server_port"), ) diff --git a/app/classes/web/routes/api/users/index.py b/app/classes/web/routes/api/users/index.py index 3e4cfdab..6f46740e 100644 --- a/app/classes/web/routes/api/users/index.py +++ b/app/classes/web/routes/api/users/index.py @@ -96,6 +96,7 @@ class ApiUsersIndexHandler(BaseApiHandler): username = data["username"] username = str(username).lower() + manager = int(user["user_id"]) password = data["password"] email = data.get("email", "default@example.com") enabled = data.get("enabled", True) @@ -149,6 +150,7 @@ class ApiUsersIndexHandler(BaseApiHandler): # TODO: do this in the most efficient way user_id = self.controller.users.add_user( username, + manager, password, email, enabled, diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index df4ba684..c13198ed 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -1,6 +1,7 @@ import json import logging import os +import time import tornado.web import tornado.escape import bleach @@ -224,6 +225,23 @@ class ServerHandler(BaseHandler): if server_id is not None: if command == "clone_server": + if ( + not superuser + and not self.controller.crafty_perms.can_create_server( + exec_user["user_id"] + ) + ): + time.sleep(3) + self.helper.websocket_helper.broadcast_user( + exec_user["user_id"], + "send_start_error", + { + "error": "" + " Not a server creator or server limit reached." + }, + ) + return def is_name_used(name): for server in self.controller.servers.get_all_defined_servers(): @@ -231,6 +249,7 @@ class ServerHandler(BaseHandler): return True return + template = "/panel/dashboard" server_data = self.controller.servers.get_server_data_by_id( server_id ) @@ -265,6 +284,7 @@ class ServerHandler(BaseHandler): backup_path = os.path.join(self.helper.backup_path, new_server_uuid) server_port = server_data.get("server_port") server_type = server_data.get("type") + created_by = exec_user["user_id"] new_server_id = self.controller.servers.create_server( new_server_name, @@ -276,6 +296,7 @@ class ServerHandler(BaseHandler): new_server_log_file, stop_command, server_type, + created_by, server_port, ) if not exec_user["superuser"]: @@ -283,7 +304,8 @@ class ServerHandler(BaseHandler): new_server_id ).get("server_uuid") role_id = self.controller.roles.add_role( - f"Creator of Server with uuid={new_server_uuid}" + f"Creator of Server with uuid={new_server_uuid}", + exec_user["user_id"], ) self.controller.server_perms.add_role_server( new_server_id, role_id, "11111111" @@ -291,9 +313,6 @@ class ServerHandler(BaseHandler): self.controller.users.add_role_to_user( exec_user["user_id"], role_id ) - self.controller.crafty_perms.add_server_creation( - exec_user["user_id"] - ) self.controller.servers.init_all_servers() @@ -353,6 +372,7 @@ class ServerHandler(BaseHandler): min_mem, max_mem, port, + exec_user["user_id"], ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -369,7 +389,13 @@ class ServerHandler(BaseHandler): return new_server_id = self.controller.import_zip_server( - server_name, zip_path, import_server_jar, min_mem, max_mem, port + server_name, + zip_path, + import_server_jar, + min_mem, + max_mem, + port, + exec_user["user_id"], ) if new_server_id == "false": self.redirect( @@ -385,8 +411,6 @@ class ServerHandler(BaseHandler): new_server_id, self.get_remote_ip(), ) - # deletes temp dir - FileHelpers.del_dirs(zip_path) else: if len(server_parts) != 3: self.redirect("/panel/error?error=Invalid server data") @@ -402,6 +426,7 @@ class ServerHandler(BaseHandler): min_mem, max_mem, port, + exec_user["user_id"], ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -420,7 +445,8 @@ class ServerHandler(BaseHandler): new_server_id ).get("server_uuid") role_id = self.controller.roles.add_role( - f"Creator of Server with uuid={new_server_uuid}" + f"Creator of Server with uuid={new_server_uuid}", + exec_user["user_id"], ) self.controller.server_perms.add_role_server( new_server_id, role_id, "11111111" @@ -428,9 +454,6 @@ class ServerHandler(BaseHandler): self.controller.users.add_role_to_user( exec_user["user_id"], role_id ) - self.controller.crafty_perms.add_server_creation( - exec_user["user_id"] - ) else: for role in captured_roles: @@ -483,7 +506,11 @@ class ServerHandler(BaseHandler): return new_server_id = self.controller.import_bedrock_server( - server_name, import_server_path, import_server_exe, port + server_name, + import_server_path, + import_server_exe, + port, + exec_user["user_id"], ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -500,7 +527,11 @@ class ServerHandler(BaseHandler): return new_server_id = self.controller.import_bedrock_zip_server( - server_name, zip_path, import_server_exe, port + server_name, + zip_path, + import_server_exe, + port, + exec_user["user_id"], ) if new_server_id == "false": self.redirect( @@ -516,8 +547,6 @@ class ServerHandler(BaseHandler): new_server_id, self.get_remote_ip(), ) - # deletes temp dir - FileHelpers.del_dirs(zip_path) else: if len(server_parts) != 2: self.redirect("/panel/error?error=Invalid server data") @@ -526,7 +555,13 @@ class ServerHandler(BaseHandler): # TODO: add server type check here and call the correct server # add functions if not a jar new_server_id = self.controller.create_jar_server( - server_type, server_version, server_name, min_mem, max_mem, port + server_type, + server_version, + server_name, + min_mem, + max_mem, + port, + exec_user["user_id"], ) self.controller.management.add_to_audit_log( exec_user["user_id"], @@ -545,7 +580,8 @@ class ServerHandler(BaseHandler): new_server_id ).get("server_uuid") role_id = self.controller.roles.add_role( - f"Creator of Server with uuid={new_server_uuid}" + f"Creator of Server with uuid={new_server_uuid}", + exec_user["user_id"], ) self.controller.server_perms.add_role_server( new_server_id, role_id, "11111111" @@ -553,9 +589,6 @@ class ServerHandler(BaseHandler): self.controller.users.add_role_to_user( exec_user["user_id"], role_id ) - self.controller.crafty_perms.add_server_creation( - exec_user["user_id"] - ) else: for role in captured_roles: diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index ec5a21db..23ebf2c7 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -193,9 +193,9 @@ {{ translate('dashboard', 'starting', data['lang']) }} - {% elif server['stats']['downloading']%} + {% elif server['stats']['importing']%} - {{ translate('serverTerm', 'downloading', + {{ translate('serverTerm', 'importing', data['lang']) }} {% else %} - {% elif server['stats']['downloading']%} + {% elif server['stats']['importing']%}
- {{ translate('serverTerm', 'downloading', data['lang']) }} + {{ translate('serverTerm', 'importing', data['lang']) }}
{% else %} @@ -893,9 +893,6 @@ message: '
  {% raw translate("dashboard", "bePatientClone", data["lang"]) %}
', closeButton: false, }); - setTimeout(function () { - location.reload(); - }, 5000) } diff --git a/app/frontend/templates/panel/panel_config.html b/app/frontend/templates/panel/panel_config.html index 94d5c0f1..b344f1c9 100644 --- a/app/frontend/templates/panel/panel_config.html +++ b/app/frontend/templates/panel/panel_config.html @@ -87,6 +87,38 @@ {% end %} + {% for user in data['managed_users'] %} + + {{ user.username }} + + {% if user.enabled %} + + Yes + + {% else %} + + No + + + {% end %} + + + + + + + + + + {% end %} @@ -146,6 +178,34 @@ {% end %} + {% if not data['superuser'] %} + {% for role in data['managed_roles'] %} + {% if role.role_id not in data['assigned_roles'] %} + + {{ role.role_name }} + + + + + + + + + {% end %} + {% end %} + {% end %} diff --git a/app/frontend/templates/panel/panel_edit_role.html b/app/frontend/templates/panel/panel_edit_role.html index 49559591..86ee953a 100644 --- a/app/frontend/templates/panel/panel_edit_role.html +++ b/app/frontend/templates/panel/panel_edit_role.html @@ -49,7 +49,7 @@
-
+ {% raw xsrf_form_html() %} @@ -63,6 +63,29 @@
+ +
+ {% if data['superuser'] %} +
+ + +
+ {% end %}
@@ -252,6 +275,8 @@
{{ translate('rolesConfig', 'configUpdate', data['lang']) }} {{ str(data['role']['last_update']) }}
+ {{ translate('userConfig', 'manager', data['lang']) }}: {{ data['role_manager']['username'] }} +

diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html index 7286f4a0..6f165f52 100644 --- a/app/frontend/templates/panel/panel_edit_user.html +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -121,6 +121,27 @@ data['lang']) }}{% end %} {% end %}
+ {% if data['superuser'] %} +
+ + +
+ {% end %} @@ -141,21 +162,29 @@ data['lang']) }}{% end %} - {% for role in data['roles_all'] %} + {% for role in data['roles'] %} + {% if data['superuser'] or role.role_id in data['user']['roles'] or role.manager == data['exec_user'] %} {{ role.role_name }} {% if role.role_id in data['user']['roles'] %} + {% if role.manager == data['exec_user'] or data['superuser'] %} {% else %} + + {% end %} + {% elif data['superuser'] or role.manager == data['exec_user'] %} {% end %} + {% end %} {% end %} @@ -268,6 +297,8 @@ data['lang']) }}{% end %}
{{ translate('userConfig', 'lastIP', data['lang']) }} {{ data['user']['last_ip'] }}
+ {{ translate('userConfig', 'manager', data['lang'])}}: {{data['manager']['username'] }} +

@@ -284,7 +315,7 @@ data['lang']) }}{% end %} {% else %} diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html index 6c9a8b36..e1c38e1e 100644 --- a/app/frontend/templates/panel/server_config.html +++ b/app/frontend/templates/panel/server_config.html @@ -165,6 +165,18 @@ {% end %} +
+ + +
+
- {% elif data['downloading'] %} + {% elif data['importing'] %}
- diff --git a/app/migrations/20220819_role_manager.py b/app/migrations/20220819_role_manager.py new file mode 100644 index 00000000..bbc102ed --- /dev/null +++ b/app/migrations/20220819_role_manager.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("roles", manager=peewee.IntegerField(null=True)) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("roles", ["manager"]) + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20220819_user_manager.py b/app/migrations/20220819_user_manager.py new file mode 100644 index 00000000..07fa186f --- /dev/null +++ b/app/migrations/20220819_user_manager.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("users", manager=peewee.IntegerField(null=True)) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("users", ["manager"]) + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20220820_quota.py b/app/migrations/20220820_quota.py new file mode 100644 index 00000000..a1d93e49 --- /dev/null +++ b/app/migrations/20220820_quota.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("servers", created_by=peewee.IntegerField(default=-100)) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("servers", ["created_by"]) + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20220820_user_crafty.py b/app/migrations/20220820_user_crafty.py new file mode 100644 index 00000000..6d1557d7 --- /dev/null +++ b/app/migrations/20220820_user_crafty.py @@ -0,0 +1,20 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.drop_columns("user_crafty", ["created_server"]) + migrator.drop_columns("user_crafty", ["created_user"]) + migrator.drop_columns("user_crafty", ["created_role"]) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.add_columns("user_crafty", created_server=peewee.IntegerField(default=0)) + migrator.add_columns("user_crafty", created_user=peewee.IntegerField(default=0)) + migrator.add_columns("user_crafty", created_role=peewee.IntegerField(default=0)) + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20220821_shutdown_timeout.py b/app/migrations/20220821_shutdown_timeout.py new file mode 100644 index 00000000..68130360 --- /dev/null +++ b/app/migrations/20220821_shutdown_timeout.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("servers", shutdown_timeout=peewee.IntegerField(default=60)) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("servers", ["shutdown_timeout"]) + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/stats/20220817_schedule_rename_downloading.py b/app/migrations/stats/20220817_schedule_rename_downloading.py new file mode 100644 index 00000000..f74ce96d --- /dev/null +++ b/app/migrations/stats/20220817_schedule_rename_downloading.py @@ -0,0 +1,17 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.rename_column("server_stats", "downloading", "importing") + + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.rename_column("server_stats", "importing", "downloading") + """ + Write your rollback migrations here. + """ diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 1c86e364..0f510b90 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -245,7 +245,8 @@ "roleUsers": "Role Users: ", "serverAccess": "Access?", "serverName": "Server Name", - "serversDesc": "servers this role is allowed to access" + "serversDesc": "servers this role is allowed to access", + "selectManager": "Select a manager for this Role" }, "serverBackups": { "backupAtMidnight": "Auto-backup at midnight?", @@ -324,7 +325,10 @@ "stopBeforeDeleting": "Please stop the server before deleting it", "update": "Update Executable", "yesDelete": "Yes, delete", - "yesDeleteFiles": "Yes, delete files" + "yesDeleteFiles": "Yes, delete files", + "shutdownTimeout": "Shutdown Timeout", + "timeoutExplain1": "How long Crafty will wait for your server to shutdown after executing the", + "timeoutExplain2": "command before it forces the process down." }, "serverConfigHelp": { "desc": "Here is where you can change the configuration of your server", @@ -464,7 +468,7 @@ "serverTerm": { "commandInput": "Enter your command", "delay-explained": "The service/agent has recently started and is delaying the start of the minecraft server instance", - "downloading": "Downloading...", + "importing": "Importing...", "restart": "Restart", "sendCommand": "Send command", "start": "Start", @@ -562,6 +566,8 @@ "userRoles": "User Roles", "userRolesDesc": "Roles this user is a member of.", "userSettings": "User Settings", - "uses": "Number of uses allowed (-1==No Limit)" + "uses": "Number of uses allowed (-1==No Limit)", + "manager": "Manager", + "selectManager": "Select Manager for User" } } \ No newline at end of file diff --git a/main.py b/main.py index f41c1d74..b6fdd89f 100644 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ from app.classes.shared.import3 import Import3 from app.classes.shared.console import Console from app.classes.shared.helpers import Helpers from app.classes.models.users import HelperUsers +from app.classes.shared.import_helper import ImportHelpers console = Console() helper = Helpers() @@ -135,8 +136,9 @@ if __name__ == "__main__": else: Console.debug("Existing install detected") file_helper = FileHelpers(helper) + import_helper = ImportHelpers(helper, file_helper) # now the tables are created, we can load the tasks_manager and server controller - controller = Controller(database, helper, file_helper) + controller = Controller(database, helper, file_helper, import_helper) import3 = Import3(helper, controller) tasks_manager = TasksManager(helper, controller) tasks_manager.start_webserver()