Add flask app with auto-reload to render the form template
This commit is contained in:
parent
433862d0ad
commit
2333ccd168
12 changed files with 662 additions and 61 deletions
5
render/__init__.py
Normal file
5
render/__init__.py
Normal 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
60
render/__main__.py
Normal 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
215
render/static/jupyter.css
Normal 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
|
||||
}
|
90
render/templates/form.html
Normal file
90
render/templates/form.html
Normal 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>
|
14
render/templates/options.html
Normal file
14
render/templates/options.html
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue