Use stdout for virtual terminal. WebSockets seem to be "laggy".

This commit is contained in:
luukas 2021-08-10 23:17:56 +03:00
parent 7b66cc261e
commit 4bac56e84a
10 changed files with 146 additions and 28 deletions

View File

@ -562,4 +562,10 @@ class Helpers:
os.path.relpath(os.path.join(root, file), os.path.relpath(os.path.join(root, file),
os.path.join(path, '..'))) os.path.join(path, '..')))
@staticmethod
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text
helper = Helpers() helper = Helpers()

View File

@ -10,11 +10,13 @@ import threading
import schedule import schedule
import logging.config import logging.config
import zipfile import zipfile
import html
from app.classes.shared.helpers import helper from app.classes.shared.helpers import helper
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.shared.models import db_helper, Servers from app.classes.shared.models import db_helper, Servers
from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -27,6 +29,58 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e.name)) console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1) sys.exit(1)
class ServerOutBuf:
lines = {}
def __init__(self, p, server_id):
self.p = p
self.server_id = str(server_id)
# Buffers text for virtual_terminal_lines config number of lines
self.max_lines = helper.get_setting('virtual_terminal_lines')
self.line_buffer = ''
ServerOutBuf.lines[self.server_id] = []
def check(self):
while self.p.isalive():
char = self.p.read(1)
if char == os.linesep:
ServerOutBuf.lines[self.server_id].append(self.line_buffer)
self.new_line_handler(self.line_buffer)
self.line_buffer = ''
# Limit list length to self.max_lines:
if len(ServerOutBuf.lines[self.server_id]) > self.max_lines:
ServerOutBuf.lines[self.server_id].pop(0)
else:
self.line_buffer += char
def new_line_handler(self, new_line):
console.debug('New line: {}'.format(new_line))
highlighted = helper.log_colors(html.escape(new_line))
print('broadcasting new vterm line')
websocket_helper.broadcast_page_params(
'/panel/server_detail',
{
'id': self.server_id
},
'notification',
'test test test'
)
# TODO: Do not send data to clients who do not have permission to view this server's console
websocket_helper.broadcast_page_params(
'/panel/server_detail',
{
'id': self.server_id
},
'vterm_new_line',
{
'line': highlighted + '<br />',
'server_id': self.server_id
}
)
class Server: class Server:
@ -127,7 +181,13 @@ class Server:
logger.info("Linux Detected") logger.info("Linux Detected")
logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command)) logger.info("Starting server in {p} with command: {c}".format(p=self.server_path, c=self.server_command))
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding=None)
self.process = pexpect.spawn(self.server_command, cwd=self.server_path, timeout=None, encoding='utf-8')
out_buf = ServerOutBuf(self.process, self.server_id)
console.cyan('Start vterm listener')
threading.Thread(target=out_buf.check, daemon=True).start()
self.is_crashed = False self.is_crashed = False
self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')) self.start_time = str(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))

View File

@ -196,8 +196,9 @@ class TasksManager:
host_stats = db_helper.get_latest_hosts_stats() host_stats = db_helper.get_latest_hosts_stats()
if len(websocket_helper.clients) > 0: if len(websocket_helper.clients) > 0:
print('there are clients')
# There are clients # There are clients
websocket_helper.broadcast('update_host_stats', { websocket_helper.broadcast_page('/panel/dashboard', 'update_host_stats', {
'cpu_usage': host_stats.get('cpu_usage'), 'cpu_usage': host_stats.get('cpu_usage'),
'cpu_cores': host_stats.get('cpu_cores'), 'cpu_cores': host_stats.get('cpu_cores'),
'cpu_cur_freq': host_stats.get('cpu_cur_freq'), 'cpu_cur_freq': host_stats.get('cpu_cur_freq'),
@ -206,9 +207,6 @@ class TasksManager:
'mem_usage': host_stats.get('mem_usage') 'mem_usage': host_stats.get('mem_usage')
}) })
time.sleep(4) time.sleep(4)
else:
# Stats are same
time.sleep(8)
def log_watcher(self): def log_watcher(self):
helper.check_for_old_logs(db_helper) helper.check_for_old_logs(db_helper)

View File

@ -12,6 +12,7 @@ from app.classes.shared.models import Users, installer
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
from app.classes.shared.models import db_helper from app.classes.shared.models import db_helper
from app.classes.shared.helpers import helper from app.classes.shared.helpers import helper
from app.classes.shared.server import ServerOutBuf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -56,16 +57,17 @@ class AjaxHandler(BaseHandler):
if not server_data: if not server_data:
logger.warning("Server Data not found in server_log ajax call") logger.warning("Server Data not found in server_log ajax call")
self.redirect("/panel/error?error=Server ID Not Found") self.redirect("/panel/error?error=Server ID Not Found")
return
if not server_data['log_path']: if not server_data['log_path']:
logger.warning("Log path not found in server_log ajax call ({})".format(server_id)) logger.warning("Log path not found in server_log ajax call ({})".format(server_id))
if full_log: if full_log:
log_lines = helper.get_setting('max_log_lines') log_lines = helper.get_setting('max_log_lines')
else:
log_lines = helper.get_setting('virtual_terminal_lines')
data = helper.tail_file(server_data['log_path'], log_lines) data = helper.tail_file(server_data['log_path'], log_lines)
else:
data = ServerOutBuf.lines.get(server_id, [])
for d in data: for d in data:
try: try:

