* Finished (almost) details of master tables

* Added templating engine based on John Resig - http://ejohn.org/,
* Added capacity to scroll to make tables visible
* Added users detail view for authenticators
* added server template serving
This commit is contained in:
Adolfo Gómez 2013-11-15 23:45:05 +00:00
parent d2cf0f6846
commit 6bb538d941
13 changed files with 297 additions and 148 deletions

View File

@ -68,6 +68,8 @@ class Types(ModelTypeHandlerMixin, Handler):
class TableInfo(ModelTableHandlerMixin, Handler):
path = 'authenticators'
detail = { 'users': Users }
title = _('Current authenticators')
fields = [
{ 'name': {'title': _('Name'), 'visible': True } },

View File

@ -36,7 +36,7 @@ from django.utils.translation import ugettext_lazy as _
from uds.models import Network
from uds.REST import Handler, HandlerError
from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin, ModelFakeType
from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin
import logging
@ -52,7 +52,6 @@ class Networks(ModelHandlerMixin, Handler):
'name': item.name,
'net_string': item.net_string,
'networks_count': item.transports.count(),
'type': 'NetworkType',
}
class Types(ModelTypeHandlerMixin, Handler):
@ -60,7 +59,7 @@ class Types(ModelTypeHandlerMixin, Handler):
# Fake mathods, to yield self on enum types and get a "fake" type for Network
def enum_types(self):
yield ModelFakeType('Network', 'NetworkType', 'A description of a network', '')
return []
class TableInfo(ModelTableHandlerMixin, Handler):
path = 'networks'

View File

@ -32,10 +32,10 @@
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _
from django.utils import formats
from uds.models import User
from uds.models import Authenticator, User
from uds.REST.mixins import DetailHandler
@ -78,5 +78,21 @@ class Users(DetailHandler):
except:
logger.exception('En users')
return { 'error': 'not found' }
def getTitle(self):
try:
return _('Users of {0}').format(Authenticator.objects.get(pk=self._kwargs['parent_id']))
except:
return _('Current users')
def getFields(self):
return [
{ 'name': {'title': _('User Id'), 'visible': True } },
{ 'real_name': { 'title': _('Name') } },
{ 'comments': { 'title': _('Comments') } },
{ 'state': { 'title': _('state') } },
{ 'last_access': { 'title': _('Last access') } },
]

View File

@ -38,12 +38,21 @@ import logging
logger = logging.getLogger(__name__)
# Details do not have types at all
# so, right now, we only process details petitions for Handling & tables info
class DetailHandler(object):
def __init__(self, parentHandler, path, *args, **kwargs):
self._parent = parentHandler
self._path = path
self._args = args
self._kwargs = kwargs
# A detail handler must also return title & fields for tables
def getTitle(self):
return ''
def getFields(self):
return []
class ModelHandlerMixin(object):
'''
@ -61,7 +70,7 @@ class ModelHandlerMixin(object):
def getItems(self, *args, **kwargs):
for item in self.model.objects.filter(*args, **kwargs):
try:
try:
yield self.item_as_dict(item)
except:
logger.exception('Exception getting item from {0}'.format(self.model))
@ -91,7 +100,8 @@ class ModelHandlerMixin(object):
item = list(self.getItems(pk=self._args[0]))[0]
except:
return {'error': 'not found' }
class ModelTypeHandlerMixin(object):
'''
As With models, a lot of UDS model contains info about its class.
@ -119,10 +129,12 @@ class ModelTypeHandlerMixin(object):
def get(self):
return list(self.getTypes())
class ModelTableHandlerMixin(object):
authenticated = True
needs_staff = True
detail = None
# Fields should have id of the field, type and length
# All options can be ommited
@ -135,11 +147,29 @@ class ModelTableHandlerMixin(object):
fields = []
title = ''
def processDetail(self):
logger.debug('Processing detail for table')
try:
detailCls = self.detail[self._args[1]]
args = list(self._args[2:])
path = self._path + '/'.join(args[:2])
detail = detailCls(self, path, parent_id = self._args[0])
return (detail.getTitle(), detail.getFields())
except:
return ([], '')
def get(self):
# Convert to unicode fields (ugettext_lazy needs to be rendered before passing it to Json
fields = [ { 'id' : {'visible': False } } ] # Always add id column as invisible
for f in self.fields:
if len(self._args) > 1:
title, fields = self.processDetail()
else:
# Convert to unicode fields (ugettext_lazy needs to be rendered before passing it to Json
title = self.title
fields = self.fields # Always add id column as invisible
processedFields = [{ 'id' : {'visible': False, 'sortable': False, 'searchable': False } }]
for f in fields:
for k1, v1 in f.iteritems():
dct = {}
for k2, v2 in v1.iteritems():
@ -147,16 +177,6 @@ class ModelTableHandlerMixin(object):
dct[k2] = v2
else:
dct[k2] = unicode(v2)
fields.append({k1: dct})
return { 'title': unicode(self.title), 'fields': fields };
processedFields.append({k1: dct})
return { 'title': unicode(title), 'fields': processedFields };
# Fake type for models that do not needs typing
class ModelFakeType(object):
def __init__(self, name, type_, description, icon):
self._name, self._type, self._description, self._icon = name, type_, description, icon
def name(self): return self._name
def type(self): return self._type
def description(self): return self._description
def icon(self): return self._icon

View File

@ -37,5 +37,6 @@ from uds import REST
urlpatterns = patterns('uds.admin.views',
(r'^$', 'index'),
(r'^tmpl/(?P<template>[a-zA-Z0-9]*)$', 'tmpl'),
(r'^sample$', 'sample'),
)

View File

@ -31,6 +31,8 @@
from __future__ import unicode_literals
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.template import RequestContext, loader
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
from django.utils.translation import ugettext as _
@ -54,6 +56,19 @@ def index(request):
return render(request, 'uds/admin/index.html')
@denyBrowsers(browsers=['ie<9'])
@webLoginRequired
def tmpl(request, template):
if request.user.isStaff() is False:
return HttpResponseForbidden(_('Forbidden'))
try:
t = loader.get_template('uds/admin/tmpl/' + template + ".html")
c = RequestContext(request)
resp = t.render(c)
except:
resp = _('requested a template that do not exists')
return HttpResponse(resp, content_type="text/plain");
@denyBrowsers(browsers=['ie<9'])
@webLoginRequired
def sample(request):

View File

@ -20,8 +20,8 @@
type: "GET",
dataType: "json",
success: function(data) {
api.doLog(gettext('Success on "') + url + '".');
api.doLog(gettext('Received ') + JSON.stringify(data));
api.doLog('Success on "' + url + '".');
api.doLog('Received ' + JSON.stringify(data));
if( success_fnc != undefined ){
api.doLog('Executing success method')
success_fnc(data);
@ -48,16 +48,16 @@ function BasicModelRest(path) {
}
BasicModelRest.prototype = {
get: function(options) {
get: function(options, alternate_url) {
if( options == undefined ){
options = {};
}
var path = this.path;
var path = alternate_url || this.path;
if( options.id != undefined )
path += '/' + options.id;
api.getJson(path, options.success);
},
types: function(success_fnc) {
types: function(success_fnc, alternate_url) {
// Cache types locally, will not change unless new broker version
if( this.cached_types ) {
if( success_fnc ) {
@ -66,8 +66,10 @@ BasicModelRest.prototype = {
}
else {
var $this = this;
api.getJson( this.path + '/types', function(data) {
var path = this.path + '/types';
if( alternate_url != undefined )
path = alternate_url;
api.getJson( path, function(data) {
$this.cached_types = data;
if( success_fnc ) {
success_fnc($this.cached_types);
@ -76,7 +78,7 @@ BasicModelRest.prototype = {
}
},
tableInfo: function(success_fnc) {
tableInfo: function(success_fnc, alternate_url) {
// Cache types locally, will not change unless new broker version
if( this.cached_tableInfo ) {
if( success_fnc ) {
@ -85,8 +87,11 @@ BasicModelRest.prototype = {
return;
}
var $this = this;
var path = this.path + '/tableinfo';
if( alternate_url != undefined )
path = alternate_url;
api.getJson( this.path + '/tableinfo', function(data) {
api.getJson( path, function(data) {
$this.cached_tableInfo = data;
if( success_fnc ) {
success_fnc($this.cached_tableInfo);
@ -104,11 +109,24 @@ function DetailModelRestApi(parentApi, path) {
}
DetailModelRestApi.prototype = {
// Generates a basic model with fixed methods for "detail" models
detail: function(parentId) {
var $this = this;
var rest = new BasicModelRest(this.parentPath + '/' + parentId + '/' + this.path);
// Overwrite types, detail do not have types
rest.types = function() {
return []; // No types at all
}
// And overwrite tableInfo
var parentTableInfo = rest.tableInfo;
rest.tableInfo = function(success_fnc, alternate_url) {
if( alternate_url == undefined )
alternate_url = $this.parentPath + '/tableinfo/' + parentId + '/' + $this.path;
parentTableInfo( success_fnc, alternate_url )
}
return rest;
}
};
@ -122,4 +140,66 @@ api.authenticators.users = new DetailModelRestApi(api.authenticators, 'users');
api.osmanagers = new BasicModelRest('osmanagers');
api.transports = new BasicModelRest('transports');
api.networks = new BasicModelRest('networks');
api.networks = new BasicModelRest('networks');
// -------------------------------
// Templates related
// This is not part of REST api provided by UDS, but it's part of the api needed for the admin app
// -------------------------------
(function(templates, $){
templates.cache = {}; // Will cache templates locally. If name contains '?', data will not be cached and always re-requested
templates.get = function(name, success_fnc) {
if( !name.contains('?') ) {
if( templates.cache[name] != undefined ) {
if( success_fnc != undefined ) {
success_fnc(templates.cache[name]);
}
return;
}
}
$.ajax({
url: '/adm/tmpl/' + name,
type: "GET",
dataType: "text",
success: function(data) {
templates.cache[name] = data;
api.doLog('Success getting template "' + name + '".');
api.doLog('Received: ' + data);
if( success_fnc != undefined ){
api.doLog('Executing success method')
success_fnc(data);
}
},
});
};
// Simple JavaScript Templating
// Based on John Resig - http://ejohn.org/ - MIT Licensed
templates.eval = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn =
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
}(api.templates = api.templates || {}, jQuery));

View File

@ -0,0 +1,87 @@
// Compose gui elements
// Service providers
gui.providers = new GuiElement(api.providers, 'provi');
gui.providers.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Service Providers')));
var tableId = gui.providers.table({
rowSelect: 'multi',
rowSelectFnc: function(nodes){
gui.doLog(nodes);
gui.doLog(this);
gui.doLog(this.fnGetSelectedData());
},
buttons: ['edit', 'refresh', 'delete'],
});
return false;
};
// --------------..
// Authenticators
// ---------------
gui.authenticators = new GuiElement(api.authenticators, 'auth');
gui.authenticators.link = function(event) {
api.templates.get('authenticators', function(tmpl){
gui.clearWorkspace();
gui.appendToWorkspace(api.templates.eval(tmpl, { auths: 'auths-placeholder', users: 'users-placeholder' }));
gui.setLinksEvents();
gui.authenticators.table({
container: 'auths-placeholder',
rowSelect: 'single',
buttons: ['edit', 'refresh', 'delete'],
rowSelectFnc: function(nodes){
var id = this.fnGetSelectedData()[0].id;
var user = new GuiElement(api.authenticators.users.detail(id), 'users');
user.table({
container: 'users-placeholder',
rowSelect: 'multi',
buttons: ['edit', 'refresh', 'delete'],
scroll: true,
});
return false;
},
});
});
return false;
};
gui.osmanagers = new GuiElement(api.osmanagers, 'osm');
gui.osmanagers.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs('Os Managers'));
gui.osmanagers.table({
rowSelect: 'single',
buttons: ['edit', 'refresh', 'delete'],
});
return false;
};
gui.connectivity = {
transports: new GuiElement(api.transports, 'trans'),
networks: new GuiElement(api.networks, 'nets'),
};
gui.connectivity.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Connectivity')));
gui.appendToWorkspace('<div class="row"><div class="col-lg-6" id="ttbl"></div><div class="col-lg-6" id="ntbl"></div></div>');
gui.connectivity.transports.table({
rowSelect: 'multi',
container: 'ttbl',
buttons: ['edit', 'refresh', 'delete', 'pdf'],
});
gui.connectivity.networks.table({
rowSelect: 'single',
container: 'ntbl',
buttons: ['edit', 'refresh', 'delete'],
});
}

View File

@ -15,7 +15,7 @@
// Several convenience "constants"
gui.dataTablesLanguage = {
"sLengthMenu": gettext("_MENU_ records per page"),
"sZeroRecords": gettext("Nothing found - sorry"),
"sZeroRecords": gettext("Empty"),
"sInfo": gettext("Records _START_ to _END_ of _TOTAL_"),
"sInfoEmpty": gettext("No records"),
"sInfoFiltered": gettext("(filtered from _MAX_ total records)"),
@ -88,13 +88,13 @@
];
$.each(sidebarLinks, function(index, value){
gui.doLog('Adding ' + value.id)
$('.'+value.id).unbind('click').click(value.exec);
// Navbar click so navbar is closed...
$('.nav a').on('click', function(){
if($('.navbar-toggle').css('display') !='none'){
$('.'+value.id).unbind('click').click(function(event) {
if($('.navbar-toggle').css('display') !='none') {
$(".navbar-toggle").trigger( "click" );
}
});
$('html, body').scrollTop(0);
value.exec(event);
});
});
}
@ -162,6 +162,8 @@ GuiElement.prototype = {
column.bVisible = options.visible;
if( options.sortable != undefined )
column.bSortable = options.sortable;
if( options.searchable != undefined )
column.bSearchable = options.searchable;
// Fix name columm so we can add a class icon
if( v == 'name' ) {
@ -174,10 +176,17 @@ GuiElement.prototype = {
gui.doLog(columns);
var processResponse = function(data) {
$.each(data, function(index, value){
var type = $this.types[value.type];
data[index].name = '<span class="' + type.css + '"> </span> ' + value.name
});
// If it has a "type" column
try {
if( data[0].type != undefined ) {
$.each(data, function(index, value){
var type = $this.types[value.type];
data[index].name = '<span class="' + type.css + '"> </span> ' + value.name
});
}
} catch (e) {
return;
}
};
$this.rest.get({
@ -319,6 +328,11 @@ GuiElement.prototype = {
});
$('#' + tableId + '_filter input').addClass('form-control');
var tableTop = $('#'+tableId).offset().top;
gui.doLog(tableTop);
//$('html, body').animate({ scrollTop: tableTop });
if( options.scroll )
$('html, body').scrollTop(tableTop);
}
});
});
@ -326,73 +340,3 @@ GuiElement.prototype = {
}
};
// Compose gui API
// Service providers
gui.providers = new GuiElement(api.providers, 'provi');
gui.providers.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Service Providers')));
var tableId = gui.providers.table({
rowSelect: 'multi',
rowSelectFnc: function(nodes){
gui.doLog(nodes);
gui.doLog(this);
gui.doLog(this.fnGetSelectedData());
},
buttons: ['edit', 'refresh', 'delete'],
});
return false;
};
gui.authenticators = new GuiElement(api.authenticators, 'auth');
gui.authenticators.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Authenticators')));
gui.authenticators.table({
rowSelect: 'single',
buttons: ['edit', 'refresh', 'delete'],
});
return false;
};
gui.osmanagers = new GuiElement(api.osmanagers, 'osm');
gui.osmanagers.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs('Os Managers'));
gui.osmanagers.table({
rowSelect: 'single',
buttons: ['edit', 'refresh', 'delete'],
});
return false;
};
gui.connectivity = {
transports: new GuiElement(api.transports, 'trans'),
networks: new GuiElement(api.networks, 'nets'),
};
gui.connectivity.link = function(event) {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Connectivity')));
gui.appendToWorkspace('<div class="row"><div class="col-lg-6" id="ttbl"></div><div class="col-lg-6" id="ntbl"></div></div>');
gui.connectivity.transports.table({
rowSelect: 'multi',
container: 'ttbl',
buttons: ['edit', 'refresh', 'delete', 'pdf'],
});
gui.connectivity.networks.table({
rowSelect: 'single',
container: 'ntbl',
buttons: ['edit', 'refresh', 'delete'],
});
}

View File

@ -1,35 +1 @@
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {};
this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<$").join("\t")
.replace(/((^|$>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)$>/g, "',$1,'")
.split("\t").join("');")
.split("$>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
})();

View File

@ -72,6 +72,7 @@
</script>
<script src="{% get_static_prefix %}adm/js/api.js"></script>
<script src="{% get_static_prefix %}adm/js/gui.js"></script>
<script src="{% get_static_prefix %}adm/js/gui-elements.js"></script>
<script>
$(function() {

View File

@ -0,0 +1,18 @@
{% load i18n html5 static %}
<div class="row">
<div class="col-xs-12">
<h1>{% trans 'Authenticators' %} <small>{% trans 'administration of authenticators' %}</small></h1>
<ol class="breadcrumb">
<li><a class="lnk-dashboard" href="#"><i class="fa fa-dashboard"></i> Dashboard</a></li>
<li>otra cosa</li>
</ol>
</div>
</div><!-- /.row -->
<div class="row">
<div id="<%= auths %>" class="col-xs-12">
</div>
</div>
<div class="row">
<div id="<%= users %>" class="col-xs-12">
</div>
</div>

View File

@ -62,7 +62,7 @@ urlpatterns = patterns('uds',
# Change Language
(r'^i18n/', include('django.conf.urls.i18n')),
# Downloadables
(r'^download/(?P<idDownload>.*)$', 'web.views.download'),
(r'^download/(?P<idDownload>[a-zA-Z0-9]*)$', 'web.views.download'),
# Custom authentication callback
(r'^auth/(?P<authName>.+)', 'web.views.authCallback'),
(r'^authJava/(?P<idAuth>.+)/(?P<hasJava>.*)$', 'web.views.authJava'),