mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 20:13:02 +00:00
allow sorting admin columns
This commit is contained in:
@@ -45,10 +45,47 @@ class AdminHandler(BaseHandler):
|
|||||||
|
|
||||||
@admin_only
|
@admin_only
|
||||||
def get(self):
|
def get(self):
|
||||||
|
available = {'name', 'admin', 'running', 'last_activity'}
|
||||||
|
default_sort = ['admin', 'name']
|
||||||
|
mapping = {
|
||||||
|
'running': '_server_id'
|
||||||
|
}
|
||||||
|
default_order = {
|
||||||
|
'name': 'asc',
|
||||||
|
'last_activity': 'desc',
|
||||||
|
'admin': 'desc',
|
||||||
|
'running': 'desc',
|
||||||
|
}
|
||||||
|
sorts = self.get_arguments('sort') or default_sort
|
||||||
|
orders = self.get_arguments('order')
|
||||||
|
|
||||||
|
for bad in set(sorts).difference(available):
|
||||||
|
self.log.warn("ignoring invalid sort: %r", bad)
|
||||||
|
sorts.remove(bad)
|
||||||
|
for bad in set(orders).difference({'asc', 'desc'}):
|
||||||
|
self.log.warn("ignoring invalid order: %r", bad)
|
||||||
|
orders.remove(bad)
|
||||||
|
|
||||||
|
# add default sort as secondary
|
||||||
|
for s in default_sort:
|
||||||
|
if s not in sorts:
|
||||||
|
sorts.append(s)
|
||||||
|
if len(orders) < len(sorts):
|
||||||
|
for col in sorts[len(orders):]:
|
||||||
|
orders.append(default_order[col])
|
||||||
|
else:
|
||||||
|
orders = orders[:len(sorts)]
|
||||||
|
|
||||||
|
# this could be one incomprehensible nested list comprehension
|
||||||
|
# get User columns
|
||||||
|
cols = [ getattr(orm.User, mapping.get(c, c)) for c in sorts ]
|
||||||
|
# get User.col.desc() order objects
|
||||||
|
ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ]
|
||||||
html = self.render_template('admin.html',
|
html = self.render_template('admin.html',
|
||||||
user=self.get_current_user(),
|
user=self.get_current_user(),
|
||||||
users=self.db.query(orm.User),
|
users=self.db.query(orm.User).order_by(*ordered),
|
||||||
admin_access=self.settings.get('admin_access', False),
|
admin_access=self.settings.get('admin_access', False),
|
||||||
|
sort={s:o for s,o in zip(sorts, orders)},
|
||||||
)
|
)
|
||||||
self.finish(html)
|
self.finish(html)
|
||||||
|
|
||||||
|
@@ -9,12 +9,46 @@ require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, mo
|
|||||||
|
|
||||||
var api = new JHAPI(base_url);
|
var api = new JHAPI(base_url);
|
||||||
|
|
||||||
var get_row = function (element) {
|
function get_row (element) {
|
||||||
while (!element.hasClass("user-row")) {
|
while (!element.hasClass("user-row")) {
|
||||||
element = element.parent();
|
element = element.parent();
|
||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function resort (col, order) {
|
||||||
|
var query = window.location.search.slice(1).split('&');
|
||||||
|
// if col already present in args, remove it
|
||||||
|
var i = 0;
|
||||||
|
while (i < query.length) {
|
||||||
|
if (query[i] === 'sort=' + col) {
|
||||||
|
query.splice(i,1);
|
||||||
|
if (query[i] && query[i].substr(0, 6) === 'order=') {
|
||||||
|
query.splice(i,1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add new order to the front
|
||||||
|
if (order) {
|
||||||
|
query.unshift('order=' + order);
|
||||||
|
}
|
||||||
|
query.unshift('sort=' + col);
|
||||||
|
// reload page with new order
|
||||||
|
window.location = window.location.pathname + '?' + query.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
$("th").map(function (i, th) {
|
||||||
|
th = $(th);
|
||||||
|
var col = th.data('sort');
|
||||||
|
var order = th.find('i').hasClass('fa-sort-desc') ? 'asc':'desc';
|
||||||
|
th.find('a').click(
|
||||||
|
function () {
|
||||||
|
resort(col, order);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
$(".time-col").map(function (i, el) {
|
$(".time-col").map(function (i, el) {
|
||||||
// convert ISO datestamps to nice momentjs ones
|
// convert ISO datestamps to nice momentjs ones
|
||||||
|
3
share/jupyter/hub/static/less/admin.less
Normal file
3
share/jupyter/hub/static/less/admin.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
i.sort-icon {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
@import "./variables.less";
|
@import "./variables.less";
|
||||||
@import "./page.less";
|
@import "./page.less";
|
||||||
|
@import "./admin.less";
|
||||||
@import "./error.less";
|
@import "./error.less";
|
||||||
@import "./logout.less";
|
@import "./logout.less";
|
||||||
@import "./login.less";
|
@import "./login.less";
|
||||||
|
@@ -1,13 +1,28 @@
|
|||||||
{% extends "page.html" %}
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% macro th(key, label, order, colspan) %}
|
||||||
|
<th data-sort="{{key}}" colspan="{{colspan}}">{{label}}
|
||||||
|
<a href="#"><i class="fa {% if order == 'asc' -%}
|
||||||
|
fa-sort-asc
|
||||||
|
{%- elif order == 'desc' -%}
|
||||||
|
fa-sort-desc
|
||||||
|
{%- else -%}
|
||||||
|
fa-sort
|
||||||
|
{%- endif %} sort-icon">
|
||||||
|
</i></a>
|
||||||
|
</th>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">User</th>
|
{{ th('name', "User", sort.get('name'), 1) }}
|
||||||
<th colspan="3">Last Seen</th>
|
{{ th('admin', "Admin", sort.get('admin'), 1) }}
|
||||||
|
{{ th('last_activity', "Last Seen", sort.get('last_activity'), 1) }}
|
||||||
|
{{ th('running', "Running", sort.get('running'), 2) }}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
Reference in New Issue
Block a user