diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 237d850c..e24e08ce 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -8,6 +8,7 @@ import threading from app.classes.shared.helpers import helper from app.classes.shared.console import console from app.classes.web.tornado import webserver +from app.classes.web.websocket_handler import WebSocketHandler from app.classes.minecraft.stats import stats from app.classes.shared.controller import controller @@ -24,6 +25,7 @@ except ModuleNotFoundError as e: console.critical("Import Error: Unable to load {} module".format(e, e.name)) sys.exit(1) + class TasksManager: def __init__(self): @@ -38,6 +40,9 @@ class TasksManager: self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher") self.command_thread.start() + self.realtime_thread = threading.Thread(target=self.realtime_thread, daemon=True, name="realtime") + self.realtime_thread.start() + def get_main_thread_run_status(self): return self.main_thread_exiting @@ -72,10 +77,8 @@ class TasksManager: db_helper.mark_command_complete(c.get('command_id', None)) - time.sleep(1) - def _main_graceful_exit(self): try: os.remove(helper.session_file) @@ -133,5 +136,33 @@ class TasksManager: logger.info("Scheduling Serverjars.com cache refresh service every 12 hours") schedule.every(12).hours.do(server_jar_obj.refresh_cache) + @staticmethod + def realtime_thread(): + console.debug('realtime zero') + while True: + if len(WebSocketHandler.connections) > 0: + print(WebSocketHandler) + WebSocketHandler.broadcast(WebSocketHandler, 'sample_data', { + 'foo': 'bar', + 'baz': 'Hello, World!' + }) + + if WebSocketHandler.host_stats.get('cpu_usage') != \ + db_helper.get_latest_hosts_stats().get('cpu_usage') or \ + WebSocketHandler.host_stats.get('mem_percent') != \ + db_helper.get_latest_hosts_stats().get('mem_percent'): + + console.debug('realtime one') + WebSocketHandler.host_stats = db_helper.get_latest_hosts_stats() + if len(WebSocketHandler.connections) > 0: + WebSocketHandler.broadcast(WebSocketHandler, 'update_host_stats', { + 'cpu': WebSocketHandler.host_stats.get('cpu_usage'), + 'mem': WebSocketHandler.host_stats.get('mem_percent') + }) + time.sleep(4) + else: + console.debug('realtime two') + time.sleep(2) + tasks_manager = TasksManager() diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 873ce834..75d2145b 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -65,6 +65,9 @@ class PanelHandler(BaseHandler): elif page == 'files_menu': template = "panel/files_menu.html" + elif page == 'websocket': + template = "panel/websocket.html" + elif page == "remove_server": server_id = self.get_argument('id', None) server_data = controller.get_server_data(server_id) diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py index 03b0a97f..7867cad4 100644 --- a/app/classes/web/tornado.py +++ b/app/classes/web/tornado.py @@ -24,6 +24,7 @@ try: from app.classes.web.server_handler import ServerHandler from app.classes.web.ajax_handler import AjaxHandler from app.classes.web.api_handler import ServersStats, NodeStats + from app.classes.web.websocket_handler import WebSocketHandler except ModuleNotFoundError as e: logger.critical("Import Error: Unable to load {} module".format(e, e.name)) @@ -125,6 +126,7 @@ class webserver: (r'/ajax/(.*)', AjaxHandler), (r'/api/stats/servers', ServersStats), (r'/api/stats/node', NodeStats), + (r'/ws', WebSocketHandler), ] app = tornado.web.Application( diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py new file mode 100644 index 00000000..1c3ff38c --- /dev/null +++ b/app/classes/web/websocket_handler.py @@ -0,0 +1,37 @@ +import json + +import tornado.websocket +from app.classes.shared.console import console +from app.classes.shared.models import db_helper + + +class WebSocketHandler(tornado.websocket.WebSocketHandler): + connections = set() + host_stats = db_helper.get_latest_hosts_stats() + + def open(self): + self.connections.add(self) + console.debug('Opened WebSocket connection') + self.broadcast('client_joined', { + 'foo': 'bar', + }) + + def on_message(self, message): + # broadcast + # for client in self.connections: + # client.write_message(message) + + # send message to client this message was sent by + # self.write_message + + console.debug('Got message from WebSocket connection {}'.format(message)) + + def on_close(self): + self.connections.remove(self) + console.debug('Closed WebSocket connection') + + def broadcast(self, message_type: str, data): + print(str(json.dumps({'type': message_type, 'data': data}))) + message = str(json.dumps({'type': message_type, 'data': data})) + for client in self.connections: + client.write_message(message) diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 877de0c4..0499c104 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -58,6 +58,8 @@
+
+ {% block content %} {% end %} @@ -103,6 +105,82 @@ }); }); }); + + {% if request.protocol == 'https'%} + let usingWebSockets = true; + + let socketTypes = []; + + try { + + var wsInternal = new WebSocket('wss://' + location.host + '/ws'); + wsInternal.onopen = function() { + console.log('opened WebSocket connection:', wsInternal) + }; + wsInternal.onmessage = function (event) { + var message = JSON.parse(event.data); + + console.log('got message: ', message) + + socketTypes + .filter(event => event.type == message.type) + .forEach(event => event.callback(message.data)) + } + + + webSocket = { + on: function (type, callback) { + socketTypes.push({ type: type, callback: callback }) + }, + emit: function (type, data) { + var message = { + type: type, + data: data + } + + wsInternal.send(JSON.stringify(message)); + } + } + } catch (error) { + console.error('Error while making websocket helpers', error); + usingWebSockets = false; + } + {% else %} + let usingWebSockets = false; + warn('WebSockets are not supported in Crafty if not using the https protocol') + {% end%} + + function warn(message) { + var closeEl = document.createElement('span'); + var strongEL = document.createElement('strong'); + var msgEl = document.createElement('div'); + + closeEl.innerHTML = '×'; + strongEL.textContent = 'Warning: '; + msgEl.append(strongEL, message); + + + closeEl.style.marginLeft = '15px'; + closeEl.style.fontWeight = 'bold'; + closeEl.style.float = 'right'; + closeEl.style.fontSize = '22px'; + closeEl.style.lineHeight = '20px'; + closeEl.style.cursor = 'pointer'; + closeEl.style.transition = '.3s'; + + closeEl.addEventListener('click', function () {this.parentElement.style.display='none';}); + + var parentEl = document.createElement('div'); + + parentEl.style.padding = '20px'; + parentEl.style.backgroundColor = '#f7970f'; + + parentEl.appendChild(closeEl); + parentEl.appendChild(msgEl); + + document.querySelector('.warnings').appendChild(parentEl); + } + {% block js %} diff --git a/app/frontend/templates/panel/websocket.html b/app/frontend/templates/panel/websocket.html new file mode 100644 index 00000000..efaa6fbd --- /dev/null +++ b/app/frontend/templates/panel/websocket.html @@ -0,0 +1,63 @@ + + + + + + + Default + + + + + + + \ No newline at end of file