mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 19:11:32 +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_LIB_DIR="/usr/local/lib/" \
|
||||||
TFW_CONTROLLER_DIR="/srv/controller" \
|
TFW_CONTROLLER_DIR="/srv/controller" \
|
||||||
TFW_TERMINADO_DIR="/tmp/terminado_server" \
|
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/nginx.conf ${TFW_NGINX_CONF}
|
||||||
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
||||||
@ -74,7 +85,6 @@ COPY supervisor/supervisord.conf ${TFW_SUPERVISORD_CONF}
|
|||||||
COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS}
|
COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS}
|
||||||
COPY lib ${TFW_LIB_DIR}
|
COPY lib ${TFW_LIB_DIR}
|
||||||
COPY src/controller ${TFW_CONTROLLER_DIR}
|
COPY src/controller ${TFW_CONTROLLER_DIR}
|
||||||
COPY lib/tfw/components/terminado_mini_server.py ${TFW_TERMINADO_DIR}/
|
|
||||||
|
|
||||||
### TFW internals ^ ### DEMO v ###############################################################
|
### TFW internals ^ ### DEMO v ###############################################################
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ class WebideReloadEventHandler(FileSystemEventHandler):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.uplink = ServerUplinkConnector()
|
self.uplink = ServerUplinkConnector()
|
||||||
self._paused = False
|
self._paused = False
|
||||||
|
self.ignore = 0
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self._paused = True
|
self._paused = True
|
||||||
@ -23,6 +24,9 @@ class WebideReloadEventHandler(FileSystemEventHandler):
|
|||||||
@RateLimiter(rate_per_second=5)
|
@RateLimiter(rate_per_second=5)
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
if self._paused: return
|
if self._paused: return
|
||||||
|
if self.ignore > 0:
|
||||||
|
self.ignore = self.ignore - 1
|
||||||
|
return
|
||||||
log.debug(event)
|
log.debug(event)
|
||||||
key = 'webide'
|
key = 'webide'
|
||||||
self.uplink.send(key, {'data': {'command': 'reload'}})
|
self.uplink.send(key, {'data': {'command': 'reload'}})
|
||||||
@ -42,6 +46,14 @@ class DirectoryMonitor:
|
|||||||
self.observer.stop()
|
self.observer.stop()
|
||||||
self.observer.join()
|
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
|
@property
|
||||||
def pauser(self):
|
def pauser(self):
|
||||||
return DirectoryMonitor.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
|
return data
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
with self.monitor.pauser:
|
self.monitor.eventhandler.ignore = 1
|
||||||
try: self.filemanager.file_contents = data['content']
|
try: self.filemanager.file_contents = data['content']
|
||||||
except Exception: log.exception('Error writing file!')
|
except Exception: log.exception('Error writing file!')
|
||||||
del data['content']
|
del data['content']
|
||||||
return data
|
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.event_handler_base import TriggerlessEventHandler
|
||||||
from tfw.components.mixins import SupervisorMixin
|
|
||||||
from tfw.config import tfwenv
|
from tfw.config import tfwenv
|
||||||
from tfw.config.logs import logging
|
from tfw.config.logs import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TerminadoEventHandler(TriggerlessEventHandler, SupervisorMixin):
|
class TerminadoEventHandler(TriggerlessEventHandler):
|
||||||
def __init__(self, key, process_name):
|
def __init__(self, key):
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self.working_directory = tfwenv.TERMINADO_DIR
|
self.working_directory = tfwenv.TERMINADO_DIR
|
||||||
self.process_name = process_name
|
self._historymonitor = HistoryMonitor(tfwenv.HISTFILE)
|
||||||
self.start_process(self.process_name)
|
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):
|
def handle_event(self, key, data_json):
|
||||||
log.debug('TerminadoEventHandler received event for key {}'.format(key))
|
log.debug('TerminadoEventHandler received event: {}'.format(data_json))
|
||||||
# TODO: wat do?
|
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.ioloop import IOLoop
|
||||||
from tornado.web import Application
|
from tornado.web import Application
|
||||||
from terminado import TermSocket, UniqueTermManager
|
from terminado import TermSocket, SingleTermManager
|
||||||
|
|
||||||
from tfw.config import tfwenv
|
from tfw.config import tfwenv
|
||||||
from tfw.config.logs import logging
|
from tfw.config.logs import logging
|
||||||
@ -10,24 +10,37 @@ log = logging.getLogger(__name__)
|
|||||||
class TerminadoMiniServer:
|
class TerminadoMiniServer:
|
||||||
def __init__(self, url, port, workdir, shellcmd):
|
def __init__(self, url, port, workdir, shellcmd):
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self._term_manager = SingleTermManager(shell_command=shellcmd,
|
||||||
|
term_settings={'cwd': workdir})
|
||||||
self.application = Application(
|
self.application = Application(
|
||||||
[(
|
[(
|
||||||
url,
|
url,
|
||||||
TerminadoMiniServer.CORSTermSocket,
|
TerminadoMiniServer.ResetterTermSocket,
|
||||||
{'term_manager': UniqueTermManager(shell_command=shellcmd,
|
{'term_manager': self._term_manager}
|
||||||
term_settings={'cwd': workdir})}
|
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
|
|
||||||
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):
|
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)
|
||||||
IOLoop.instance().start()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT))
|
log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT))
|
||||||
TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash']).listen()
|
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.source_code_event_handler import SourceCodeEventHandler
|
||||||
from tfw.components.terminado_event_handler import TerminadoEventHandler
|
from tfw.components.terminado_event_handler import TerminadoEventHandler
|
||||||
from tfw.components.process_managing_event_handler import ProcessManagingEventHandler
|
from tfw.components.process_managing_event_handler import ProcessManagingEventHandler
|
||||||
from tornado.ioloop import IOLoop
|
|
||||||
|
|
||||||
from tfw.config import tfwenv
|
from tfw.config import tfwenv
|
||||||
|
from tfw.config.logs import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ide = SourceCodeEventHandler('webide', tfwenv.WEBIDE_WD)
|
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)
|
processmanager = ProcessManagingEventHandler('processmanager', ide.monitor)
|
||||||
|
|
||||||
eventhandlers = {ide, terminado, processmanager}
|
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