Add flask app with auto-reload to render the form template

This commit is contained in:
Benoît Seignovert 2024-02-14 10:02:07 +01:00
parent 433862d0ad
commit 2333ccd168
Signed by: Benoît Seignovert
GPG key ID: F5D8895227D18A0B
12 changed files with 662 additions and 61 deletions

5
render/__init__.py Normal file
View file

@ -0,0 +1,5 @@
"""HTML dynamic template rendering submodule.
This file is required for pytest pre-commit hook (pytest#3151).
"""

60
render/__main__.py Normal file
View file

@ -0,0 +1,60 @@
"""Web Server Gateway Interface with autoreload to render spawner template.
Usage: `python -m render`
"""
from pathlib import Path
from traceback import format_exc
import glicid_spawner
from flask import Flask, render_template, request
from glicid_spawner.form import options_attrs, options_form, options_from_form
from livereload import Server
# Monkeypatch
USERNAME = 'john-doe'
glicid_spawner.micromamba.MICROMAMBA_ROOT = (
Path(__file__).parent / '..' / 'tests' / 'data' / 'micromamba'
).resolve()
glicid_spawner.micromamba.GLOBAL_USER = 'global'
glicid_spawner.micromamba.GLOBAL_EXCLUDED = 'qux'
# Flask app
app = Flask(__name__)
app.debug = True
@app.route('/')
def home():
"""Form spawner home page."""
return render_template(
'form.html', spawner_options_form=options_form(USERNAME), options=options_attrs(USERNAME)
)
@app.route('/submit', methods=['POST'])
def submit():
"""Reformat form data and extract spawner options.
https://jupyterhub.readthedocs.io/en/stable/reference/spawners.html#spawner-options-from-form
"""
formdata = dict(request.form.lists())
# Trying to parse the options from the formdata
try:
return render_template(
'options.html', formdata=formdata, options=options_from_form(formdata)
)
except Exception:
return render_template('options.html', formdata=formdata, err=format_exc())
def server_autoreload():
"""Start auto-reload server."""
server = Server(app.wsgi_app)
server.serve()
if __name__ == '__main__':
server_autoreload()

215
render/static/jupyter.css Normal file
View file

