1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +03:00

* 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): class TableInfo(ModelTableHandlerMixin, Handler):
path = 'authenticators' path = 'authenticators'
detail = { 'users': Users }
title = _('Current authenticators') title = _('Current authenticators')
fields = [ fields = [
{ 'name': {'title': _('Name'), 'visible': True } }, { '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.models import Network
from uds.REST import Handler, HandlerError 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 import logging
@ -52,7 +52,6 @@ class Networks(ModelHandlerMixin, Handler):
'name': item.name, 'name': item.name,
'net_string': item.net_string, 'net_string': item.net_string,
'networks_count': item.transports.count(), 'networks_count': item.transports.count(),
'type': 'NetworkType',
} }
class Types(ModelTypeHandlerMixin, Handler): 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 # Fake mathods, to yield self on enum types and get a "fake" type for Network
def enum_types(self): def enum_types(self):
yield ModelFakeType('Network', 'NetworkType', 'A description of a network', '') return []
class TableInfo(ModelTableHandlerMixin, Handler): class TableInfo(ModelTableHandlerMixin, Handler):
path = 'networks' path = 'networks'

View File

@ -32,10 +32,10 @@
''' '''
from __future__ import unicode_literals 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 django.utils import formats
from uds.models import User from uds.models import Authenticator, User
from uds.REST.mixins import DetailHandler from uds.REST.mixins import DetailHandler
@ -79,4 +79,20 @@ class Users(DetailHandler):
logger.exception('En users') logger.exception('En users')
return { 'error': 'not found' } 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,6 +38,8 @@ import logging
logger = logging.getLogger(__name__) 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): class DetailHandler(object):
def __init__(self, parentHandler, path, *args, **kwargs): def __init__(self, parentHandler, path, *args, **kwargs):
self._parent = parentHandler self._parent = parentHandler
@ -45,6 +47,13 @@ class DetailHandler(object):
self._args = args self._args = args
self._kwargs = kwargs self._kwargs = kwargs
# A detail handler must also return title & fields for tables
def getTitle(self):
return ''
def getFields(self):
return []
class ModelHandlerMixin(object): class ModelHandlerMixin(object):
''' '''
Basic Handler for a model Basic Handler for a model
@ -92,6 +101,7 @@ class ModelHandlerMixin(object):
except: except:
return {'error': 'not found' } return {'error': 'not found' }
class ModelTypeHandlerMixin(object): class ModelTypeHandlerMixin(object):
''' '''
As With models, a lot of UDS model contains info about its class. As With models, a lot of UDS model contains info about its class.
@ -120,9 +130,11 @@ class ModelTypeHandlerMixin(object):
def get(self): def get(self):
return list(self.getTypes()) return list(self.getTypes())
class ModelTableHandlerMixin(object): class ModelTableHandlerMixin(object):
authenticated = True authenticated = True
needs_staff = True needs_staff = True
detail = None
# Fields should have id of the field, type and length # Fields should have id of the field, type and length
# All options can be ommited # All options can be ommited
@ -136,10 +148,28 @@ class ModelTableHandlerMixin(object):
fields = [] fields = []
title = '' 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): def get(self):
# Convert to unicode fields (ugettext_lazy needs to be rendered before passing it to Json if len(self._args) > 1:
fields = [ { 'id' : {'visible': False } } ] # Always add id column as invisible title, fields = self.processDetail()
for f in self.fields: 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(): for k1, v1 in f.iteritems():
dct = {} dct = {}
for k2, v2 in v1.iteritems(): for k2, v2 in v1.iteritems():
@ -147,16 +177,6 @@ class ModelTableHandlerMixin(object):
dct[k2] = v2 dct[k2] = v2
else: else:
dct[k2] = unicode(v2) dct[k2] = unicode(v2)
fields.append({k1: dct}) processedFields.append({k1: dct})
return { 'title': unicode(self.title), 'fields': fields }; 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', urlpatterns = patterns('uds.admin.views',
(r'^$', 'index'), (r'^$', 'index'),
(r'^tmpl/(?P<template>[a-zA-Z0-9]*)$', 'tmpl'),
(r'^sample$', 'sample'), (r'^sample$', 'sample'),
) )

View File

@ -31,6 +31,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.template import RequestContext, loader
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -54,6 +56,19 @@ def index(request):
return render(request, 'uds/admin/index.html') 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']) @denyBrowsers(browsers=['ie<9'])
@webLoginRequired @webLoginRequired
def sample(request): def sample(request):

View File

