1
0
mirror of https://github.com/Yubico/yubiadmin.git synced 2024-11-29 10:24:11 +01:00

Started adding client table for YKVAL.

This commit is contained in:
Dain Nilsson 2013-05-17 17:14:03 +02:00
parent 68f8c9c987
commit 5e79f93ea2
7 changed files with 211 additions and 85 deletions

View File

@ -26,13 +26,12 @@
# POSSIBILITY OF SUCH DAMAGE.
import os
import re
from wtforms import Form
from wtforms.fields import (SelectField, TextField, BooleanField, IntegerField,
PasswordField)
from wtforms.widgets import PasswordInput
from wtforms.validators import NumberRange, URL, EqualTo, Regexp, Optional
from yubiadmin.util.app import App, render
from yubiadmin.util.app import App, CollectionApp, render
from yubiadmin.util.config import (python_handler, python_list_handler,
FileConfig)
from yubiadmin.util.form import ConfigForm, FileForm, ListField
@ -291,31 +290,29 @@ class AssignYubiKeyForm(Form):
self.auth.commit()
class YubiAuthUsers(App):
user_range = re.compile('(\d+)-(\d+)')
class YubiAuthUsers(CollectionApp):
base_url = '/auth/users'
item_name = 'Users'
caption = 'YubiAuth Users'
columns = ['Username', 'YubiKeys']
template = 'auth/list'
def __init__(self):
self.auth = YubiAuth()
def __call__(self, request):
sub_cmd = request.path_info_pop()
if sub_cmd == 'create':
return self.create(request)
elif sub_cmd == 'delete':
return self.delete(request)
elif sub_cmd == 'delete_confirm':
return self.delete_confirm(request)
elif sub_cmd == 'user':
return self.show_user(request)
else:
match = self.user_range.match(sub_cmd) if sub_cmd else None
if match:
offset = int(match.group(1)) - 1
limit = int(match.group(2)) - offset
else:
offset = 0
limit = 10
return self.list_users(offset, limit)
def size(self):
return self.auth.session.query(User).count()
def get(self, offset, limit):
users = self.auth.session.query(User).order_by(User.name) \
.offset(offset).limit(limit)
return map(lambda user: {
'id': user.id,
'Username': '<a href="/auth/users/show/%d">%s</a>' % (user.id,
user.name),
'YubiKeys': ', '.join(user.yubikeys.keys())
}, users)
def create(self, request):
return self.render_forms(request, [CreateUserForm(self.auth)],
@ -333,28 +330,7 @@ class YubiAuthUsers(App):
self.auth.commit()
return self.redirect('/auth/users')
def list_users(self, offset, limit):
users = self.auth.session.query(User).order_by(User.name) \
.offset(offset).limit(limit)
num_users = self.auth.session.query(User).count()
shown = (min(offset + 1, num_users), min(offset + limit, num_users))
if offset > 0:
st = max(0, offset - limit)
ed = st + limit
prev = '/auth/users/%d-%d' % (st + 1, ed)
else:
prev = None
if num_users > shown[1]:
next = '/auth/users/%d-%d' % (offset + limit + 1, shown[1] + limit)
else:
next = None
return render(
'auth/list', script='auth', users=users, offset=offset,
limit=limit, num_users=num_users, shown='%d-%d' % shown, prev=prev,
next=next)
def show_user(self, request):
def show(self, request):
id = int(request.path_info_pop())
user = self.auth.get_user(id)
if 'unassign' in request.params:

View File

@ -29,11 +29,11 @@ import re
import os
from wtforms.fields import IntegerField
from wtforms.validators import NumberRange, IPAddress, URL
from yubiadmin.util.app import App
from yubiadmin.util.app import App, CollectionApp
from yubiadmin.util.config import (RegexHandler, FileConfig, php_inserter,
parse_block, strip_comments, strip_quotes)
from yubiadmin.util.form import ConfigForm, FileForm, DBConfigForm, ListField
from yubiadmin.util.system import invoke_rc_d
from yubiadmin.util.system import invoke_rc_d, run
__all__ = [
'app'
@ -210,15 +210,25 @@ class YubikeyVal(App):
"""
name = 'val'
sections = ['general', 'database', 'synchronization', 'ksms', 'advanced']
sections = ['general', 'clients', 'database', 'synchronization', 'ksms',
'advanced']
disabled = not os.path.isfile(YKVAL_CONFIG_FILE)
def __init__(self):
self._clients = YubikeyValClients()
def general(self, request):
"""
General
"""
return self.render_forms(request, [SyncLevelsForm(), MiscForm()])
def clients(self, request):
"""
API Clients
"""
return self._clients(request)
def database(self, request):
"""
Database Settings
@ -260,7 +270,40 @@ class YubikeyVal(App):
FileForm(YKVAL_CONFIG_FILE, 'Configuration')
])
#Pulls the tab to the right:
# Pulls the tab to the right:
advanced.advanced = True
class YubikeyValClients(CollectionApp):
base_url = '/val/clients'
item_name = 'Clients'
caption = 'Client API Keys'
columns = ['Client ID', 'API Key']
template = 'val/client_list'
def __call__(self, request):
self._data = None
return super(YubikeyValClients, self).__call__(request)
@property
def data(self):
if self._data is None:
self._data = []
status, output = run('ykval-export-clients')
for line in output.splitlines():
parts = line.split(',')
self._data.append({
'id': parts[0],
'Client ID': parts[0],
'API Key': parts[3]
})
return self._data
def size(self):
return len(self.data)
def get(self, offset, limit):
return self.data[offset:offset + limit]
app = YubikeyVal()

View File