View File

@ -1,9 +1,10 @@
import json import json
import logging import logging
from urllib.parse import parse_qsl
import tornado.websocket import tornado.websocket
from app.classes.shared.console import console
from app.classes.shared.models import Users, db_helper from app.classes.shared.models import Users, db_helper
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper from app.classes.web.websocket_helper import websocket_helper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -35,6 +36,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self): def open(self):
logger.debug('Checking WebSocket authentication')
if self.check_auth(): if self.check_auth():
self.handle() self.handle()
else: else:
@ -42,10 +44,15 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
self.close() self.close()
db_helper.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip()) db_helper.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip())
websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication') websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication')
logger.warning('Someone tried to connect via WebSocket without proper authentication')
def handle(self): def handle(self):
self.page = self.get_query_argument('page')
websocket_helper.addClient(self) self.page_query_params = dict(parse_qsl(helper.remove_prefix(
self.get_query_argument('page_query_params'),
'?'
)))
websocket_helper.add_client(self)
logger.debug('Opened WebSocket connection') logger.debug('Opened WebSocket connection')
# websocket_helper.broadcast('notification', 'New client connected') # websocket_helper.broadcast('notification', 'New client connected')
@ -56,7 +63,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data'])) logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
def on_close(self): def on_close(self):
websocket_helper.removeClient(self) websocket_helper.remove_client(self)
logger.debug('Closed WebSocket connection') logger.debug('Closed WebSocket connection')
# websocket_helper.broadcast('notification', 'Client disconnected') # websocket_helper.broadcast('notification', 'Client disconnected')

View File

@ -6,25 +6,59 @@ from app.classes.shared.console import console
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WebSocketHelper: class WebSocketHelper:
clients = set() def __init__(self):
self.clients = set()
def addClient(self, client): def add_client(self, client):
self.clients.add(client) self.clients.add(client)
def removeClient(self, client): def remove_client(self, client):
self.clients.add(client) self.clients.remove(client)
def send_message(self, client, event_type, data): def send_message(self, client, event_type: str, data):
if client.check_auth(): if client.check_auth():
message = str(json.dumps({'event': event_type, 'data': data})) message = str(json.dumps({'event': event_type, 'data': data}))
client.write_message(message) client.write_message(message)
def broadcast(self, event_type, data): def broadcast(self, event_type: str, data):
logger.debug('Sending: ' + str(json.dumps({'event': event_type, 'data': data}))) logger.debug('Sending to {} clients: {}'.format(len(self.clients), json.dumps({'event': event_type, 'data': data})))
for client in self.clients: for client in self.clients:
try: try:
self.send_message(client, event_type, data) self.send_message(client, event_type, data)
except: except Exception:
pass
def broadcast_page(self, page: str, event_type: str, data):
def filter_fn(client):
return client.page == page
clients = list(filter(filter_fn, self.clients))
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
for client in clients:
try:
self.send_message(client, event_type, data)
except Exception:
pass
def broadcast_page_params(self, page: str, params: dict, event_type: str, data):
def filter_fn(client):
if client.page != page:
return False
for key, param in params.items():
if param != client.page_query_params.get(key, None):
return False
return True
clients = list(filter(filter_fn, self.clients))
logger.debug('Sending to {} out of {} clients: {}'.format(len(clients), len(self.clients), json.dumps({'event': event_type, 'data': data})))
for client in clients:
try:
self.send_message(client, event_type, data)
except Exception:
pass pass
def disconnect_all(self): def disconnect_all(self):

View File

@ -10,7 +10,7 @@
"stats_update_frequency": 30, "stats_update_frequency": 30,
"delete_default_json": false, "delete_default_json": false,
"show_contribute_link": true, "show_contribute_link": true,
"virtual_terminal_lines": 10, "virtual_terminal_lines": 30,
"max_log_lines": 700, "max_log_lines": 700,
"keywords": ["help", "chunk"] "keywords": ["help", "chunk"]
} }

View File

@ -173,8 +173,9 @@
let listenEvents = []; let listenEvents = [];
try { try {
pageQueryParams = 'page_query_params=' + encodeURIComponent(location.search)
var wsInternal = new WebSocket('wss://' + location.host + '/ws'); page = 'page=' + encodeURIComponent(location.pathname)
var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + page + '&' + pageQueryParams);
wsInternal.onopen = function() { wsInternal.onopen = function() {
console.log('opened WebSocket connection:', wsInternal) console.log('opened WebSocket connection:', wsInternal)
}; };

View File

@ -161,6 +161,12 @@
} }
} }
function new_line_handler(data) {
if (server_id === data.server_id) {
$('#virt_console').append(data.line)
}
}
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
@ -171,9 +177,13 @@
console.log( "ready!" ); console.log( "ready!" );
get_server_log() get_server_log()
if (webSocket) {
webSocket.on('vterm_new_line', new_line_handler)
} else {
setInterval(function(){ setInterval(function(){
get_server_log() // this will run after every 5 seconds get_server_log() // this will run after every 5 seconds
}, 1500); }, 1500);
}
}); });
$('#server_command').on('keydown', function (e) { $('#server_command').on('keydown', function (e) {