6
0
mirror of https://github.com/ChristianLight/tutor.git synced 2024-12-12 14:17:46 +00:00
tutor/tutor/webui.py
2019-03-10 18:02:20 +01:00

139 lines
4.1 KiB
Python

import io
import os
import platform
import subprocess
import sys
import tarfile
import yaml
from urllib.request import urlopen
import click
# Note: it is important that this module does not depend on config, such that
# the web ui can be launched even where there is no configuration.
from . import fmt
from . import opts
from . import env as tutor_env
@click.group(
short_help="Web user interface",
help="""Run Tutor commands from a web terminal"""
)
def webui():
pass
@click.command(
help="Start the web UI",
)
@opts.root
@click.option(
"-p", "--port", default=3737, type=int, show_default=True,
help="Port number to listen",
)
@click.option(
"-h", "--host", default="0.0.0.0", show_default=True,
help="Host address to listen",
)
def start(root, port, host):
check_gotty_binary(root)
click.echo(fmt.info("Access the Tutor web UI at http://{}:{}".format(host, port)))
while True:
config = load_config(root)
user = config["user"]
password = config["password"]
command = [
gotty_path(root), "--permit-write",
"--address", host, "--port", str(port),
"--title-format", "Tutor web UI - {{ .Command }} ({{ .Hostname }})",
]
if user and password:
credential = "{}:{}".format(user, password)
command += ["--credential", credential]
else:
click.echo(fmt.alert("Running web UI without user authentication. Run 'tutor webui configure' to setup authentication"))
command += [sys.argv[0], "ui"]
p = subprocess.Popen(command)
while True:
try:
p.wait(timeout=2)
except subprocess.TimeoutExpired:
new_config = load_config(root)
if new_config != config:
click.echo("WARNING configuration changed. Tutor web UI is now going to restart. Reload this page to continue.")
p.kill()
p.wait()
break
@click.command(help="Configure authentication")
@opts.root
@click.option("-u", "--user", prompt="User name", help="Authentication user name")
@click.option(
"-p", "--password",
prompt=True, hide_input=True, confirmation_prompt=True,
help="Authentication password"
)
def configure(root, user, password):
save_config(root, {
"user": user,
"password": password,
})
click.echo(fmt.info(
"The web UI configuration has been updated. "
"If at any point you wish to reset your username and password, "
"just delete the following file:\n\n {}".format(config_path(root))
))
def check_gotty_binary(root):
path = gotty_path(root)
if os.path.exists(path):
return
click.echo(fmt.info("Downloading gotty to {}...".format(path)))
# Generate release url
# Note: I don't know how to handle arm
architecture = "amd64" if platform.architecture()[0] == "64bit" else "386"
url = "https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_{system}_{architecture}.tar.gz".format(
system=platform.system().lower(),
architecture=architecture,
)
# Download
response = urlopen(url)
# Decompress
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
os.makedirs(dirname)
compressed = tarfile.open(fileobj=io.BytesIO(response.read()))
compressed.extract("./gotty", dirname)
def load_config(root):
path = config_path(root)
if not os.path.exists(path):
save_config(root, {
"user": None,
"password": None,
})
with open(config_path(root)) as f:
return yaml.load(f)
def save_config(root, config):
path = config_path(root)
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
with open(path, "w") as of:
yaml.dump(config, of, default_flow_style=False)
def gotty_path(root):
return get_path(root, "gotty")
def config_path(root):
return get_path(root, "config.yml")
def get_path(root, filename):
return tutor_env.pathjoin(root, "webui", filename)
webui.add_command(start)
webui.add_command(configure)