@ -0,0 +1,215 @@
/* Jupyterhub style.css */
.btn-jupyter {
color: #fff;
background-color: #f37524;
border-color: #e34f21
}
.btn-jupyter.focus,
.btn-jupyter:focus {
color: #fff;
background-color: #d85c0c;
border-color: #76270f
}
.btn-jupyter:hover {
color: #fff;
background-color: #d85c0c;
border-color: #b13b16
}
.btn-jupyter.active,
.btn-jupyter:active,
.open>.dropdown-toggle.btn-jupyter {
color: #fff;
background-color: #d85c0c;
background-image: none;
border-color: #b13b16
}
.btn-jupyter.active.focus,
.btn-jupyter.active:focus,
.btn-jupyter.active:hover,
.btn-jupyter:active.focus,
.btn-jupyter:active:focus,
.btn-jupyter:active:hover,
.open>.dropdown-toggle.btn-jupyter.focus,
.open>.dropdown-toggle.btn-jupyter:focus,
.open>.dropdown-toggle.btn-jupyter:hover {
color: #fff;
background-color: #b64d0a;
border-color: #76270f
}
.btn-jupyter.disabled.focus,
.btn-jupyter.disabled:focus,
.btn-jupyter.disabled:hover,
.btn-jupyter[disabled].focus,
.btn-jupyter[disabled]:focus,
.btn-jupyter[disabled]:hover,
fieldset[disabled] .btn-jupyter.focus,
fieldset[disabled] .btn-jupyter:focus,
fieldset[disabled] .btn-jupyter:hover {
background-color: #f37524;
border-color: #e34f21
}
.btn-jupyter .badge {
color: #f37524;
background-color: #fff
}
@media (max-width:480px) {
#jupyterhub-logo {
margin-left: 15px
}
}
#jupyterhub-logo .jpy-logo {
height: 28px;
margin-top: 6px
}
@media (max-width:480px) {
.navbar-right li span {
position: relative;
display: block;
padding: 10px 15px
}
}
#header {
border-bottom: 1px solid #e7e7e7
}
.hidden {
display: none
}
#progress-log {
margin-top: 8px
}
.progress-log-event {
border-top: 1px solid #e7e7e7;
padding: 8px
}
.feedback-container {
margin-top: 16px
}
.feedback-widget {
padding: 5px 0 0 6px
}
.feedback-widget i {
font-size: 2em;
color: #d3d3d3
}
.form-control:focus {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px #f37524;
border-color: #f37524;
outline-color: #f37524
}
i.sort-icon {
margin-left: 4px
}
tr.pagination-row>td.pagination-page-info {
vertical-align: middle
}
.version_footer {
bottom: 0;
width: 100%
}
div.error {
margin: 2em;
text-align: center
}
div.ajax-error {
padding: 1em;
text-align: center;
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1
}
div.ajax-error hr {
border-top-color: #e4b9c0
}
div.ajax-error .alert-link {
color: #843534
}
div.error>h1 {
font-size: 300%;
line-height: normal
}
div.error>p {
font-size: 200%;
line-height: normal
}
#login-main {
display: table;
height: 80vh
}
#login-main #insecure-login-warning {
background-color: #fcf8e3;
padding: 10px
}
a#login-main #insecure-login-warning:focus,
a#login-main #insecure-login-warning:hover {
background-color: #f7ecb5
}
#login-main .service-login {
text-align: center;
display: table-cell;
vertical-align: middle;
margin: auto auto 20% auto
}
#login-main form {
display: table-cell;
vertical-align: middle;
margin: auto auto 20% auto;
width: 350px
}
#login-main .login_error {
color: #ff4500;
font-weight: 700;
text-align: center
}
#login-main .auth-form-header {
padding: 10px 20px;
color: #fff;
background: #f37524;
border-radius: 3px 3px 0 0;
font-size: large
}
#login-main .auth-form-header>h1 {
font-size: inherit;
font-weight: inherit;
margin: 0
}
#login-main .auth-form-body {
padding: 20px;
border: thin silver solid;
border-top: none;
border-radius: 0 0 3px 3px
}

View file

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[TEST] Spawner Form</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
crossorigin="anonymous"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"
integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
crossorigin="anonymous"></script>
<!-- Jupyter CSS stylesheet -->
<link rel="stylesheet" href="/static/jupyter.css" type="text/css">
</head>
<body>
<div class="container">
<div class="row text-center">
<h1>🚧 Spawner options form 🚧</h1>
</div>
<div class="row col-sm-offset-2 col-sm-8">
<form enctype="multipart/form-data" id="spawn_form" action="/submit" method="post" role="form">
<br>
{{spawner_options_form | safe}}
<br>
<div class="feedback-container">
<input type="submit" value="Submit" class="btn btn-jupyter form-control">
<div class="feedback-widget hidden">
<i class="fa fa-spinner"></i>
</div>
</div>
</form>
</div>
<div class="clearfix visible-block"></div>
<hr>
<div class="row text-center">
<h2>🐛 Debug <a href="/">🔄</a></h2>
</div>
<div id="debug-results">
<div class="row col-sm-offset-2 col-sm-8">
<h3>🔤 Input options form</h3>
<pre>{{options|pprint}}</pre>
</div>
</div>
</div>
<script>
var $debug = $('#debug-results');
var $form = $('#spawn_form');
$('input').change(function(){
$form.submit();
});
$('select').change(function(){
$form.submit();
});
$form.submit(function (e) {
e.preventDefault();
$.ajax({
type: "POST",
url: "/submit",
data: $("#spawn_form").serialize(),
success: function (content) {
$debug.empty();
$debug.append(content);
}
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,14 @@
<div class="row col-sm-offset-2 col-sm-8">
<h3>📝 Submitted form data</h3>
<pre><code>{{formdata}}</code></pre>
</div>
<div class="row col-sm-offset-2 col-sm-8 text-success">
{% if options %}
<h3>✅ Parsed spawner options</h3>
<pre><code>{{options}}</code></pre>
{% else %}
<h3>⛔️ Spawner options error</h3>
<pre><code>{{err}}</code></pre>
{% endif %}
</div>