@ -0,0 +1,17 @@
$(document).ready(function() {
$('#delete_btn').attr('disabled', 'disabled');
$('#toggle_all').change(function() {
$('tbody :checkbox').prop('checked', $(this).is(':checked'));
});
$(':checkbox').change(function() {
if($('tbody :checkbox:checked').length > 0) {
console.log('enable');
$('#delete_btn').removeAttr('disabled');
} else {
console.log('disable');
$('#delete_btn').attr('disabled', 'disabled');
}
});
});

View File

@ -1,40 +1,8 @@
<form action="/auth/users/delete" method="post">
{% from 'table.html' import table %}
<table class="table table-striped table-condensed">
<caption>YubiAuth users</caption>
<thead>
<tr>
<th style="width: 5%"><input type="checkbox" id="toggle_all" /></th>
<th style="width: 15%">Username</th>
<th style="width: 15%">YubiKeys</th>
<th style="width: 65%; text-align: right;">
Users {{ shown }} of {{ num_users }}
&nbsp;
<div class="btn-group">
{% if prev %}
<a class="btn btn-small" href="{{ prev }}">Prev</a>
{% else %}
<a class="btn btn-small disabled">Prev</a>
{% endif %}
{% if next %}
<a class="btn btn-small" href="{{ next }}">Next</a>
{% else %}
<a class="btn btn-small disabled">Next</a>
{% endif %}
</div>
</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td><input type="checkbox" name="user/{{ user.id }}"/></td>
<td><a href="/auth/users/user/{{ user.id }}">{{ user.name }}</a></td>
<td colspan="2">{{ user.yubikeys | join(', ') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<form action="/auth/users/delete" method="post">
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}
<input id="delete_btn" type="submit" class="btn btn-danger" value="Delete selected" />
<a href="/auth/users/create" class="btn btn-primary pull-right">Create new user</a>

View File

@ -0,0 +1,57 @@
{% macro header(cols, next=None, prev=None, shown=0, total=0, item_name='Items') %}
<thead>
<tr>
<th style="width: 5%">
<input type="checkbox" id="toggle_all" />
</th>
{% for col in cols %}
<th style="width: 15%">
{{ col }}
</th>
{% endfor %}
{% set col_len = cols|length %}
<th style="width: {{ 95 - col_len * 15 }}%; text-align:right;">
{{ item_name }} {{ shown }} of {{ total }}
&nbsp;
<div class="btn-group">
{% if prev %}
<a class="btn btn-small" href="{{ prev }}">Prev</a>
{% else %}
<a class="btn btn-small disabled">Prev</a>
{% endif %}
{% if next %}
<a class="btn btn-small" href="{{ next }}">Next</a>
{% else %}
<a class="btn btn-small disabled">Next</a>
{% endif %}
</div>
</th>
</tr>
</thead>
{% endmacro %}
{% macro body(cols, items) %}
<tbody>
{% for item in items %}
<tr>
<td><input type="checkbox" name="item/{{ item.id }}"/></td>
{% for col in cols %}
<td{% if loop.last %} colspan="2"{% endif %}>{{ item[col] }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
{% endmacro %}
{% macro table(cols, items, caption=None, next=None, prev=None, shown=0, total=0, item_name='Items') %}
<table class="table table-striped table-condensed">
{% if caption %}
<caption>{{ caption }}</caption>
{% endif %}
{{ header(cols, next, prev, shown, total, item_name) }}
{{ body(cols, items) }}
</table>
{% endmacro %}
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}

View File

@ -0,0 +1,9 @@
{% from 'table.html' import table %}
<form action="/val/clients/delete" method="post">
{{ table(cols, items, caption, next, prev, shown, total, item_name) }}
<input id="delete_btn" type="submit" class="btn btn-danger" value="Delete selected" />
<a href="/val/clients/create" class="btn btn-primary pull-right">Create new API client</a>
</form>

View File

@ -26,12 +26,14 @@
# POSSIBILITY OF SUCH DAMAGE.
import os
import re
from jinja2 import Environment, FileSystemLoader
from webob import exc
from webob.dec import wsgify
__all__ = [
'App',
'CollectionApp',
'render',
'populate_forms',
]
@ -113,3 +115,57 @@ class App(object):
return render(template, target=request.path, fieldsets=forms,
alert=alert, **kwargs)
ITEM_RANGE = re.compile('(\d+)-(\d+)')
class CollectionApp(App):
base_url = ''
caption = 'Items'
item_name = 'Items'
columns = []
template = 'table'
script = 'table'
def size(self):
return 0
def get(self, offset, limit):
return [{}]
def __call__(self, request):
sub_cmd = request.path_info_pop()
if sub_cmd and hasattr(self, sub_cmd):
return getattr(self, sub_cmd)(request)
else:
match = ITEM_RANGE.match(sub_cmd) if sub_cmd else None
if match:
offset = int(match.group(1)) - 1
limit = int(match.group(2)) - offset
else:
offset = 0
limit = 10
return self.list(offset, limit)
def list(self, offset, limit):
items = self.get(offset, limit)
total = self.size()
shown = (min(offset + 1, total), min(offset + limit, total))
if offset > 0:
st = max(0, offset - limit)
ed = st + limit
prev = '%s/%d-%d' % (self.base_url, st + 1, ed)
else:
prev = None
if total > shown[1]:
next = '%s/%d-%d' % (self.base_url, offset + limit + 1, shown[1]
+ limit)
else:
next = None
return render(
self.template, script=self.script, items=items, offset=offset,
limit=limit, total=total, shown='%d-%d' % shown, prev=prev,
next=next, base_url=self.base_url, caption=self.caption,
cols=self.columns, item_name=self.item_name)