mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 20:13:02 +00:00
add basic home, admin pages
- index no longer requires login, it's just a big fat login button - home has stop, start, admin buttons - admin doesn't do anything yet - add jhapi javascript client for REST API
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
from .base import *
|
from .base import *
|
||||||
from .login import *
|
from .login import *
|
||||||
|
|
||||||
from . import base, login
|
from . import base, pages, login
|
||||||
|
|
||||||
default_handlers = []
|
default_handlers = []
|
||||||
for mod in (base, login):
|
for mod in (base, pages, login):
|
||||||
default_handlers.extend(mod.default_handlers)
|
default_handlers.extend(mod.default_handlers)
|
||||||
|
55
jupyterhub/handlers/pages.py
Normal file
55
jupyterhub/handlers/pages.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"""Basic html-rendering handlers."""
|
||||||
|
# Copyright (c) IPython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
from tornado import web
|
||||||
|
|
||||||
|
from .. import orm
|
||||||
|
from ..utils import admin_only, url_path_join
|
||||||
|
from .base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
|
class RootHandler(BaseHandler):
|
||||||
|
"""Render the Hub root page.
|
||||||
|
|
||||||
|
Currently redirects to home if logged in,
|
||||||
|
shows big fat login button otherwise.
|
||||||
|
"""
|
||||||
|
def get(self):
|
||||||
|
if self.get_current_user():
|
||||||
|
self.redirect(
|
||||||
|
url_path_join(self.hub.server.base_url, 'home'),
|
||||||
|
permanent=False,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
html = self.render_template('index.html',
|
||||||
|
login_url=self.settings['login_url'],
|
||||||
|
)
|
||||||
|
self.finish(html)
|
||||||
|
|
||||||
|
class HomeHandler(BaseHandler):
|
||||||
|
"""Render the user's home page."""
|
||||||
|
@web.authenticated
|
||||||
|
def get(self):
|
||||||
|
html = self.render_template('home.html',
|
||||||
|
user=self.get_current_user(),
|
||||||
|
)
|
||||||
|
self.finish(html)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminHandler(BaseHandler):
|
||||||
|
"""Render the admin page."""
|
||||||
|
@admin_only
|
||||||
|
def get(self):
|
||||||
|
html = self.render_template('admin.html',
|
||||||
|
user=self.get_current_user(),
|
||||||
|
users=self.db.query(orm.User),
|
||||||
|
)
|
||||||
|
self.finish(html)
|
||||||
|
|
||||||
|
default_handlers = [
|
||||||
|
(r'/', RootHandler),
|
||||||
|
(r'/home', HomeHandler),
|
||||||
|
(r'/admin', AdminHandler),
|
||||||
|
]
|
19
share/jupyter/static/js/home.js
Normal file
19
share/jupyter/static/js/home.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) Jupyter Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
require(["jquery", "jhapi"], function ($, JHAPI) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var base_url = window.jhdata.base_url;
|
||||||
|
var user = window.jhdata.user;
|
||||||
|
var api = new JHAPI(base_url);
|
||||||
|
|
||||||
|
$("#stop").click(function () {
|
||||||
|
api.stop_server(user, {
|
||||||
|
success: function () {
|
||||||
|
$("#stop").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
89
share/jupyter/static/js/jhapi.js
Normal file
89
share/jupyter/static/js/jhapi.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) Jupyter Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
define(['jquery', 'utils'], function ($, utils) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var JHAPI = function (base_url) {
|
||||||
|
this.base_url = base_url;
|
||||||
|
};
|
||||||
|
|
||||||
|
var default_options = {
|
||||||
|
type: 'GET',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
cache: false,
|
||||||
|
dataType : "json",
|
||||||
|
processData: false,
|
||||||
|
success: null,
|
||||||
|
error: utils.log_jax_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
var update = function (d1, d2) {
|
||||||
|
$.map(d2, function (i, key) {
|
||||||
|
d1[key] = d2[key];
|
||||||
|
});
|
||||||
|
return d1;
|
||||||
|
};
|
||||||
|
|
||||||
|
var ajax_defaults = function (options) {
|
||||||
|
var d = {};
|
||||||
|
update(d, default_options);
|
||||||
|
update(d, options);
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.api_request = function (path, options) {
|
||||||
|
options = options || {};
|
||||||
|
options = ajax_defaults(options || {});
|
||||||
|
var url = utils.url_path_join(
|
||||||
|
this.base_url,
|
||||||
|
'api',
|
||||||
|
utils.encode_uri_components(path)
|
||||||
|
);
|
||||||
|
$.ajax(url, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.start_server = function (user, options) {
|
||||||
|
options = options || {};
|
||||||
|
options.update({
|
||||||
|
type: 'POST',
|
||||||
|
});
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user, 'server'),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.stop_server = function (user, options) {
|
||||||
|
options = update(options || {}, {
|
||||||
|
type: 'DELETE',
|
||||||
|
});
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user, 'server'),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.list_users = function (options) {
|
||||||
|
this.api_request('users', options);
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.get_user = function (user, options) {
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.delete_user = function (user, options) {
|
||||||
|
options = update(options || {}, {
|
||||||
|
type: 'DELETE',
|
||||||
|
});
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return JHAPI;
|
||||||
|
});
|
127
share/jupyter/static/js/utils.js
Normal file
127
share/jupyter/static/js/utils.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// Based on IPython's base.js.utils
|
||||||
|
// Original Copyright (c) IPython Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
// Modifications Copyright (c) Juptyer Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
define(['jquery'], function($){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var url_path_join = function () {
|
||||||
|
// join a sequence of url components with '/'
|
||||||
|
var url = '';
|
||||||
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
|
if (arguments[i] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (url.length > 0 && url[url.length-1] != '/') {
|
||||||
|
url = url + '/' + arguments[i];
|
||||||
|
} else {
|
||||||
|
url = url + arguments[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = url.replace(/\/\/+/, '/');
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
var parse_url = function (url) {
|
||||||
|
// an `a` element with an href allows attr-access to the parsed segments of a URL
|
||||||
|
// a = parse_url("http://localhost:8888/path/name#hash")
|
||||||
|
// a.protocol = "http:"
|
||||||
|
// a.host = "localhost:8888"
|
||||||
|
// a.hostname = "localhost"
|
||||||
|
// a.port = 8888
|
||||||
|
// a.pathname = "/path/name"
|
||||||
|
// a.hash = "#hash"
|
||||||
|
var a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
var encode_uri_components = function (uri) {
|
||||||
|
// encode just the components of a multi-segment uri,
|
||||||
|
// leaving '/' separators
|
||||||
|
return uri.split('/').map(encodeURIComponent).join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
var url_join_encode = function () {
|
||||||
|
// join a sequence of url components with '/',
|
||||||
|
// encoding each component with encodeURIComponent
|
||||||
|
return encode_uri_components(url_path_join.apply(null, arguments));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var escape_html = function (text) {
|
||||||
|
// escape text to HTML
|
||||||
|
return $("<div/>").text(text).html();
|
||||||
|
};
|
||||||
|
|
||||||
|
var get_body_data = function(key) {
|
||||||
|
// get a url-encoded item from body.data and decode it
|
||||||
|
// we should never have any encoded URLs anywhere else in code
|
||||||
|
// until we are building an actual request
|
||||||
|
return decodeURIComponent($('body').data(key));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
|
||||||
|
var browser = (function() {
|
||||||
|
if (typeof navigator === 'undefined') {
|
||||||
|
// navigator undefined in node
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
var N= navigator.appName, ua= navigator.userAgent, tem;
|
||||||
|
var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
|
||||||
|
if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
|
||||||
|
M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
|
||||||
|
return M;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
|
||||||
|
var platform = (function () {
|
||||||
|
if (typeof navigator === 'undefined') {
|
||||||
|
// navigator undefined in node
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
var OSName="None";
|
||||||
|
if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
|
||||||
|
if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
|
||||||
|
if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
|
||||||
|
if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
|
||||||
|
return OSName;
|
||||||
|
})();
|
||||||
|
|
||||||
|
var ajax_error_msg = function (jqXHR) {
|
||||||
|
// Return a JSON error message if there is one,
|
||||||
|
// otherwise the basic HTTP status text.
|
||||||
|
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
|
||||||
|
return jqXHR.responseJSON.message;
|
||||||
|
} else {
|
||||||
|
return jqXHR.statusText;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var log_ajax_error = function (jqXHR, status, error) {
|
||||||
|
// log ajax failures with informative messages
|
||||||
|
var msg = "API request failed (" + jqXHR.status + "): ";
|
||||||
|
console.log(jqXHR);
|
||||||
|
msg += ajax_error_msg(jqXHR);
|
||||||
|
console.log(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
var utils = {
|
||||||
|
url_path_join : url_path_join,
|
||||||
|
url_join_encode : url_join_encode,
|
||||||
|
encode_uri_components : encode_uri_components,
|
||||||
|
escape_html : escape_html,
|
||||||
|
get_body_data : get_body_data,
|
||||||
|
parse_url : parse_url,
|
||||||
|
browser : browser,
|
||||||
|
platform: platform,
|
||||||
|
ajax_error_msg : ajax_error_msg,
|
||||||
|
log_ajax_error : log_ajax_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
return utils;
|
||||||
|
});
|
13
share/jupyter/templates/admin.html
Normal file
13
share/jupyter/templates/admin.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-center">
|
||||||
|
<a class="btn btn-lg btn-primary">Administrate!</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
29
share/jupyter/templates/home.html
Normal file
29
share/jupyter/templates/home.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-center">
|
||||||
|
{% if user.server %}
|
||||||
|
<a id="stop" class="btn btn-lg btn-danger">Stop My Server</a>
|
||||||
|
{% endif %}
|
||||||
|
<a id="start" class="btn btn-lg btn-success"
|
||||||
|
href="{{base_url}}user/{{user.name}}/"
|
||||||
|
>
|
||||||
|
My Server
|
||||||
|
</a>
|
||||||
|
{% if user.admin %}
|
||||||
|
<a id="admin" class="btn btn-lg btn-primary" href="{{base_url}}admin">Admin</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
require(["home"]);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@@ -1,11 +1,14 @@
|
|||||||
{% extends "page.html" %}
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% block login_widget %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a id="start" class="btn btn-lg btn-primary" href="{{server_url}}">Dashboard</a>
|
<a id="login" class="btn btn-lg btn-primary" href="{{login_url}}">Log in</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
{% extends "page.html" %}
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
|
||||||
{% block login_widget %}
|
{% block login_widget %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user