@ -20,8 +20,8 @@
type: "GET", type: "GET",
dataType: "json", dataType: "json",
success: function(data) { success: function(data) {
api.doLog(gettext('Success on "') + url + '".'); api.doLog('Success on "' + url + '".');
api.doLog(gettext('Received ') + JSON.stringify(data)); api.doLog('Received ' + JSON.stringify(data));
if( success_fnc != undefined ){ if( success_fnc != undefined ){
api.doLog('Executing success method') api.doLog('Executing success method')
success_fnc(data); success_fnc(data);
@ -48,16 +48,16 @@ function BasicModelRest(path) {
} }
BasicModelRest.prototype = { BasicModelRest.prototype = {
get: function(options) { get: function(options, alternate_url) {
if( options == undefined ){ if( options == undefined ){
options = {}; options = {};
} }
var path = this.path; var path = alternate_url || this.path;
if( options.id != undefined ) if( options.id != undefined )
path += '/' + options.id; path += '/' + options.id;
api.getJson(path, options.success); 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 // Cache types locally, will not change unless new broker version
if( this.cached_types ) { if( this.cached_types ) {
if( success_fnc ) { if( success_fnc ) {
@ -66,8 +66,10 @@ BasicModelRest.prototype = {
} }
else { else {
var $this = this; var $this = this;
var path = this.path + '/types';
api.getJson( this.path + '/types', function(data) { if( alternate_url != undefined )
path = alternate_url;
api.getJson( path, function(data) {
$this.cached_types = data; $this.cached_types = data;
if( success_fnc ) { if( success_fnc ) {
success_fnc($this.cached_types); 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 // Cache types locally, will not change unless new broker version
if( this.cached_tableInfo ) { if( this.cached_tableInfo ) {
if( success_fnc ) { if( success_fnc ) {
@ -85,8 +87,11 @@ BasicModelRest.prototype = {
return; return;
} }
var $this = this; 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; $this.cached_tableInfo = data;
if( success_fnc ) { if( success_fnc ) {
success_fnc($this.cached_tableInfo); success_fnc($this.cached_tableInfo);
@ -104,11 +109,24 @@ function DetailModelRestApi(parentApi, path) {
} }
DetailModelRestApi.prototype = { DetailModelRestApi.prototype = {
// Generates a basic model with fixed methods for "detail" models
detail: function(parentId) { detail: function(parentId) {
var $this = this;
var rest = new BasicModelRest(this.parentPath + '/' + parentId + '/' + this.path); var rest = new BasicModelRest(this.parentPath + '/' + parentId + '/' + this.path);
// Overwrite types, detail do not have types
rest.types = function() { rest.types = function() {
return []; // No types at all 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;
} }
}; };
@ -123,3 +141,65 @@ api.authenticators.users = new DetailModelRestApi(api.authenticators, 'users');
api.osmanagers = new BasicModelRest('osmanagers'); api.osmanagers = new BasicModelRest('osmanagers');
api.transports = new BasicModelRest('transports'); 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" // Several convenience "constants"
gui.dataTablesLanguage = { gui.dataTablesLanguage = {
"sLengthMenu": gettext("_MENU_ records per page"), "sLengthMenu": gettext("_MENU_ records per page"),
"sZeroRecords": gettext("Nothing found - sorry"), "sZeroRecords": gettext("Empty"),
"sInfo": gettext("Records _START_ to _END_ of _TOTAL_"), "sInfo": gettext("Records _START_ to _END_ of _TOTAL_"),
"sInfoEmpty": gettext("No records"), "sInfoEmpty": gettext("No records"),
"sInfoFiltered": gettext("(filtered from _MAX_ total records)"), "sInfoFiltered": gettext("(filtered from _MAX_ total records)"),
@ -88,13 +88,13 @@
]; ];
$.each(sidebarLinks, function(index, value){ $.each(sidebarLinks, function(index, value){
gui.doLog('Adding ' + value.id) gui.doLog('Adding ' + value.id)
$('.'+value.id).unbind('click').click(value.exec); $('.'+value.id).unbind('click').click(function(event) {
// Navbar click so navbar is closed... if($('.navbar-toggle').css('display') !='none') {
$('.nav a').on('click', function(){
if($('.navbar-toggle').css('display') !='none'){
$(".navbar-toggle").trigger( "click" ); $(".navbar-toggle").trigger( "click" );
} }
}); $('html, body').scrollTop(0);
value.exec(event);
});
}); });
} }
@ -162,6 +162,8 @@ GuiElement.prototype = {
column.bVisible = options.visible; column.bVisible = options.visible;
if( options.sortable != undefined ) if( options.sortable != undefined )
column.bSortable = options.sortable; column.bSortable = options.sortable;
if( options.searchable != undefined )
column.bSearchable = options.searchable;
// Fix name columm so we can add a class icon // Fix name columm so we can add a class icon
if( v == 'name' ) { if( v == 'name' ) {
@ -174,10 +176,17 @@ GuiElement.prototype = {
gui.doLog(columns); gui.doLog(columns);
var processResponse = function(data) { var processResponse = function(data) {
$.each(data, function(index, value){ // If it has a "type" column
var type = $this.types[value.type]; try {
data[index].name = '<span class="' + type.css + '"> </span> ' + value.name 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({ $this.rest.get({
@ -319,6 +328,11 @@ GuiElement.prototype = {
}); });
$('#' + tableId + '_filter input').addClass('form-control'); $('#' + 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>
<script src="{% get_static_prefix %}adm/js/api.js"></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.js"></script>
<script src="{% get_static_prefix %}adm/js/gui-elements.js"></script>
<script> <script>
$(function() { $(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 # Change Language
(r'^i18n/', include('django.conf.urls.i18n')), (r'^i18n/', include('django.conf.urls.i18n')),
# Downloadables # Downloadables
(r'^download/(?P<idDownload>.*)$', 'web.views.download'), (r'^download/(?P<idDownload>[a-zA-Z0-9]*)$', 'web.views.download'),
# Custom authentication callback # Custom authentication callback
(r'^auth/(?P<authName>.+)', 'web.views.authCallback'), (r'^auth/(?P<authName>.+)', 'web.views.authCallback'),
(r'^authJava/(?P<idAuth>.+)/(?P<hasJava>.*)$', 'web.views.authJava'), (r'^authJava/(?P<idAuth>.+)/(?P<hasJava>.*)$', 'web.views.authJava'),