mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-12 20:43:02 +00:00
Handle named servers in UserSpawnHandler, home, admin pages
Made CSS and HTML (and Jinja2) of admin page compatible with named servers.
This commit is contained in:
@@ -20,7 +20,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
from tornado.httputil import url_concat, HTTPHeaders
|
from tornado.httputil import url_concat, HTTPHeaders
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.web import RequestHandler
|
from tornado.web import RequestHandler, MissingArgumentError
|
||||||
from tornado import gen, web
|
from tornado import gen, web
|
||||||
|
|
||||||
from .. import __version__
|
from .. import __version__
|
||||||
@@ -854,10 +854,10 @@ class BaseHandler(RequestHandler):
|
|||||||
await self.proxy.delete_user(user, server_name)
|
await self.proxy.delete_user(user, server_name)
|
||||||
await user.stop(server_name)
|
await user.stop(server_name)
|
||||||
|
|
||||||
async def stop_single_user(self, user, name=''):
|
async def stop_single_user(self, user, server_name=''):
|
||||||
if name not in user.spawners:
|
if server_name not in user.spawners:
|
||||||
raise KeyError("User %s has no such spawner %r", user.name, name)
|
raise KeyError("User %s has no such spawner %r", user.name, server_name)
|
||||||
spawner = user.spawners[name]
|
spawner = user.spawners[server_name]
|
||||||
if spawner.pending:
|
if spawner.pending:
|
||||||
raise RuntimeError("%s pending %s" % (spawner._log_name, spawner.pending))
|
raise RuntimeError("%s pending %s" % (spawner._log_name, spawner.pending))
|
||||||
# set user._stop_pending before doing anything async
|
# set user._stop_pending before doing anything async
|
||||||
@@ -873,8 +873,8 @@ class BaseHandler(RequestHandler):
|
|||||||
"""
|
"""
|
||||||
tic = IOLoop.current().time()
|
tic = IOLoop.current().time()
|
||||||
try:
|
try:
|
||||||
await self.proxy.delete_user(user, name)
|
await self.proxy.delete_user(user, server_name)
|
||||||
await user.stop(name)
|
await user.stop(server_name)
|
||||||
finally:
|
finally:
|
||||||
spawner._stop_pending = False
|
spawner._stop_pending = False
|
||||||
toc = IOLoop.current().time()
|
toc = IOLoop.current().time()
|
||||||
@@ -885,7 +885,7 @@ class BaseHandler(RequestHandler):
|
|||||||
await gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), stop())
|
await gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), stop())
|
||||||
except gen.TimeoutError:
|
except gen.TimeoutError:
|
||||||
# hit timeout, but stop is still pending
|
# hit timeout, but stop is still pending
|
||||||
self.log.warning("User %s:%s server is slow to stop", user.name, name)
|
self.log.warning("User %s:%s server is slow to stop", user.name, server_name)
|
||||||
|
|
||||||
#---------------------------------------------------------------
|
#---------------------------------------------------------------
|
||||||
# template rendering
|
# template rendering
|
||||||
@@ -1019,7 +1019,7 @@ class PrefixRedirectHandler(BaseHandler):
|
|||||||
|
|
||||||
|
|
||||||
class UserSpawnHandler(BaseHandler):
|
class UserSpawnHandler(BaseHandler):
|
||||||
"""Redirect requests to /user/name/* handled by the Hub.
|
"""Redirect requests to /user/user_name/* handled by the Hub.
|
||||||
|
|
||||||
If logged in, spawn a single-user server and redirect request.
|
If logged in, spawn a single-user server and redirect request.
|
||||||
If a user, alice, requests /user/bob/notebooks/mynotebook.ipynb,
|
If a user, alice, requests /user/bob/notebooks/mynotebook.ipynb,
|
||||||
@@ -1035,21 +1035,21 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
self.write(json.dumps({"message": "%s is not running" % user.name}))
|
self.write(json.dumps({"message": "%s is not running" % user.name}))
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
async def get(self, name, user_path):
|
async def get(self, user_name, user_path):
|
||||||
if not user_path:
|
if not user_path:
|
||||||
user_path = '/'
|
user_path = '/'
|
||||||
current_user = self.current_user
|
current_user = self.current_user
|
||||||
if (
|
if (
|
||||||
current_user
|
current_user
|
||||||
and current_user.name != name
|
and current_user.name != user_name
|
||||||
and current_user.admin
|
and current_user.admin
|
||||||
and self.settings.get('admin_access', False)
|
and self.settings.get('admin_access', False)
|
||||||
):
|
):
|
||||||
# allow admins to spawn on behalf of users
|
# allow admins to spawn on behalf of users
|
||||||
user = self.find_user(name)
|
user = self.find_user(user_name)
|
||||||
if user is None:
|
if user is None:
|
||||||
# no such user
|
# no such user
|
||||||
raise web.HTTPError(404, "No such user %s" % name)
|
raise web.HTTPError(404, "No such user %s" % user_name)
|
||||||
self.log.info("Admin %s requesting spawn on behalf of %s",
|
self.log.info("Admin %s requesting spawn on behalf of %s",
|
||||||
current_user.name, user.name)
|
current_user.name, user.name)
|
||||||
admin_spawn = True
|
admin_spawn = True
|
||||||
@@ -1059,7 +1059,7 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
admin_spawn = False
|
admin_spawn = False
|
||||||
# For non-admins, we should spawn if the user matches
|
# For non-admins, we should spawn if the user matches
|
||||||
# otherwise redirect users to their own server
|
# otherwise redirect users to their own server
|
||||||
should_spawn = (current_user and current_user.name == name)
|
should_spawn = (current_user and current_user.name == user_name)
|
||||||
|
|
||||||
if "api" in user_path.split("/") and not user.active:
|
if "api" in user_path.split("/") and not user.active:
|
||||||
# API request for not-running server (e.g. notebook UI left open)
|
# API request for not-running server (e.g. notebook UI left open)
|
||||||
@@ -1071,7 +1071,7 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
# if spawning fails for any reason, point users to /hub/home to retry
|
# if spawning fails for any reason, point users to /hub/home to retry
|
||||||
self.extra_error_html = self.spawn_home_error
|
self.extra_error_html = self.spawn_home_error
|
||||||
|
|
||||||
# If people visit /user/:name directly on the Hub,
|
# If people visit /user/:user_name directly on the Hub,
|
||||||
# the redirects will just loop, because the proxy is bypassed.
|
# the redirects will just loop, because the proxy is bypassed.
|
||||||
# Try to check for that and warn,
|
# Try to check for that and warn,
|
||||||
# though the user-facing behavior is unchanged
|
# though the user-facing behavior is unchanged
|
||||||
@@ -1087,7 +1087,11 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
""", self.request.full_url(), self.proxy.public_url)
|
""", self.request.full_url(), self.proxy.public_url)
|
||||||
|
|
||||||
# logged in as valid user, check for pending spawn
|
# logged in as valid user, check for pending spawn
|
||||||
spawner = user.spawner
|
if self.allow_named_servers:
|
||||||
|
server_name = self.get_argument('server', '')
|
||||||
|
else:
|
||||||
|
server_name = ''
|
||||||
|
spawner = user.spawners[server_name]
|
||||||
|
|
||||||
# First, check for previous failure.
|
# First, check for previous failure.
|
||||||
if (
|
if (
|
||||||
@@ -1152,7 +1156,7 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
{'next': self.request.uri}))
|
{'next': self.request.uri}))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
await self.spawn_single_user(user)
|
await self.spawn_single_user(user, server_name)
|
||||||
|
|
||||||
# spawn didn't finish, show pending page
|
# spawn didn't finish, show pending page
|
||||||
if spawner.pending:
|
if spawner.pending:
|
||||||
@@ -1208,7 +1212,7 @@ class UserSpawnHandler(BaseHandler):
|
|||||||
url_parts = urlparse(target)
|
url_parts = urlparse(target)
|
||||||
query_parts = parse_qs(url_parts.query)
|
query_parts = parse_qs(url_parts.query)
|
||||||
query_parts['redirects'] = redirects + 1
|
query_parts['redirects'] = redirects + 1
|
||||||
url_parts = url_parts._replace(query=urlencode(query_parts))
|
url_parts = url_parts._replace(query=urlencode(query_parts, doseq=True))
|
||||||
target = urlunparse(url_parts)
|
target = urlunparse(url_parts)
|
||||||
else:
|
else:
|
||||||
target = url_concat(target, {'redirects': 1})
|
target = url_concat(target, {'redirects': 1})
|
||||||
@@ -1276,7 +1280,7 @@ class AddSlashHandler(BaseHandler):
|
|||||||
|
|
||||||
default_handlers = [
|
default_handlers = [
|
||||||
(r'', AddSlashHandler), # add trailing / to `/hub`
|
(r'', AddSlashHandler), # add trailing / to `/hub`
|
||||||
(r'/user/([^/]+)(/.*)?', UserSpawnHandler),
|
(r'/user/(?P<user_name>[^/]+)(?P<user_path>/.*)?', UserSpawnHandler),
|
||||||
(r'/user-redirect/(.*)?', UserRedirectHandler),
|
(r'/user-redirect/(.*)?', UserRedirectHandler),
|
||||||
(r'/security/csp-report', CSPReportHandler),
|
(r'/security/csp-report', CSPReportHandler),
|
||||||
]
|
]
|
||||||
|
@@ -50,13 +50,18 @@ class HomeHandler(BaseHandler):
|
|||||||
# trigger poll_and_notify event in case of a server that died
|
# trigger poll_and_notify event in case of a server that died
|
||||||
await user.spawner.poll_and_notify()
|
await user.spawner.poll_and_notify()
|
||||||
|
|
||||||
# send the user to /spawn if they aren't running or pending a spawn,
|
# send the user to /spawn if they have no active servers,
|
||||||
# to establish that this is an explicit spawn request rather
|
# to establish that this is an explicit spawn request rather
|
||||||
# than an implicit one, which can be caused by any link to `/user/:name`
|
# than an implicit one, which can be caused by any link to `/user/:name(/:server_name)`
|
||||||
url = user.url if user.spawner.active else url_path_join(self.hub.base_url, 'spawn')
|
url = url_path_join(self.hub.base_url, 'user', user.name) if user.active else url_path_join(self.hub.base_url, 'spawn')
|
||||||
html = self.render_template('home.html',
|
html = self.render_template('home.html',
|
||||||
user=user,
|
user=user,
|
||||||
url=url,
|
url=url,
|
||||||
|
allow_named_servers=self.allow_named_servers,
|
||||||
|
url_path_join=url_path_join,
|
||||||
|
# can't use user.spawners because the stop method of User pops named servers from user.spawners when they're stopped
|
||||||
|
spawners = user.orm_user._orm_spawners,
|
||||||
|
default_server = user.spawner,
|
||||||
)
|
)
|
||||||
self.finish(html)
|
self.finish(html)
|
||||||
|
|
||||||
@@ -214,11 +219,12 @@ class AdminHandler(BaseHandler):
|
|||||||
running = [ u for u in users if u.running ]
|
running = [ u for u in users if u.running ]
|
||||||
|
|
||||||
html = self.render_template('admin.html',
|
html = self.render_template('admin.html',
|
||||||
user=self.current_user,
|
current_user=self.current_user,
|
||||||
admin_access=self.settings.get('admin_access', False),
|
admin_access=self.settings.get('admin_access', False),
|
||||||
users=users,
|
users=users,
|
||||||
running=running,
|
running=running,
|
||||||
sort={s:o for s,o in zip(sorts, orders)},
|
sort={s:o for s,o in zip(sorts, orders)},
|
||||||
|
allow_named_servers=self.allow_named_servers,
|
||||||
)
|
)
|
||||||
self.finish(html)
|
self.finish(html)
|
||||||
|
|
||||||
|
@@ -182,19 +182,19 @@ class User:
|
|||||||
await self.save_auth_state(auth_state)
|
await self.save_auth_state(auth_state)
|
||||||
return auth_state
|
return auth_state
|
||||||
|
|
||||||
def _new_spawner(self, name, spawner_class=None, **kwargs):
|
def _new_spawner(self, server_name, spawner_class=None, **kwargs):
|
||||||
"""Create a new spawner"""
|
"""Create a new spawner"""
|
||||||
if spawner_class is None:
|
if spawner_class is None:
|
||||||
spawner_class = self.spawner_class
|
spawner_class = self.spawner_class
|
||||||
self.log.debug("Creating %s for %s:%s", spawner_class, self.name, name)
|
self.log.debug("Creating %s for %s:%s", spawner_class, self.name, server_name)
|
||||||
|
|
||||||
orm_spawner = self.orm_spawners.get(name)
|
orm_spawner = self.orm_spawners.get(server_name)
|
||||||
if orm_spawner is None:
|
if orm_spawner is None:
|
||||||
orm_spawner = orm.Spawner(user=self.orm_user, name=name)
|
orm_spawner = orm.Spawner(user=self.orm_user, name=server_name)
|
||||||
self.db.add(orm_spawner)
|
self.db.add(orm_spawner)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
assert name in self.orm_spawners
|
assert server_name in self.orm_spawners
|
||||||
if name == '' and self.state:
|
if server_name == '' and self.state:
|
||||||
# migrate user.state to spawner.state
|
# migrate user.state to spawner.state
|
||||||
orm_spawner.state = self.state
|
orm_spawner.state = self.state
|
||||||
self.state = None
|
self.state = None
|
||||||
@@ -202,15 +202,15 @@ class User:
|
|||||||
# use fully quoted name for client_id because it will be used in cookie-name
|
# use fully quoted name for client_id because it will be used in cookie-name
|
||||||
# self.escaped_name may contain @ which is legal in URLs but not cookie keys
|
# self.escaped_name may contain @ which is legal in URLs but not cookie keys
|
||||||
client_id = 'jupyterhub-user-%s' % quote(self.name)
|
client_id = 'jupyterhub-user-%s' % quote(self.name)
|
||||||
if name:
|
if server_name:
|
||||||
client_id = '%s-%s' % (client_id, quote(name))
|
client_id = '%s-%s' % (client_id, quote(server_name))
|
||||||
spawn_kwargs = dict(
|
spawn_kwargs = dict(
|
||||||
user=self,
|
user=self,
|
||||||
orm_spawner=orm_spawner,
|
orm_spawner=orm_spawner,
|
||||||
hub=self.settings.get('hub'),
|
hub=self.settings.get('hub'),
|
||||||
authenticator=self.authenticator,
|
authenticator=self.authenticator,
|
||||||
config=self.settings.get('config'),
|
config=self.settings.get('config'),
|
||||||
proxy_spec=url_path_join(self.proxy_spec, name, '/'),
|
proxy_spec=url_path_join(self.proxy_spec, server_name, '/'),
|
||||||
db=self.db,
|
db=self.db,
|
||||||
oauth_client_id=client_id,
|
oauth_client_id=client_id,
|
||||||
cookie_options = self.settings.get('cookie_options', {}),
|
cookie_options = self.settings.get('cookie_options', {}),
|
||||||
|
@@ -62,24 +62,50 @@ require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, mo
|
|||||||
el.text(m.isValid() ? m.fromNow() : "Never");
|
el.text(m.isValid() ? m.fromNow() : "Never");
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".stop-server").click(function () {
|
$(".stop-server.default-server").click(function () {
|
||||||
var el = $(this);
|
var el = $(this);
|
||||||
var row = get_row(el);
|
var row = get_row(el);
|
||||||
var user = row.data('user');
|
var user = row.data('user');
|
||||||
el.text("stopping...");
|
el.text("stopping...");
|
||||||
api.stop_server(user, {
|
api.stop_server(user, {
|
||||||
success: function () {
|
success: function () {
|
||||||
el.text('stop server').addClass('hidden');
|
if (el.data("named_servers") === true) {
|
||||||
row.find('.access-server').addClass('hidden');
|
el.text("stop default server").addClass("hidden");
|
||||||
row.find('.start-server').removeClass('hidden');
|
} else {
|
||||||
}
|
el.text("stop server").addClass("hidden");
|
||||||
|
}
|
||||||
|
row.find(".access-server").addClass("hidden");
|
||||||
|
row.find(".start-server").removeClass("hidden");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".access-server").map(function (i, el) {
|
$(".stop-server[id^='stop-']").click(function () {
|
||||||
|
var el = $(this);
|
||||||
|
var row = get_row(el);
|
||||||
|
var user = row.data("user");
|
||||||
|
var server_name = (this.id).replace(/^(stop-)/, "");
|
||||||
|
el.text("stopping...");
|
||||||
|
api.stop_named_server(user, server_name, {
|
||||||
|
success: function() {
|
||||||
|
el.text("stop "+server_name).addClass("hidden");
|
||||||
|
row.find(".access-server").addClass("hidden");
|
||||||
|
row.find(".start-server").removeClass("hidden");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".access-server.default-server").map(function (i, el) {
|
||||||
el = $(el);
|
el = $(el);
|
||||||
var user = get_row(el).data('user');
|
var user = get_row(el).data("user");
|
||||||
el.attr('href', utils.url_path_join(prefix, 'user', user) + '/');
|
el.attr("href", utils.url_path_join(prefix, "user", user) + "/");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".access-server[id^='access-']").map(function (i, el) {
|
||||||
|
el = $(el);
|
||||||
|
var user = get_row(el).data("user");
|
||||||
|
var server_name = (this.id).replace(/^(access-)/, "");
|
||||||
|
el.attr("href", utils.url_path_join(prefix, "user", user, server_name) + "/" );
|
||||||
});
|
});
|
||||||
|
|
||||||
if (admin_access && options_form) {
|
if (admin_access && options_form) {
|
||||||
@@ -94,16 +120,35 @@ require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, mo
|
|||||||
// since it would mean opening a bunch of tabs
|
// since it would mean opening a bunch of tabs
|
||||||
$('#start-all-servers').addClass('hidden');
|
$('#start-all-servers').addClass('hidden');
|
||||||
} else {
|
} else {
|
||||||
$(".start-server").click(function () {
|
$(".start-server.default-server").click(function () {
|
||||||
var el = $(this);
|
var el = $(this);
|
||||||
var row = get_row(el);
|
var row = get_row(el);
|
||||||
var user = row.data('user');
|
var user = row.data('user');
|
||||||
el.text("starting...");
|
el.text("starting...");
|
||||||
api.start_server(user, {
|
api.start_server(user, {
|
||||||
success: function () {
|
success: function () {
|
||||||
el.text('start server').addClass('hidden');
|
if (el.data("named_servers") === true ){
|
||||||
row.find('.stop-server').removeClass('hidden');
|
el.text("start default server").addClass("hidden");
|
||||||
row.find('.access-server').removeClass('hidden');
|
} else {
|
||||||
|
el.text("start server").addClass("hidden");
|
||||||
|
}
|
||||||
|
row.find(".stop-server").removeClass("hidden");
|
||||||
|
row.find(".access-server").removeClass("hidden");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".start-server[id^='start-']").click(function () {
|
||||||
|
var el = $(this);
|
||||||
|
var row = get_row(el);
|
||||||
|
var user = row.data("user");
|
||||||
|
var server_name = (this.id).replace(/^(start-)/, "");
|
||||||
|
el.text("starting...");
|
||||||
|
api.start_named_server(user, server_name, {
|
||||||
|
success: function () {
|
||||||
|
el.text("start "+server_name).addClass("hidden");
|
||||||
|
row.find(".stop-server").removeClass("hidden");
|
||||||
|
row.find(".access-server").removeClass("hidden");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -17,13 +17,156 @@ require(["jquery", "jhapi"], function($, JHAPI) {
|
|||||||
});
|
});
|
||||||
api.stop_server(user, {
|
api.stop_server(user, {
|
||||||
success: function() {
|
success: function() {
|
||||||
|
if ($("#start").data("named_servers") === true) {
|
||||||
|
$("#start")
|
||||||
|
.text("Start Default Server")
|
||||||
|
.attr("title", "Start the default server")
|
||||||
|
.attr("disabled", false)
|
||||||
|
.off("click");
|
||||||
|
} else {
|
||||||
$("#start")
|
$("#start")
|
||||||
.text("Start My Server")
|
.text("Start My Server")
|
||||||
.attr("title", "Start your server")
|
.attr("title", "Start your server")
|
||||||
.attr("disabled", false)
|
.attr("disabled", false)
|
||||||
.off("click");
|
.off("click");
|
||||||
$("#stop").hide();
|
$("#stop").hide();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//// Named servers buttons
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
let server_name = (this.id).replace(/^(stop-)/, "");
|
||||||
|
// before request
|
||||||
|
$("#stop-"+server_name)
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
$("#goto-"+server_name)
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
// request
|
||||||
|
api.stop_named_server(user, server_name, {
|
||||||
|
success: function() {
|
||||||
|
// after request --> establish final local state
|
||||||
|
$("#stop-"+server_name)
|
||||||
|
.data("state", false)
|
||||||
|
.hide()
|
||||||
|
|
||||||
|
$("#goto-"+server_name)
|
||||||
|
.data("state", false)
|
||||||
|
.hide()
|
||||||
|
|
||||||
|
$("#start-"+server_name)
|
||||||
|
.data("state", false)
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.off("click")
|
||||||
|
.click(start);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function goto() {
|
||||||
|
let server_name = (this.id).replace(/^(goto-)/, "");
|
||||||
|
// before request
|
||||||
|
$("#stop-"+server_name)
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
$("#goto-"+server_name)
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
window.location = base_url+"/user/"+user+"/"+server_name+"/tree"; }
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
let server_name = (this.id).replace(/^(start-)/, "");
|
||||||
|
// before request
|
||||||
|
$("#start-"+server_name)
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
// request
|
||||||
|
api.start_named_server(user, server_name, {
|
||||||
|
success: function() {
|
||||||
|
// after request --> establish final local state
|
||||||
|
$("#stop-"+server_name)
|
||||||
|
.data("state", true)
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.off("click")
|
||||||
|
.click(stop);
|
||||||
|
|
||||||
|
$("#goto-"+server_name)
|
||||||
|
.data("state", true)
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.off("click")
|
||||||
|
.click(goto);
|
||||||
|
|
||||||
|
$("#start-"+server_name)
|
||||||
|
.data("state", true)
|
||||||
|
.hide()
|
||||||
|
}});};
|
||||||
|
|
||||||
|
// Initial state: TRUE (server running) -- local state transitions occur by clicking stop
|
||||||
|
// - stop visible
|
||||||
|
// - goto visible
|
||||||
|
// - start invisible
|
||||||
|
|
||||||
|
$("[data-state='true'][id^='stop-']")
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.click(stop);
|
||||||
|
|
||||||
|
$("[data-state='true'][id^='goto-']")
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.click(goto);
|
||||||
|
|
||||||
|
$("[data-state='true'][id^='start-']")
|
||||||
|
.hide()
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
// Initial state: FALSE (server not running) -- local state transitions occur by clicking start
|
||||||
|
// - stop invisible
|
||||||
|
// - goto invisible
|
||||||
|
// - start visible
|
||||||
|
|
||||||
|
$("[data-state='false'][id^='stop-']")
|
||||||
|
.hide()
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
$("[data-state='false'][id^='goto-']")
|
||||||
|
.hide()
|
||||||
|
.attr("disabled", true)
|
||||||
|
.off("click")
|
||||||
|
.click(function() {
|
||||||
|
return false;});
|
||||||
|
|
||||||
|
$("[data-state='false'][id^='start-']")
|
||||||
|
.show()
|
||||||
|
.attr("disabled", false)
|
||||||
|
.click(start);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -52,6 +52,15 @@ define(['jquery', 'utils'], function ($, utils) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.start_named_server = function (user, server_name, options) {
|
||||||
|
options = options || {};
|
||||||
|
options = update(options, {type: 'POST', dataType: null});
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user, 'servers', server_name),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
JHAPI.prototype.stop_server = function (user, options) {
|
JHAPI.prototype.stop_server = function (user, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options = update(options, {type: 'DELETE', dataType: null});
|
options = update(options, {type: 'DELETE', dataType: null});
|
||||||
@@ -61,6 +70,15 @@ define(['jquery', 'utils'], function ($, utils) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JHAPI.prototype.stop_named_server = function (user, server_name, options) {
|
||||||
|
options = options || {};
|
||||||
|
options = update(options, {type: 'DELETE', dataType: null});
|
||||||
|
this.api_request(
|
||||||
|
utils.url_path_join('users', user, 'servers', server_name),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
JHAPI.prototype.list_users = function (options) {
|
JHAPI.prototype.list_users = function (options) {
|
||||||
this.api_request('users', options);
|
this.api_request('users', options);
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
i.sort-icon {
|
i.sort-icon {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
@@ -40,37 +40,87 @@
|
|||||||
<a id="shutdown-hub" role="button" class="col-xs-2 col-xs-offset-1 btn btn-danger">Shutdown Hub</a>
|
<a id="shutdown-hub" role="button" class="col-xs-2 col-xs-offset-1 btn btn-danger">Shutdown Hub</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for u in users %}
|
{% for user in users %}
|
||||||
<tr class="user-row" data-user="{{u.name}}" data-admin="{{u.admin}}">
|
<tr class="user-row{% if allow_named_servers %} default-server-row{% endif %}" id="user-{{user.name}}" data-user="{{ user.name }}" data-admin="{{user.admin}}">
|
||||||
{% block user_row scoped %}
|
{% block user_row scoped %}
|
||||||
<td class="name-col col-sm-2">{{u.name}}</td>
|
<td class="name-col col-sm-2">{{user.name}}</td>
|
||||||
<td class="admin-col col-sm-2">{% if u.admin %}admin{% endif %}</td>
|
<td class="admin-col col-sm-2">{% if user.admin %}admin{% endif %}</td>
|
||||||
<td class="time-col col-sm-3">
|
<td class="time-col col-sm-3">
|
||||||
{%- if u.last_activity -%}
|
{%- if user.last_activity -%}
|
||||||
{{ u.last_activity.isoformat() + 'Z' }}
|
{{ user.last_activity.isoformat() + 'Z' }}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
Never
|
Never
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</td>
|
</td>
|
||||||
<td class="server-col col-sm-2 text-center">
|
<td class="server-col col-sm-2 text-center">
|
||||||
<a role="button" class="stop-server btn btn-xs btn-danger {% if not u.running %}hidden{% endif %}">stop server</a>
|
<a role="button" class="stop-server btn btn-xs btn-danger{% if not user.running %} hidden{% endif %} default-server" data-named_servers={% if allow_named_servers %}"true"{% else %}"false"{% endif %}>
|
||||||
<a role="button" class="start-server btn btn-xs btn-primary {% if u.running %}hidden{% endif %}">start server</a>
|
{% if allow_named_servers %}
|
||||||
|
stop default server
|
||||||
|
{% else %}
|
||||||
|
stop server
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<a role="button" class="start-server btn btn-xs btn-primary{% if user.running %} hidden{% endif %} default-server" data-named_servers={% if allow_named_servers %}"true"{% else %}"false"{% endif %}>
|
||||||
|
{% if allow_named_servers %}
|
||||||
|
start default server
|
||||||
|
{% else %}
|
||||||
|
start server</a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="server-col col-sm-1 text-center">
|
<td class="server-col col-sm-1 text-center">
|
||||||
{% if admin_access %}
|
{% if admin_access %}
|
||||||
<a role="button" class="access-server btn btn-xs btn-primary {% if not u.running %}hidden{% endif %}">access server</a>
|
<a role="button" class="access-server btn btn-xs btn-primary{% if not user.running %} hidden{% endif %} default-server" data-named_servers={% if allow_named_servers %}"true"{% else %}"false"{% endif %}>
|
||||||
|
{% if allow_named_servers %}
|
||||||
|
access default server
|
||||||
|
{% else %}
|
||||||
|
access server
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="edit-col col-sm-1 text-center">
|
<td class="edit-col col-sm-1 text-center">
|
||||||
<a role="button" class="edit-user btn btn-xs btn-primary">edit</a>
|
<a role="button" class="edit-user btn btn-xs btn-primary">edit{% if allow_named_servers %} {{ user.name }}{% endif %}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="edit-col col-sm-1 text-center">
|
<td class="edit-col col-sm-1 text-center">
|
||||||
{% if u.name != user.name %}
|
{% if user.name != current_user.name %}
|
||||||
<a role="button" class="delete-user btn btn-xs btn-danger">delete</a>
|
<a role="button" class="delete-user btn btn-xs btn-danger">delete{% if allow_named_servers %} {{ user.name }}{% endif %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% if allow_named_servers %}
|
||||||
|
{% for spawner in user.orm_user._orm_spawners %}
|
||||||
|
{% if spawner.name %}
|
||||||
|
<tr class="user-row" data-user="{{user.name}}" data-admin="{{user.admin}}">
|
||||||
|
{# {% block user_row scoped %} #}
|
||||||
|
<td class="name-col col-sm-2"> </td>
|
||||||
|
<td class="admin-col col-sm-2"> </td>
|
||||||
|
<td class="time-col col-sm-3">
|
||||||
|
{%- if spawner.last_activity -%}
|
||||||
|
{{ spawner.last_activity.isoformat() + 'Z' }}
|
||||||
|
{%- else -%}
|
||||||
|
Never
|
||||||
|
{%- endif -%}
|
||||||
|
</td>
|
||||||
|
<td class="server-col col-sm-2 text-center">
|
||||||
|
<a role="button" class="stop-server btn btn-xs btn-danger{% if not spawner.state %} hidden{% endif %}" id="stop-{{ spawner.name }}">stop {{ spawner.name }}</a>
|
||||||
|
<a role="button" class="start-server btn btn-xs btn-primary{% if spawner.state %} hidden{% endif %}" id="start-{{ spawner.name }}">start {{ spawner.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="server-col col-sm-1 text-center">
|
||||||
|
{% if admin_access %}
|
||||||
|
<a role="button" class="access-server btn btn-xs btn-primary{% if not spawner.state %} hidden{% endif %}" id="access-{{ spawner.name }}">access server</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="edit-col col-sm-1 text-center">
|
||||||
|
<!-- edit user button empty for named servers -->
|
||||||
|
</td>
|
||||||
|
<td class="edit-col col-sm-1 text-center">
|
||||||
|
<!-- delete user button empty for named servers -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endblock user_row %}
|
{% endblock user_row %}
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -6,20 +6,76 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{% if user.active %}
|
||||||
|
<div class="row" style="margin: 1%">
|
||||||
|
<div class="text-center">
|
||||||
|
{% if default_server.active %}
|
||||||
|
<a id="stop" role="button" class="btn btn-lg btn-danger">
|
||||||
|
{% if allow_named_servers %}
|
||||||
|
Stop Default Server
|
||||||
|
{% else %}
|
||||||
|
Stop My Server
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% if allow_named_servers %}
|
||||||
|
<a id="start" role="button" class="btn btn-lg btn-primary" href="{{ url }}" data-named_servers="true">
|
||||||
|
Default Server
|
||||||
|
{% else %}
|
||||||
|
<a id="start" role="button" class="btn btn-lg btn-primary" href="{{ url }}" data-named_servers="false">
|
||||||
|
My Server
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a id="start" role="button" class="btn btn-lg btn-primary" href="{{ url }}">
|
||||||
|
Start Default Server
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for spawner in spawners %}
|
||||||
|
{% if spawner.name %}
|
||||||
|
<div class="row" style="margin: 1%">
|
||||||
|
<div class="text-center">
|
||||||
|
{% if spawner.state %}
|
||||||
|
<a id="stop-{{ spawner.name }}" role="button" class="btn btn-lg btn-danger" data-state="true">
|
||||||
|
Stop {{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
<a id="goto-{{ spawner.name }}" role="button" class="btn btn-lg btn-primary" href="{{ url_path_join(url, spawner.name) }}" data-state="true">
|
||||||
|
{{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
<a id="start-{{ spawner.name }}" role="button" class="btn btn-lg btn-primary" data-state="true" style="display: none">
|
||||||
|
Start {{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a id="stop-{{ spawner.name }}" role="button" class="btn btn-lg btn-danger" data-state="false" style="display: none">
|
||||||
|
Stop {{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
<a id="goto-{{ spawner.name }}" role="button" class="btn btn-lg btn-primary" href="{{ url_path_join(url, spawner.name) }}" data-state="false" style="display: none">
|
||||||
|
{{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
<a id="start-{{ spawner.name }}" role="button" class="btn btn-lg btn-primary" data-state="false">
|
||||||
|
Start {{ spawner.name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
{% if user.running %}
|
|
||||||
<a id="stop" role="button" class="btn btn-lg btn-danger">Stop My Server</a>
|
|
||||||
{% endif %}
|
|
||||||
<a id="start" role="button" class="btn btn-lg btn-primary" href="{{ url }}">
|
<a id="start" role="button" class="btn btn-lg btn-primary" href="{{ url }}">
|
||||||
{% if not user.active %}
|
{% if allow_named_servers %}
|
||||||
Start
|
Start a New Server
|
||||||
|
{% else %}
|
||||||
|
Start My Server
|
||||||
{% endif %}
|
{% endif %}
|
||||||
My Server
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user