Compare commits

...
16 Commits
Author SHA1 Message Date
R. Richard dc5e26843a Refactor MessageSender 2020-06-24 13:44:07 +02:00
Gergo Turcsanyi e5d0e91e93 Fix message timing on empty queue 2020-05-29 12:01:37 +02:00
Gergo Turcsanyi e58f0b1d04 Update inotify paths on pattern change 2020-05-29 11:57:54 +02:00
R. Richard ea0fff5662 Remove the custom on_close event to keep the session alive 2020-05-21 17:45:03 +02:00
R. Richard 70f4d666e3 Keep websocket connection alive 2020-05-18 18:02:07 +02:00
R. Richard dc76f1b732 Fix broken ZMQ messages 2020-05-07 09:47:35 +02:00
R. Richard f374cb7e46 Look up every module in the package 2020-02-03 16:45:42 +01:00
Kristóf Tóth 9481e8f921 Add support for stopping/starting ProcessLogHandler from API 2019-12-19 16:21:15 +01:00
Kristóf Tóth c8d4080ef5 Fix fsm.update messages being thiggered without a state change 2019-12-19 13:58:48 +01:00
Kristóf Tóth 269cab691e Add priorities to TFW processes 2019-11-21 14:23:34 +01:00
Kristóf Tóth 16f0335d76 Make /etc/supervisor a writable volume 2019-11-13 12:52:20 +01:00
R. Richard 52280acf41 Check for a generic Mapping type instead of dict 2019-11-12 14:23:09 +01:00
Kristóf Tóth 1b274fa019 Avoid mixing up terminal.write command with user input 2019-11-08 11:34:15 +01:00
Kristóf Tóth ba4803d660 Fix race condition in FrontendConfigHandler (frontend.ready) 2019-10-31 15:23:55 +01:00
Kristóf Tóth ac198e5731 Avoid 'Update frontend' commits with Dockerfile build-args 2019-10-31 15:17:43 +01:00
Kristóf Tóth 135760854e Update frontend 2019-10-21 16:09:03 +02:00
15 changed files with 76 additions and 41 deletions
+6 -2
View File
@@ -1,5 +1,9 @@
FROM avatao/frontend-tutorial-framework:chausie-20191014 as frontend ARG FRONTEND_VERSION
FROM avatao/frontend-tutorial-framework:${FRONTEND_VERSION} as frontend
FROM avatao/debian:buster FROM avatao/debian:buster
ARG FRONTEND_VERSION
LABEL tfw.frontend.version=${FRONTEND_VERSION}
RUN apt-get update &&\ RUN apt-get update &&\
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
@@ -69,7 +73,7 @@ ONBUILD COPY ${BUILD_CONTEXT}/supervisor/ ${TFW_SUPERVISORD_COMPONENTS}
ONBUILD RUN for f in "${TFW_NGINX_DEFAULT}" ${TFW_NGINX_COMPONENTS}/*.conf; do \ ONBUILD RUN for f in "${TFW_NGINX_DEFAULT}" ${TFW_NGINX_COMPONENTS}/*.conf; do \
envsubst "$(printenv | cut -d= -f1 | grep TFW_ | sed -e 's/^/$/g')" < $f > $f~ && mv $f~ $f ;\ envsubst "$(printenv | cut -d= -f1 | grep TFW_ | sed -e 's/^/$/g')" < $f > $f~ && mv $f~ $f ;\
done done
ONBUILD VOLUME ["/etc/nginx", "/var/lib/nginx", "/var/log/nginx", "${TFW_LIB_DIR}/tfw"] ONBUILD VOLUME ["/etc/nginx", "/etc/supervisor", "/var/lib/nginx", "/var/log/nginx", "${TFW_LIB_DIR}/tfw"]
ENTRYPOINT ["/bin/init", "--"] ENTRYPOINT ["/bin/init", "--"]
CMD exec supervisord --nodaemon --configuration ${TFW_SUPERVISORD_CONF} CMD exec supervisord --nodaemon --configuration ${TFW_SUPERVISORD_CONF}
+2 -2
View File
@@ -1,6 +1,6 @@
from os.path import dirname, realpath, join from os.path import dirname, realpath, join
from setuptools import setup from setuptools import setup, find_packages
here = dirname(realpath(__file__)) here = dirname(realpath(__file__))
@@ -17,7 +17,7 @@ setup(
author='Avatao.com Innovative Learning Kft.', author='Avatao.com Innovative Learning Kft.',
author_email='support@avatao.com', author_email='support@avatao.com',
license='custom', license='custom',
packages=['tfw'], packages=find_packages(),
package_dir={'tfw': 'tfw'}, package_dir={'tfw': 'tfw'},
install_requires=requirements, install_requires=requirements,
extras_require={ extras_require={
+1
View File
@@ -4,3 +4,4 @@ directory=/tmp
command=bash tfw_init.sh command=bash tfw_init.sh
autorestart=false autorestart=false
startsecs=0 startsecs=0
priority=1
+1
View File
@@ -2,3 +2,4 @@
user=root user=root
directory=%(ENV_TFW_SERVER_DIR)s directory=%(ENV_TFW_SERVER_DIR)s
command=python3 -u tfw_server.py command=python3 -u tfw_server.py
priority=2
@@ -12,7 +12,7 @@ class FrontendConfigHandler:
def handle_event(self, _, connector): def handle_event(self, _, connector):
# pylint: disable=no-self-use # pylint: disable=no-self-use
for message in self._config_messages: for message in self._config_messages:
connector.send_message(message) connector.send_message(message, scope=Scope.WEBSOCKET)
connector.send_message({'key': 'frontend.ready'}, scope=Scope.WEBSOCKET) connector.send_message({'key': 'frontend.ready'}, scope=Scope.WEBSOCKET)
@property @property
@@ -27,14 +27,15 @@ class MessageQueueHandler:
def _dispatch_messages(self): def _dispatch_messages(self):
for message in iter(self._queue.get, None): for message in iter(self._queue.get, None):
message['typing'] = not self._queue.empty() is_queue_empty = self._queue.empty()
message['typing'] = not is_queue_empty
self.connector.send_message(message) self.connector.send_message(message)
self._sleep(self._get_sleep_time(message)) self._sleep(self._get_sleep_time(message, is_queue_empty))
def _get_sleep_time(self, message): def _get_sleep_time(self, message, is_queue_empty):
words_per_min = message['wpm'] if 'wpm' in message else self.wpm words_per_min = message['wpm'] if 'wpm' in message else self.wpm
chars_per_min = self.avg_word_len * words_per_min / 60 chars_per_min = self.avg_word_len * words_per_min / 60
return len(message['message']) / chars_per_min return 0 if is_queue_empty else len(message['message']) / chars_per_min
def _sleep(self, seconds): def _sleep(self, seconds):
poll_freq = self.drain_poll_freq poll_freq = self.drain_poll_freq
+9 -8
View File
@@ -1,17 +1,22 @@
DEFAULT_ORIGINATOR = 'avataobot'
class MessageSender: class MessageSender:
def __init__(self, uplink): def __init__(self, uplink):
self.uplink = uplink self.uplink = uplink
def send(self, message, originator=None): def send(self, message, originator=DEFAULT_ORIGINATOR, buttons=None):
message = { message = {
'key': 'message.send', 'key': 'message.send',
'message': message 'message': message
} }
if originator: if originator:
message['originator'] = originator message['originator'] = originator
if buttons:
message['buttons'] = buttons
self.uplink.send_message(message) self.uplink.send_message(message)
def queue_messages(self, messages, originator=None): def queue_messages(self, messages, originator=DEFAULT_ORIGINATOR, buttons=None):
message_queue = { message_queue = {
'key': 'message.queue', 'key': 'message.queue',
'messages': [] 'messages': []
@@ -21,10 +26,6 @@ class MessageSender:
if originator: if originator:
next_message['originator'] = originator next_message['originator'] = originator
message_queue['messages'].append(next_message) message_queue['messages'].append(next_message)
if buttons and message_queue['messages']:
message_queue['messages'][-1]['buttons'] = buttons
self.uplink.send_message(message_queue) self.uplink.send_message(message_queue)
def set_originator(self, originator):
self.uplink.send_message({
'key': 'message.config',
'originator': originator
})
+1
View File
@@ -79,6 +79,7 @@ class IdeHandler:
def read(self, message): def read(self, message):
if 'patterns' in message: if 'patterns' in message:
self.filemanager.patterns = message['patterns'] self.filemanager.patterns = message['patterns']
self.monitor.paths = self.filemanager.parents
try: try:
message['filename'] = self.filemanager.find_file( message['filename'] = self.filemanager.find_file(
message.get('filename') or self.initial_file message.get('filename') or self.initial_file
@@ -16,10 +16,13 @@ class ProcessLogHandler:
self._initial_log_tail = log_tail self._initial_log_tail = log_tail
self.command_handlers = { self.command_handlers = {
'process.log.set': self.handle_set 'process.log.set': self.handle_set,
'process.log.start': self.handle_start,
'process.log.stop': self.handle_stop
} }
def start(self): def start(self):
if not self._monitor:
self._monitor = LogInotifyObserver( self._monitor = LogInotifyObserver(
connector=self.connector, connector=self.connector,
process_name=self.process_name, process_name=self.process_name,
@@ -40,5 +43,16 @@ class ProcessLogHandler:
if data.get('tail'): if data.get('tail'):
self._monitor.log_tail = data['tail'] self._monitor.log_tail = data['tail']
def cleanup(self): def handle_start(self, _):
self.start()
def handle_stop(self, _):
self._stop_monitor()
def _stop_monitor(self):
if self._monitor:
self._monitor.stop() self._monitor.stop()
self._monitor = None
def cleanup(self):
self._stop_monitor()
@@ -17,7 +17,7 @@ class TerminadoMiniServer:
url, url,
TerminadoMiniServer.ResetterTermSocket, TerminadoMiniServer.ResetterTermSocket,
{'term_manager': self._term_manager} {'term_manager': self._term_manager}
)]) )], websocket_ping_interval=30)
@property @property
def term_manager(self): def term_manager(self):
@@ -33,10 +33,6 @@ class TerminadoMiniServer:
def check_origin(self, origin): def check_origin(self, origin):
return True return True
def on_close(self):
self.term_manager.terminal = None
self.term_manager.get_terminal()
def listen(self): def listen(self):
self.application.listen(self.port) self.application.listen(self.port)
@@ -42,6 +42,9 @@ class TerminalHandler:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
def handle_write(self, message): def handle_write(self, message):
concat = message.get('concat', False)
if not concat:
self.terminado_server.pty.write('\x15')
self.terminado_server.pty.write(message['command']) self.terminado_server.pty.write(message['command'])
def cleanup(self): def cleanup(self):
+3 -4
View File
@@ -1,6 +1,7 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from contextlib import suppress
from transitions import Machine, MachineError from transitions import Machine, MachineError
@@ -62,14 +63,12 @@ class FSMBase(Machine, CallbackMixin):
) )
if all(predicate_results): if all(predicate_results):
try: with suppress(AttributeError, MachineError):
from_state = self.state from_state = self.state
self.trigger(trigger) if self.trigger(trigger):
self.update_event_log(from_state, trigger) self.update_event_log(from_state, trigger)
return True return True
except (AttributeError, MachineError):
LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger) LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger)
return False
def update_event_log(self, from_state, trigger): def update_event_log(self, from_state, trigger):
self.event_log.append({ self.event_log.append({
+14 -1
View File
@@ -42,7 +42,8 @@ def deserialize_tfw_msg(*args):
""" """
Return message from TFW multipart data Return message from TFW multipart data
""" """
return _deserialize_all(*args)[1] envelope = _deserialize_all(*args)
return _repair_if_needed(envelope)
def _serialize_all(*args): def _serialize_all(*args):
@@ -84,6 +85,18 @@ def _deserialize_single(data):
return _decode_if_needed(data) return _decode_if_needed(data)
def _repair_if_needed(envelope):
"""
Quick fix for broken messages received from separate processes.
"""
if len(envelope) == 2:
return envelope[1]
for part in envelope:
if isinstance(part, dict):
return part
return {}
def _encode_if_needed(value): def _encode_if_needed(value):
""" """
Return input as bytes Return input as bytes
+2 -1
View File
@@ -2,6 +2,7 @@
from datetime import datetime from datetime import datetime
from typing import TextIO, Union from typing import TextIO, Union
from dataclasses import dataclass from dataclasses import dataclass
from collections.abc import Mapping
from traceback import format_exception from traceback import format_exception
from logging import DEBUG, getLogger, Handler, Formatter, Filter from logging import DEBUG, getLogger, Handler, Formatter, Filter
@@ -81,7 +82,7 @@ class LogFormatter(Formatter):
def format(self, record): def format(self, record):
time = datetime.utcfromtimestamp(record.created).strftime('%H:%M:%S') time = datetime.utcfromtimestamp(record.created).strftime('%H:%M:%S')
if record.args: if record.args:
tuple_args = (record.args,) if isinstance(record.args, dict) else record.args tuple_args = (record.args,) if isinstance(record.args, Mapping) else record.args
clean_args = tuple((self.trim(arg) for arg in tuple_args)) clean_args = tuple((self.trim(arg) for arg in tuple_args))
message = record.msg % clean_args message = record.msg % clean_args
else: else:
+1 -1
View File
@@ -28,7 +28,7 @@ class TFWServer:
r'/ws', ZMQWebSocketRouter, { r'/ws', ZMQWebSocketRouter, {
'listener': self._listener, 'listener': self._listener,
} }
)]) )], websocket_ping_interval=30)
def listen(self): def listen(self):
self.application.listen(TFWENV.WEB_PORT) self.application.listen(TFWENV.WEB_PORT)