mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Use stdout for virtual terminal. WebSockets seem to be "laggy".
This commit is contained in:
parent
7b66cc261e
commit
4bac56e84a
@ -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()
|
||||||
|
@ -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'))
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
@ -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)
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user