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 .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)
|
||||
|
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" %}
|
||||
|
||||
{% block login_widget %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<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>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{% extends "page.html" %}
|
||||
|
||||
|
||||
{% block login_widget %}
|
||||
{% endblock %}
|
||||
|
||||
|
Reference in New Issue
Block a user