mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 20:01:33 +00:00
Merge pull request #17 from avatao-content/terminado_commands
Implement terminal manipulation via a new EventHandler
This commit is contained in:
commit
6db4c80671
14
Dockerfile
14
Dockerfile
@ -60,7 +60,18 @@ ENV PYTHONPATH="/usr/local/lib/" \
|
||||
TFW_LIB_DIR="/usr/local/lib/" \
|
||||
TFW_CONTROLLER_DIR="/srv/controller" \
|
||||
TFW_TERMINADO_DIR="/tmp/terminado_server" \
|
||||
TFW_FRONTEND_DIR="/srv/frontend"
|
||||
TFW_FRONTEND_DIR="/srv/frontend" \
|
||||
TFW_HISTFILE="/home/${AVATAO_USER}/.bash_history" \
|
||||
PROMPT_COMMAND="history -a"
|
||||
|
||||
RUN echo "shopt -s cmdhist\n" \
|
||||
"shopt -s histappend\n" \
|
||||
"unset HISTCONTROL\n" \
|
||||
"export HISTFILE=$TFW_HISTFILE\n" \
|
||||
"export HISTFILESIZE=1000\n" \
|
||||
"export HISTSIZE=1000\n" \
|
||||
'PROMPT_COMMAND="history -a"\n' \
|
||||
>> /home/${AVATAO_USER}/.bashrc
|
||||
|
||||
COPY nginx/nginx.conf ${TFW_NGINX_CONF}
|
||||
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
||||
@ -74,7 +85,6 @@ COPY supervisor/supervisord.conf ${TFW_SUPERVISORD_CONF}
|
||||
COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS}
|
||||
COPY lib ${TFW_LIB_DIR}
|
||||
COPY src/controller ${TFW_CONTROLLER_DIR}
|
||||
COPY lib/tfw/components/terminado_mini_server.py ${TFW_TERMINADO_DIR}/
|
||||
|
||||
### TFW internals ^ ### DEMO v ###############################################################
|
||||
|
||||
|
@ -13,6 +13,7 @@ class WebideReloadEventHandler(FileSystemEventHandler):
|
||||
super().__init__()
|
||||
self.uplink = ServerUplinkConnector()
|
||||
self._paused = False
|
||||
self.ignore = 0
|
||||
|
||||
def pause(self):
|
||||
self._paused = True
|
||||
@ -23,6 +24,9 @@ class WebideReloadEventHandler(FileSystemEventHandler):
|
||||
@RateLimiter(rate_per_second=5)
|
||||
def on_modified(self, event):
|
||||
if self._paused: return
|
||||
if self.ignore > 0:
|
||||
self.ignore = self.ignore - 1
|
||||
return
|
||||
log.debug(event)
|
||||
key = 'webide'
|
||||
self.uplink.send(key, {'data': {'command': 'reload'}})
|
||||
@ -42,6 +46,14 @@ class DirectoryMonitor:
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
|
||||
@property
|
||||
def ignore(self):
|
||||
return self.eventhandler.ignore
|
||||
|
||||
@ignore.setter
|
||||
def ignore(self, value):
|
||||
self.ignore = value if value >= 0 else 0
|
||||
|
||||
@property
|
||||
def pauser(self):
|
||||
return DirectoryMonitor.Pauser(self)
|
||||
|
51
lib/tfw/components/history_monitor.py
Normal file
51
lib/tfw/components/history_monitor.py
Normal file
@ -0,0 +1,51 @@
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
from os.path import dirname
|
||||
|
||||
|
||||
class CallbackEventHandler(PatternMatchingEventHandler):
|
||||
def __init__(self, files, *callbacks):
|
||||
super().__init__(files)
|
||||
self.callbacks = callbacks
|
||||
|
||||
def on_modified(self, event):
|
||||
for callback in self.callbacks:
|
||||
callback()
|
||||
|
||||
|
||||
class HistoryMonitor:
|
||||
def __init__(self, histfile):
|
||||
self.histfile = histfile
|
||||
self._history = []
|
||||
self._last_length = len(self._history)
|
||||
self._callbacks = []
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(CallbackEventHandler([self.histfile],
|
||||
self._fetch_history,
|
||||
self._invoke_callbacks),
|
||||
dirname(self.histfile))
|
||||
|
||||
@property
|
||||
def history(self):
|
||||
return self._history
|
||||
|
||||
@property
|
||||
def callbacks(self):
|
||||
return self._callbacks
|
||||
|
||||
def _fetch_history(self):
|
||||
self._last_length = len(self._history)
|
||||
with open(self.histfile, 'r') as ifile:
|
||||
self._history = [line.rstrip() for line in ifile.readlines()]
|
||||
|
||||
def _invoke_callbacks(self):
|
||||
if self._last_length < len(self._history):
|
||||
for callback in self.callbacks:
|
||||
callback(self.history)
|
||||
|
||||
def watch(self):
|
||||
self.observer.start()
|
||||
|
||||
def stop(self):
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
@ -68,9 +68,9 @@ class SourceCodeEventHandler(TriggerlessEventHandler):
|
||||
return data
|
||||
|
||||
def write(self, data):
|
||||
with self.monitor.pauser:
|
||||
try: self.filemanager.file_contents = data['content']
|
||||
except Exception: log.exception('Error writing file!')
|
||||
self.monitor.eventhandler.ignore = 1
|
||||
try: self.filemanager.file_contents = data['content']
|
||||
except Exception: log.exception('Error writing file!')
|
||||
del data['content']
|
||||
return data
|
||||
|
||||
|
@ -1,17 +1,38 @@
|
||||
from tfw.components.terminado_mini_server import TerminadoMiniServer
|
||||
from tfw.components.history_monitor import HistoryMonitor
|
||||
from tfw.event_handler_base import TriggerlessEventHandler
|
||||
from tfw.components.mixins import SupervisorMixin
|
||||
from tfw.config import tfwenv
|
||||
from tfw.config.logs import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TerminadoEventHandler(TriggerlessEventHandler, SupervisorMixin):
|
||||
def __init__(self, key, process_name):
|
||||
class TerminadoEventHandler(TriggerlessEventHandler):
|
||||
def __init__(self, key):
|
||||
super().__init__(key)
|
||||
self.working_directory = tfwenv.TERMINADO_DIR
|
||||
self.process_name = process_name
|
||||
self.start_process(self.process_name)
|
||||
self._historymonitor = HistoryMonitor(tfwenv.HISTFILE)
|
||||
self.terminado_server = TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash'])
|
||||
self.commands = {'write': self.write,
|
||||
'read': self.read}
|
||||
self._historymonitor.watch()
|
||||
self.terminado_server.listen()
|
||||
|
||||
@property
|
||||
def historymonitor(self):
|
||||
return self._historymonitor
|
||||
|
||||
def handle_event(self, key, data_json):
|
||||
log.debug('TerminadoEventHandler received event for key {}'.format(key))
|
||||
# TODO: wat do?
|
||||
log.debug('TerminadoEventHandler received event: {}'.format(data_json))
|
||||
data_json['data'] = self.commands[data_json['data']['command']](data_json['data'])
|
||||
return data_json
|
||||
|
||||
def write(self, data):
|
||||
self.terminado_server.pty.write(data['shellcmd'])
|
||||
|
||||
def read(self, data):
|
||||
data['count'] = int(data.get('count', 1))
|
||||
data['history'] = self.historymonitor.history[-data['count']:]
|
||||
return data
|
||||
|
||||
def cleanup(self):
|
||||
self.historymonitor.stop()
|
||||
|
@ -1,6 +1,6 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import Application
|
||||
from terminado import TermSocket, UniqueTermManager
|
||||
from terminado import TermSocket, SingleTermManager
|
||||
|
||||
from tfw.config import tfwenv
|
||||
from tfw.config.logs import logging
|
||||
@ -10,24 +10,37 @@ log = logging.getLogger(__name__)
|
||||
class TerminadoMiniServer:
|
||||
def __init__(self, url, port, workdir, shellcmd):
|
||||
self.port = port
|
||||
self._term_manager = SingleTermManager(shell_command=shellcmd,
|
||||
term_settings={'cwd': workdir})
|
||||
self.application = Application(
|
||||
[(
|
||||
url,
|
||||
TerminadoMiniServer.CORSTermSocket,
|
||||
{'term_manager': UniqueTermManager(shell_command=shellcmd,
|
||||
term_settings={'cwd': workdir})}
|
||||
TerminadoMiniServer.ResetterTermSocket,
|
||||
{'term_manager': self._term_manager}
|
||||
)]
|
||||
)
|
||||
|
||||
class CORSTermSocket(TermSocket):
|
||||
@property
|
||||
def term_manager(self):
|
||||
return self._term_manager
|
||||
|
||||
@property
|
||||
def pty(self):
|
||||
return self.term_manager.terminal.ptyproc
|
||||
|
||||
class ResetterTermSocket(TermSocket):
|
||||
def check_origin(self, origin):
|
||||
return True
|
||||
|
||||
def on_close(self):
|
||||
self.term_manager.terminal = None
|
||||
self.term_manager.get_terminal()
|
||||
|
||||
def listen(self):
|
||||
self.application.listen(self.port)
|
||||
IOLoop.instance().start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT))
|
||||
TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash']).listen()
|
||||
IOLoop.instance().start()
|
||||
|
@ -1,14 +1,17 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from tfw.components.source_code_event_handler import SourceCodeEventHandler
|
||||
from tfw.components.terminado_event_handler import TerminadoEventHandler
|
||||
from tfw.components.process_managing_event_handler import ProcessManagingEventHandler
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from tfw.config import tfwenv
|
||||
from tfw.config.logs import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ide = SourceCodeEventHandler('webide', tfwenv.WEBIDE_WD)
|
||||
terminado = TerminadoEventHandler('terminado', 'terminado')
|
||||
terminado = TerminadoEventHandler('shell')
|
||||
terminado.historymonitor.callbacks.append(lambda hist: log.debug('User executed command: "{}"'.format(hist[-1])))
|
||||
processmanager = ProcessManagingEventHandler('processmanager', ide.monitor)
|
||||
|
||||
eventhandlers = {ide, terminado, processmanager}
|
||||
|
@ -1,4 +0,0 @@
|
||||
[program:terminado]
|
||||
directory=%(ENV_TFW_TERMINADO_DIR)s
|
||||
command=env python terminado_mini_server.py
|
||||
autostart=false
|
Loading…
Reference in New Issue
Block a user