diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py index 4a27d686..0d21d9d4 100644 --- a/app/classes/minecraft/mc_ping.py +++ b/app/classes/minecraft/mc_ping.py @@ -1,9 +1,12 @@ +from app.classes.shared.helpers import Helpers import struct import socket import base64 import json import sys +import os import logging.config +from app.classes.shared.console import console logger = logging.getLogger(__name__) @@ -25,8 +28,26 @@ class Server: description = self.description if 'extra' in description.keys(): for e in description['extra']: + #Conversion format code needed only for Java Version + lines.append(get_code_format("reset")) + if "bold" in e.keys(): + lines.append(get_code_format("bold")) + if "italic" in e.keys(): + lines.append(get_code_format("italic")) + if "underlined" in e.keys(): + lines.append(get_code_format("underlined")) + if "strikethrough" in e.keys(): + lines.append(get_code_format("strikethrough")) + if "obfuscated" in e.keys(): + lines.append(get_code_format("obfuscated")) + if "color" in e.keys(): + lines.append(get_code_format(e['color'])) + #Then append the text if "text" in e.keys(): - lines.append(e['text']) + if e['text'] == '\n': + lines.append("§§") + else: + lines.append(e['text']) total_text = " ".join(lines) self.description = total_text @@ -70,6 +91,26 @@ class Player: def __str__(self): return self.name +def get_code_format(format_name): + root_dir = os.path.abspath(os.path.curdir) + format_file = os.path.join(root_dir, 'app', 'config', 'motd_format.json') + try: + with open(format_file, "r", encoding='utf-8') as f: + data = json.load(f) + + if format_name in data.keys(): + return data.get(format_name) + else: + logger.error("Format MOTD Error: format name {} does not exist".format(format_name)) + console.error("Format MOTD Error: format name {} does not exist".format(format_name)) + return "" + + except Exception as e: + logger.critical("Config File Error: Unable to read {} due to {}".format(format_file, e)) + console.critical("Config File Error: Unable to read {} due to {}".format(format_file, e)) + + return "" + # For the rest of requests see wiki.vg/Protocol def ping(ip, port): diff --git a/app/classes/minecraft/server_props.py b/app/classes/minecraft/server_props.py index cd4ffd89..89c24c99 100644 --- a/app/classes/minecraft/server_props.py +++ b/app/classes/minecraft/server_props.py @@ -19,7 +19,7 @@ class ServerProps: s = line s1 = s[:s.find('=')] if '\n' in s: - s2 = s[s.find('=')+1:s.find('\\')] + s2 = s[s.find('=')+1:s.find('\n')] else: s2 = s[s.find('=')+1:] d[s1] = s2 diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index e7a5f448..66f90cbc 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -11,6 +11,7 @@ from app.classes.shared.helpers import helper from app.classes.shared.console import console from app.classes.shared.models import Servers from app.classes.minecraft.server_props import ServerProps +from app.classes.web.websocket_helper import websocket_helper logger = logging.getLogger(__name__) @@ -173,11 +174,11 @@ class ServerJars: response = self._get_api_result(url) return response - def download_jar(self, server, version, path): - update_thread = threading.Thread(target=self.a_download_jar, daemon=True, name="exe_download", args=(server, version, path)) + def download_jar(self, server, version, path, name): + update_thread = threading.Thread(target=self.a_download_jar, daemon=True, name="exe_download", args=(server, version, path, name)) update_thread.start() - def a_download_jar(self, server, version, path): + def a_download_jar(self, server, version, path, name): fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version) # open a file stream @@ -189,6 +190,8 @@ class ServerJars: except Exception as e: logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e)) pass + websocket_helper.broadcast('notification', "Executable download finished for server named: " + name) + return False diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index 6d0492c2..30b78697 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -4,6 +4,7 @@ import time import psutil import logging import datetime +import base64 from app.classes.shared.helpers import helper @@ -145,12 +146,19 @@ class Stats: logger.info("Unable to read json from ping_obj: {}".format(e)) pass + try: + server_icon = base64.encodebytes(ping_obj.icon) + except Exception as e: + server_icon = False + logger.info("Unable to read the server icon : {}".format(e)) + ping_data = { 'online': online_stats.get("online", 0), 'max': online_stats.get('max', 0), 'players': online_stats.get('players', 0), 'server_description': ping_obj.description, - 'server_version': ping_obj.version + 'server_version': ping_obj.version, + 'server_icon': server_icon } return ping_data @@ -167,7 +175,7 @@ class Stats: # TODO: search server properties file for possible override of 127.0.0.1 - internal_ip = server_data.get('server-ip', "127.0.0.1") + internal_ip = server_data.get('server_ip', "127.0.0.1") server_port = server_settings.get('server-port', "25565") logger.debug("Pinging {} on port {}".format(internal_ip, server_port)) @@ -210,7 +218,7 @@ class Stats: p_stats = self._get_process_stats(server_obj.PID) # TODO: search server properties file for possible override of 127.0.0.1 - internal_ip = server_data.get('server-ip', "127.0.0.1") + internal_ip = server_data.get('server_ip', "127.0.0.1") server_port = server_settings.get('server-port', "25565") logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port)) @@ -246,6 +254,62 @@ class Stats: server_stats_list.append(server_stats) return server_stats_list + + def get_raw_server_stats(self, server_id): + + server_stats = {} + server = self.controller.get_server_obj(server_id) + + logger.debug('Getting stats for server: {}'.format(server_id)) + + # get our server object, settings and data dictionaries + server_obj = self.controller.get_server_obj(server_id) + server_obj.reload_server_settings() + server_settings = self.controller.get_server_settings(server_id) + server_data = self.controller.get_server_data(server_id) + + # world data + world_name = server_settings.get('level-name', 'Unknown') + world_path = os.path.join(server_data.get('path', None), world_name) + + # process stats + p_stats = self._get_process_stats(server_obj.PID) + + # TODO: search server properties file for possible override of 127.0.0.1 + internal_ip = server_data.get('server_ip', "127.0.0.1") + server_port = server_settings.get('server-port', "25565") + + logger.debug("Pinging server '{}' on {}:{}".format(server.name, internal_ip, server_port)) + int_mc_ping = ping(internal_ip, int(server_port)) + + int_data = False + ping_data = {} + + # if we got a good ping return, let's parse it + if int_mc_ping: + int_data = True + ping_data = self.parse_server_ping(int_mc_ping) + + server_stats = { + 'id': server_id, + 'started': server_obj.get_start_time(), + 'running': server_obj.check_running(), + 'cpu': p_stats.get('cpu_usage', 0), + 'mem': p_stats.get('memory_usage', 0), + "mem_percent": p_stats.get('mem_percentage', 0), + 'world_name': world_name, + 'world_size': self.get_world_size(world_path), + 'server_port': server_port, + 'int_ping_results': int_data, + 'online': ping_data.get("online", False), + "max": ping_data.get("max", False), + 'players': ping_data.get("players", False), + 'desc': ping_data.get("server_description", False), + 'version': ping_data.get("server_version", False), + 'icon': ping_data.get("server_icon", False) + } + + return server_stats def record_stats(self): stats_to_send = self.get_node_stats() diff --git a/app/classes/shared/controller.py b/app/classes/shared/controller.py index 9c7e53cf..97fd1775 100644 --- a/app/classes/shared/controller.py +++ b/app/classes/shared/controller.py @@ -97,6 +97,14 @@ class Controller: server_obj = self.get_server_obj(server_id) server_obj.reload_server_settings() + def get_server_settings(self, server_id): + for s in self.servers_list: + if int(s['server_id']) == int(server_id): + return s['server_settings'] + + logger.warning("Unable to find server object for server id {}".format(server_id)) + return False + def get_server_obj(self, server_id): for s in self.servers_list: if int(s['server_id']) == int(server_id): @@ -104,6 +112,14 @@ class Controller: logger.warning("Unable to find server object for server id {}".format(server_id)) return False + + def get_server_data(self, server_id): + for s in self.servers_list: + if int(s['server_id']) == int(server_id): + return s['server_data_obj'] + + logger.warning("Unable to find server object for server id {}".format(server_id)) + return False @staticmethod def list_defined_servers(): @@ -157,7 +173,7 @@ class Controller: @staticmethod def can_add_role(user_id): - #TODO: Complete if we need a User Addition limit + #TODO: Complete if we need a Role Addition limit #return db_helper.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config) return True @@ -184,14 +200,6 @@ class Controller: server_list = db_helper.get_authorized_servers(userId) return server_list - def get_server_data(self, server_id): - for s in self.servers_list: - if int(s['server_id']) == int(server_id): - return s['server_data_obj'] - - logger.warning("Unable to find server object for server id {}".format(server_id)) - return False - def list_running_servers(self): running_servers = [] @@ -297,7 +305,7 @@ class Controller: server_stop = "stop" # download the jar - server_jar_obj.download_jar(server, version, full_jar_path) + server_jar_obj.download_jar(server, version, full_jar_path, name) new_id = self.register_server(name, server_id, server_dir, backup_path, server_command, server_file, server_log_file, server_stop) return new_id diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index bb95d56b..ec50704a 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -242,7 +242,8 @@ class db_builder: # Users.enabled: True, # Users.superuser: True #}).execute() - db_shortcuts.add_user(username, password=password, superuser=True) + user_id = db_shortcuts.add_user(username, password=password, superuser=True) + #db_shortcuts.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} ) #console.info("API token is {}".format(api_token)) @@ -257,6 +258,9 @@ class db_builder: class db_shortcuts: + #************************************************************************************************ + # Generic Databse Methods + #************************************************************************************************ @staticmethod def return_rows(query): rows = [] @@ -271,6 +275,14 @@ class db_shortcuts: return rows + @staticmethod + def return_db_rows(model): + data = [model_to_dict(row) for row in model] + return data + + #************************************************************************************************ + # Generic Servers Methods + #************************************************************************************************ @staticmethod def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565): return Servers.insert({ @@ -302,6 +314,9 @@ class db_shortcuts: except IndexError: return {} + #************************************************************************************************ + # Servers Methods + #************************************************************************************************ @staticmethod def get_all_defined_servers(): query = Servers.select() @@ -345,21 +360,14 @@ class db_shortcuts: return server_data @staticmethod - def get_user_roles_id(user_id): - roles_list = [] - roles = User_Roles.select().where(User_Roles.user_id == user_id) - for r in roles: - roles_list.append(db_helper.get_role(r.role_id)['role_id']) - return roles_list - - @staticmethod - def get_user_roles_names(user_id): - roles_list = [] - roles = User_Roles.select().where(User_Roles.user_id == user_id) - for r in roles: - roles_list.append(db_helper.get_role(r.role_id)['role_name']) - return roles_list + def get_server_friendly_name(server_id): + server_data = db_helper.get_server_data_by_id(server_id) + friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0)) + return friendly_name + #************************************************************************************************ + # Servers Permissions Methods + #************************************************************************************************ @staticmethod def get_permissions_mask(role_id, server_id): permissions_mask = '' @@ -414,6 +422,10 @@ class db_shortcuts: server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0]}) return server_data + + #************************************************************************************************ + # Servers_Stats Methods + #************************************************************************************************ @staticmethod def get_server_stats_by_id(server_id): stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1) @@ -438,6 +450,32 @@ class db_shortcuts: return False return True + @staticmethod + def set_update(server_id, value): + try: + row = Server_Stats.select().where(Server_Stats.server_id == server_id) + except Exception as ex: + logger.error("Database entry not found. ".format(ex)) + with database.atomic(): + Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute() + + @staticmethod + def get_TTL_without_player(server_id): + last_stat = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).first() + last_stat_with_player = Server_Stats.select().where(Server_Stats.server_id == server_id).where(Server_Stats.online > 0).order_by(Server_Stats.created.desc()).first() + return last_stat.created - last_stat_with_player.created + + @staticmethod + def can_stop_no_players(server_id, time_limit): + can = False + ttl_no_players = get_TTL_without_player(server_id) + if (time_limit == -1) or (ttl_no_players > time_limit): + can = True + return can + + #************************************************************************************************ + # Crafty Permissions Methods + #************************************************************************************************ @staticmethod def get_crafty_permissions_mask(user_id): permissions_mask = '' @@ -470,6 +508,9 @@ class db_shortcuts: } return quantity_list + #************************************************************************************************ + # User_Crafty Methods + #************************************************************************************************ @staticmethod def get_User_Crafty(user_id): try: @@ -488,6 +529,11 @@ class db_shortcuts: user_crafty = db_helper.get_User_Crafty(user_id) return user_crafty + @staticmethod + def add_user_crafty(user_id, uc_permissions): + user_crafty = User_Crafty.insert({User_Crafty.user_id: user_id, User_Crafty.permissions: uc_permissions}).execute() + return user_crafty + @staticmethod def get_created_quantity_list(user_id): user_crafty = db_helper.get_User_Crafty(user_id) @@ -518,7 +564,11 @@ class db_shortcuts: user_crafty.created_server += 1 User_Crafty.save(user_crafty) return user_crafty.created_server - + + + #************************************************************************************************ + # Host_Stats Methods + #************************************************************************************************ @staticmethod def get_latest_hosts_stats(): query = Host_Stats.select().order_by(Host_Stats.id.desc()).get() @@ -532,16 +582,15 @@ class db_shortcuts: if len(test) == 0: return token + + #************************************************************************************************ + # Users Methods + #************************************************************************************************ @staticmethod def get_all_users(): query = Users.select() return query - @staticmethod - def get_all_roles(): - query = Roles.select() - return query - @staticmethod def get_user_id_by_name(username): if username == "SYSTEM": @@ -562,57 +611,12 @@ class db_shortcuts: return user else: return {} - - @staticmethod - def add_role_to_user(user_id, role_id): - User_Roles.insert({ - User_Roles.user_id: user_id, - User_Roles.role_id: role_id - }).execute() - - @staticmethod - def add_user_roles(user): - if type(user) == dict: - user_id = user['user_id'] - else: - user_id = user.user_id - - # I just copied this code from get_user, it had those TODOs & comments made by mac - Lukas - - roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id) - # TODO: this query needs to be narrower - roles = set() - for r in roles_query: - roles.add(r.role_id.role_id) - - user['roles'] = roles - #logger.debug("user: ({}) {}".format(user_id, user)) - return user - - @staticmethod - def add_user_crafty(user_id, uc_permissions): - user_crafty = User_Crafty.insert({User_Crafty.user_id: user_id, User_Crafty.permissions: uc_permissions}).execute() - return user_crafty - - @staticmethod - def add_role_server(server_id, role_id, rs_permissions="00000000"): - servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, Role_Servers.permissions: rs_permissions}).execute() - return servers - - + @staticmethod def user_query(user_id): user_query = Users.select().where(Users.user_id == user_id) return user_query - - @staticmethod - def user_role_query(user_id): - user_query = User_Roles.select().where(User_Roles.user_id == user_id) - query = Roles.select().where(Roles.role_id == -1) - for u in user_query: - query = Roles.select().where(Roles.role_id == u.role_id) - return query - + @staticmethod def get_user(user_id): if user_id == 0: @@ -731,6 +735,14 @@ class db_shortcuts: return False return True + #************************************************************************************************ + # Roles Methods + #************************************************************************************************ + @staticmethod + def get_all_roles(): + query = Roles.select() + return query + @staticmethod def get_roleid_by_name(role_name): try: @@ -805,18 +817,79 @@ class db_shortcuts: if not db_shortcuts.get_role(role_id): return False return True + + #************************************************************************************************ + # User_Roles Methods + #************************************************************************************************ + @staticmethod + def get_user_roles_id(user_id): + roles_list = [] + roles = User_Roles.select().where(User_Roles.user_id == user_id) + for r in roles: + roles_list.append(db_helper.get_role(r.role_id)['role_id']) + return roles_list + @staticmethod + def get_user_roles_names(user_id): + roles_list = [] + roles = User_Roles.select().where(User_Roles.user_id == user_id) + for r in roles: + roles_list.append(db_helper.get_role(r.role_id)['role_name']) + return roles_list + + @staticmethod + def add_role_to_user(user_id, role_id): + User_Roles.insert({ + User_Roles.user_id: user_id, + User_Roles.role_id: role_id + }).execute() + + @staticmethod + def add_user_roles(user): + if type(user) == dict: + user_id = user['user_id'] + else: + user_id = user.user_id + + # I just copied this code from get_user, it had those TODOs & comments made by mac - Lukas + + roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id) + # TODO: this query needs to be narrower + roles = set() + for r in roles_query: + roles.add(r.role_id.role_id) + + user['roles'] = roles + #logger.debug("user: ({}) {}".format(user_id, user)) + return user + + + #************************************************************************************************ + # Role_Servers Methods + #************************************************************************************************ + @staticmethod + def add_role_server(server_id, role_id, rs_permissions="00000000"): + servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, Role_Servers.permissions: rs_permissions}).execute() + return servers + + + @staticmethod + def user_role_query(user_id): + user_query = User_Roles.select().where(User_Roles.user_id == user_id) + query = Roles.select().where(Roles.role_id == -1) + for u in user_query: + query = Roles.select().where(Roles.role_id == u.role_id) + return query + + + #************************************************************************************************ + # Commands Methods + #************************************************************************************************ @staticmethod def get_unactioned_commands(): query = Commands.select().where(Commands.executed == 0) return db_helper.return_rows(query) - @staticmethod - def get_server_friendly_name(server_id): - server_data = db_helper.get_server_data_by_id(server_id) - friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0)) - return friendly_name - @staticmethod def send_command(user_id, server_id, remote_ip, command): @@ -833,16 +906,6 @@ class db_shortcuts: Commands.command: command }).execute() - @staticmethod - def get_actity_log(): - q = Audit_Log.select() - return db_helper.return_db_rows(q) - - @staticmethod - def return_db_rows(model): - data = [model_to_dict(row) for row in model] - return data - @staticmethod def mark_command_complete(command_id=None): if command_id is not None: @@ -850,6 +913,14 @@ class db_shortcuts: Commands.update({ Commands.executed: True }).where(Commands.command_id == command_id).execute() + + #************************************************************************************************ + # Audit_Log Methods + #************************************************************************************************ + @staticmethod + def get_actity_log(): + q = Audit_Log.select() + return db_helper.return_db_rows(q) def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg)) @@ -877,6 +948,9 @@ class db_shortcuts: Audit_Log.source_ip: source_ip }).execute() + #************************************************************************************************ + # Schedules Methods + #************************************************************************************************ @staticmethod def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True): sch_id = Schedules.insert({ @@ -916,6 +990,9 @@ class db_shortcuts: def get_schedules_enabled(): return Schedules.select().where(Schedules.enabled == True).execute() + #************************************************************************************************ + # Backups Methods + #************************************************************************************************ @staticmethod def get_backup_config(server_id): try: @@ -937,15 +1014,6 @@ class db_shortcuts: } return conf - @staticmethod - def set_update(server_id, value): - try: - row = Server_Stats.select().where(Server_Stats.server_id == server_id) - except Exception as ex: - logger.error("Database entry not found. ".format(ex)) - with database.atomic(): - Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute() - @staticmethod def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, auto_enabled: bool = True): logger.debug("Updating server {} backup config with {}".format(server_id, locals())) @@ -992,6 +1060,10 @@ class db_shortcuts: b = Backups.create(**conf) logger.debug("Creating new backup record.") + +#************************************************************************************************ +# Servers Permissions Class +#************************************************************************************************ class Enum_Permissions_Server(Enum): Commands = 0 Terminal = 1 @@ -1037,6 +1109,9 @@ class Permissions_Servers: def get_permission(permission_mask, permission_tested: Enum_Permissions_Server): return permission_mask[permission_tested.value] +#************************************************************************************************ +# Crafty Permissions Class +#************************************************************************************************ class Enum_Permissions_Crafty(Enum): Server_Creation = 0 User_Config = 1 @@ -1077,6 +1152,10 @@ class Permissions_Crafty: def get_permission(permission_mask, permission_tested: Enum_Permissions_Crafty): return permission_mask[permission_tested.value] + +#************************************************************************************************ +# Static Accessors +#************************************************************************************************ installer = db_builder() db_helper = db_shortcuts() server_permissions = Permissions_Servers() diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 6998be13..a504c15c 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -115,7 +115,6 @@ class Server: self.settings = server_data_obj # build our server run command - self.setup_server_run_command() if server_data_obj['auto_start']: delay = int(self.settings['auto_start_delay']) diff --git a/app/classes/web/status_handler.py b/app/classes/web/status_handler.py new file mode 100644 index 00000000..aeaba5c1 --- /dev/null +++ b/app/classes/web/status_handler.py @@ -0,0 +1,55 @@ +from re import template +import sys +import json +import logging +import tornado.web +import tornado.escape +import requests + +from app.classes.shared.helpers import helper +from app.classes.web.base_handler import BaseHandler +from app.classes.shared.console import console +from app.classes.shared.models import Users, fn, db_helper + +logger = logging.getLogger(__name__) + +try: + import bleach + +except ModuleNotFoundError as e: + logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) + console.critical("Import Error: Unable to load {} module".format(e.name)) + sys.exit(1) + + +class StatusHandler(BaseHandler): + def get(self): + page_data = {} + page_data['servers'] = db_helper.get_all_servers_stats() + for srv in page_data['servers']: + server_data = srv.get('server_data', False) + server_id = server_data.get('server_id', False) + srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id) + + template = 'public/status.html' + + self.render( + template, + data=page_data, + translate=self.translator.translate, + ) + def post(self): + page_data = {} + page_data['servers'] = db_helper.get_all_servers_stats() + for srv in page_data['servers']: + server_data = srv.get('server_data', False) + server_id = server_data.get('server_id', False) + srv['raw_ping_result'] = self.controller.stats.get_raw_server_stats(server_id) + + template = 'public/status.html' + + self.render( + template, + data=page_data, + translate=self.translator.translate, + ) \ No newline at end of file diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py index a82f619f..aeed856f 100644 --- a/app/classes/web/tornado.py +++ b/app/classes/web/tornado.py @@ -29,6 +29,7 @@ try: from app.classes.shared.translation import translation from app.classes.web.upload_handler import UploadHandler from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage + from app.classes.web.status_handler import StatusHandler except ModuleNotFoundError as e: logger.critical("Import Error: Unable to load {} module".format(e, e.name)) @@ -132,6 +133,7 @@ class Webserver: (r'/api/stats/node', NodeStats, handler_args), (r'/ws', SocketHandler, handler_args), (r'/upload', UploadHandler), + (r'/status', StatusHandler, handler_args) ] app = tornado.web.Application( diff --git a/app/frontend/static/assets/css/crafty.css b/app/frontend/static/assets/css/crafty.css index 892bd52f..c39b2c2a 100644 --- a/app/frontend/static/assets/css/crafty.css +++ b/app/frontend/static/assets/css/crafty.css @@ -80,3 +80,8 @@ body { background-color: var(--dark) !important; /* Firefox */ } .actions_serverlist > a > i { cursor: pointer; } +.corner { + position: absolute; + margin-top: 0; + margin-left: 0; +} \ No newline at end of file diff --git a/app/frontend/static/assets/images/logo_long.png b/app/frontend/static/assets/images/logo_long.png new file mode 100644 index 00000000..8ebca3a5 Binary files /dev/null and b/app/frontend/static/assets/images/logo_long.png differ diff --git a/app/frontend/static/assets/images/pack.png b/app/frontend/static/assets/images/pack.png new file mode 100644 index 00000000..d198156c Binary files /dev/null and b/app/frontend/static/assets/images/pack.png differ diff --git a/app/frontend/static/assets/js/motd.js b/app/frontend/static/assets/js/motd.js new file mode 100644 index 00000000..e90c5ebf --- /dev/null +++ b/app/frontend/static/assets/js/motd.js @@ -0,0 +1,127 @@ +var obfuscators = []; +var styleMap = { + '§0': 'color:#000000', + '§1': 'color:#0000AA', + '§2': 'color:#00AA00', + '§3': 'color:#00AAAA', + '§4': 'color:#AA0000', + '§5': 'color:#AA00AA', + '§6': 'color:#FFAA00', + '§7': 'color:#AAAAAA', + '§8': 'color:#555555', + '§9': 'color:#5555FF', + '§a': 'color:#55FF55', + '§b': 'color:#55FFFF', + '§c': 'color:#FF5555', + '§d': 'color:#FF55FF', + '§e': 'color:#FFFF55', + '§f': 'color:#FFFFFF', + '§l': 'font-weight:bold', + '§m': 'text-decoration:line-through', + '§n': 'text-decoration:underline', + '§o': 'font-style:italic', +}; +function obfuscate(string, elem) { + var magicSpan, + currNode; + if(string.indexOf('
') > -1) { + elem.innerHTML = string; + for(var j = 0, len = elem.childNodes.length; j < len; j++) { + currNode = elem.childNodes[j]; + if(currNode.nodeType === 3) { + magicSpan = document.createElement('span'); + magicSpan.innerHTML = currNode.nodeValue; + elem.replaceChild(magicSpan, currNode); + init(magicSpan); + } + } + } else { + init(elem, string); + } + function init(el, str) { + var i = 0, + obsStr = str || el.innerHTML, + len = obsStr.length; + obfuscators.push( window.setInterval(function () { + if(i >= len) i = 0; + obsStr = replaceRand(obsStr, i); + el.innerHTML = obsStr; + i++; + }, 0) ); + } + function randInt(min, max) { + return Math.floor( Math.random() * (max - min + 1) ) + min; + } + function replaceRand(string, i) { + var randChar = String.fromCharCode( randInt(64, 95) ); + return string.substr(0, i) + randChar + string.substr(i + 1, string.length); + } +} +function applyCode(string, codes) { + var elem = document.createElement('span'), + obfuscated = false; + string = string.replace(/\x00*/g, ''); + for(var i = 0, len = codes.length; i < len; i++) { + elem.style.cssText += styleMap[codes[i]] + ';'; + if(codes[i] === '§k') { + obfuscate(string, elem); + obfuscated = true; + } + } + if(!obfuscated) elem.innerHTML = string; + return elem; +} +function parseStyle(string) { + var codes = string.match(/§.{1}/g) || [], + indexes = [], + apply = [], + tmpStr, + deltaIndex, + noCode, + final = document.createDocumentFragment(), + i; + string = string.replace(/\n|\\n/g, '
'); + for(i = 0, len = codes.length; i < len; i++) { + indexes.push( string.indexOf(codes[i]) ); + string = string.replace(codes[i], '\x00\x00'); + } + if(indexes[0] !== 0) { + final.appendChild( applyCode( string.substring(0, indexes[0]), [] ) ); + } + for(i = 0; i < len; i++) { + indexDelta = indexes[i + 1] - indexes[i]; + if(indexDelta === 2) { + while(indexDelta === 2) { + apply.push ( codes[i] ); + i++; + indexDelta = indexes[i + 1] - indexes[i]; + } + apply.push ( codes[i] ); + } else { + apply.push( codes[i] ); + } + if( apply.lastIndexOf('§r') > -1) { + apply = apply.slice( apply.lastIndexOf('§r') + 1 ); + } + tmpStr = string.substring( indexes[i], indexes[i + 1] ); + final.appendChild( applyCode(tmpStr, apply) ); + } + return final; +} +function clearObfuscators() { + var i = obfuscators.length; + for(;i--;) { + clearInterval(obfuscators[i]); + } + obfuscators = []; +} +function initParser(input, output) { + clearObfuscators(); + var input = document.getElementById(input), + output = document.getElementById(output); + if (input != null && output != null) { + var parsed = parseStyle( input.innerHTML ); + output.innerHTML = ''; + output.appendChild(parsed); + } +} diff --git a/app/frontend/templates/blank_base.html b/app/frontend/templates/blank_base.html index ace4b7dd..f4e8ec65 100644 --- a/app/frontend/templates/blank_base.html +++ b/app/frontend/templates/blank_base.html @@ -11,6 +11,8 @@ + + @@ -24,7 +26,7 @@
-
+
{% block content %} {% end %} @@ -47,5 +49,11 @@ + + {% block js %} + + + {% end %} + \ No newline at end of file diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html index c40658d2..58c1f329 100644 --- a/app/frontend/templates/panel/panel_edit_user.html +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -184,7 +184,7 @@
-{% end %} + {% end %} diff --git a/app/frontend/templates/public/status.html b/app/frontend/templates/public/status.html new file mode 100644 index 00000000..abb766dd --- /dev/null +++ b/app/frontend/templates/public/status.html @@ -0,0 +1,82 @@ +{% extends ../public_base.html %} + +{% block meta %} + +{% end %} + +{% block title %}Crafty Controller - {{ translate('dashboard', 'dashboard') }}{% end %} + +{% block content %} + + +{% end %} + +{% block js %} + + + + +{% end %} \ No newline at end of file diff --git a/app/frontend/templates/public_base.html b/app/frontend/templates/public_base.html new file mode 100644 index 00000000..9fc4cd28 --- /dev/null +++ b/app/frontend/templates/public_base.html @@ -0,0 +1,59 @@ + + + + + + + {% block meta %}{% end %} + {% block title %}{{ _('Default') }}{% end %} + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ {% block content %} + {% end %} +
+
+
+
+ +
+ +
+ + + + + + + + + + + + + {% block js %} + + + {% end %} + + + \ No newline at end of file diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index ca50b6cc..e30907a8 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -70,6 +70,8 @@ "server": "Server", "actions": "Actions", "world": "World", + "motd": "MOTD", + "version": "Version", "status": "Status", "online": "Online", "offline": "Offline", diff --git a/app/translations/fi_FI.json b/app/translations/fi_FI.json index 1b07ddec..22fbfd2f 100644 --- a/app/translations/fi_FI.json +++ b/app/translations/fi_FI.json @@ -70,6 +70,8 @@ "server": "Palvelin", "actions": "Toiminnot", "world": "Maailma", + "motd": "MOTD", + "version": "Versio", "status": "Tila", "online": "Päällä", "offline": "Pois päältä", diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json index 7329c48d..dd6f1c10 100644 --- a/app/translations/fr_FR.json +++ b/app/translations/fr_FR.json @@ -70,6 +70,8 @@ "server": "Serveur", "actions": "Actions", "world": "Monde", + "motd": "MOTD", + "version": "Version", "status": "Statut", "online": "En Ligne", "offline": "Hors Ligne",