From c93f17c3241016933c3b4bbab24f34d22e8223ef Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 14 Sep 2014 16:46:23 -0700 Subject: [PATCH] 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 --- jupyterhub/handlers/__init__.py | 4 +- jupyterhub/handlers/pages.py | 55 +++++++++++++ share/jupyter/static/js/home.js | 19 +++++ share/jupyter/static/js/jhapi.js | 89 ++++++++++++++++++++ share/jupyter/static/js/utils.js | 127 +++++++++++++++++++++++++++++ share/jupyter/templates/admin.html | 13 +++ share/jupyter/templates/home.html | 29 +++++++ share/jupyter/templates/index.html | 5 +- share/jupyter/templates/login.html | 1 - 9 files changed, 338 insertions(+), 4 deletions(-) create mode 100644 jupyterhub/handlers/pages.py create mode 100644 share/jupyter/static/js/home.js create mode 100644 share/jupyter/static/js/jhapi.js create mode 100644 share/jupyter/static/js/utils.js create mode 100644 share/jupyter/templates/admin.html create mode 100644 share/jupyter/templates/home.html diff --git a/jupyterhub/handlers/__init__.py b/jupyterhub/handlers/__init__.py index 579fa265..8b2ffd58 100644 --- a/jupyterhub/handlers/__init__.py +++ b/jupyterhub/handlers/__init__.py @@ -1,8 +1,8 @@ from .base import * from .login import * -from . import base, login +from . import base, pages, login default_handlers = [] -for mod in (base, login): +for mod in (base, pages, login): default_handlers.extend(mod.default_handlers) diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py new file mode 100644 index 00000000..7a088835 --- /dev/null +++ b/jupyterhub/handlers/pages.py @@ -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), +] \ No newline at end of file diff --git a/share/jupyter/static/js/home.js b/share/jupyter/static/js/home.js new file mode 100644 index 00000000..cb9be3ed --- /dev/null +++ b/share/jupyter/static/js/home.js @@ -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(); + } + }); + }); + +}); diff --git a/share/jupyter/static/js/jhapi.js b/share/jupyter/static/js/jhapi.js new file mode 100644 index 00000000..9770d736 --- /dev/null +++ b/share/jupyter/static/js/jhapi.js @@ -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; +}); \ No newline at end of file diff --git a/share/jupyter/static/js/utils.js b/share/jupyter/static/js/utils.js new file mode 100644 index 00000000..251e180f --- /dev/null +++ b/share/jupyter/static/js/utils.js @@ -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 $("
").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; +}); diff --git a/share/jupyter/templates/admin.html b/share/jupyter/templates/admin.html new file mode 100644 index 00000000..6a8a700c --- /dev/null +++ b/share/jupyter/templates/admin.html @@ -0,0 +1,13 @@ +{% extends "page.html" %} + +{% block main %} + +
+
+ +
+
+ +{% endblock %} diff --git a/share/jupyter/templates/home.html b/share/jupyter/templates/home.html new file mode 100644 index 00000000..66a09629 --- /dev/null +++ b/share/jupyter/templates/home.html @@ -0,0 +1,29 @@ +{% extends "page.html" %} + +{% block main %} + +
+
+
+ {% if user.server %} + Stop My Server + {% endif %} + + My Server + + {% if user.admin %} + Admin + {% endif %} +
+
+
+ +{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/share/jupyter/templates/index.html b/share/jupyter/templates/index.html index 72a89b54..49e3888a 100644 --- a/share/jupyter/templates/index.html +++ b/share/jupyter/templates/index.html @@ -1,11 +1,14 @@ {% extends "page.html" %} +{% block login_widget %} +{% endblock %} + {% block main %} diff --git a/share/jupyter/templates/login.html b/share/jupyter/templates/login.html index b9fcc59d..6b1d525b 100644 --- a/share/jupyter/templates/login.html +++ b/share/jupyter/templates/login.html @@ -1,6 +1,5 @@ {% extends "page.html" %} - {% block login_widget %} {% endblock %}