Improved Service providers section over administration client implementation. js now shows logs for providers & for services

This commit is contained in:
Adolfo Gómez 2013-12-08 06:41:57 +00:00
parent 744515f11f
commit b9aff159d8
12 changed files with 316 additions and 81 deletions

View File

@ -15,6 +15,11 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>

View File

@ -0,0 +1,8 @@
DELEGATES_PREFERENCE=delegateValidatorList
USER_BUILD_PREFERENCE=enabledBuildValidatorList
USER_MANUAL_PREFERENCE=enabledManualValidatorList
USER_PREFERENCE=overrideGlobalPreferencesfalse
eclipse.preferences.version=1
override=false
suspend=false
vf.version=3

View File

@ -36,7 +36,6 @@ from django.utils.translation import ugettext_lazy as _
from uds.models import Authenticator
from uds.core import auths
from users_groups import Users, Groups
from uds.REST import NotFound
from uds.REST.model import ModelHandler

View File

@ -34,8 +34,10 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models import Service
from uds.core.util import log
from uds.core.Environment import Environment
from uds.REST.model import DetailHandler
from uds.REST import NotFound, ResponseError, RequestError
@ -90,7 +92,7 @@ class Services(DetailHandler):
service.data = service.getInstance(self._params).serialize()
service.save()
except Service.DoesNotExist:
raise NotFound('Item not found')
self.invalidItemException()
except IntegrityError: # Duplicate key probably
raise RequestError('Element already exists (duplicate key error)')
except Exception:
@ -108,7 +110,7 @@ class Services(DetailHandler):
service.delete()
except:
raise NotFound('service not found')
self.invalidItemException()
return 'deleted'
@ -155,3 +157,11 @@ class Services(DetailHandler):
except Exception as e:
logger.exception('getGui')
raise ResponseError(unicode(e))
def getLogs(self, parent, item):
try:
item = parent.services.get(pk=item)
logger.debug('Getting logs for {0}'.format(item))
return log.getLogs(item)
except:
self.invalidItemException()

View File

@ -37,6 +37,8 @@ from django.utils.translation import ugettext as _
from django.db import IntegrityError
from uds.REST.handlers import Handler
from uds.core.util import log
import logging
logger = logging.getLogger(__name__)
@ -46,6 +48,7 @@ OVERVIEW = 'overview'
TYPES = 'types'
TABLEINFO = 'tableinfo'
GUI = 'gui'
LOG = 'log'
# Base for Gui Related mixins
class BaseModelHandler(Handler):
@ -151,7 +154,13 @@ class BaseModelHandler(Handler):
# Exceptions
def invalidRequestException(self):
raise RequestError('Invalid Request')
raise RequestError(_('Invalid Request'))
def invalidMethodException(self):
raise NotFound(_('Method not found'))
def invalidItemException(self):
raise NotFound(_('Item not found'))
# Details do not have types at all
# so, right now, we only process details petitions for Handling & tables info
@ -208,6 +217,8 @@ class DetailHandler(BaseModelHandler):
return sorted(gui, key=lambda f: f['gui']['order'])
elif self._args[0] == TYPES:
return self.getTypes(parent, self._args[1])
elif self._args[1] == LOG:
return self.getLogs(parent, self._args[0])
return self.fallbackGet()
@ -252,6 +263,7 @@ class DetailHandler(BaseModelHandler):
def fallbackGet(self):
raise self.invalidRequestException()
# Override this to provide functionality
# Default (as sample) getItems
def getItems(self, parent, item):
if item is None: # Returns ALL detail items
@ -279,6 +291,9 @@ class DetailHandler(BaseModelHandler):
def getTypes(self, parent, forType):
return [] # Default is that details do not have types
def getLogs(self, parent, item):
self.invalidMethodException()
class ModelHandler(BaseModelHandler):
'''
@ -335,6 +350,11 @@ class ModelHandler(BaseModelHandler):
logger.debug('Found type {0}'.format(v))
return found
# log related
def getLogs(self, item):
logger.debug('Default getLogs invoked')
return log.getLogs(item)
# gui related
def getGui(self, type_):
self.invalidRequestException()
@ -357,7 +377,7 @@ class ModelHandler(BaseModelHandler):
detail = detailCls(self, path, self._params, *args, parent = item)
method = getattr(detail, self._operation)
except AttributeError:
raise NotFound('method not found')
self.invalidMethodException()
return method()
@ -394,25 +414,33 @@ class ModelHandler(BaseModelHandler):
self.fillIntanceFields(val, res)
return res
except:
raise NotFound('item not found')
self.invalidItemException()
# nArgs > 1
# Request type info or gui, or detail
if self._args[0] == TYPES:
if nArgs != 2:
raise RequestError('invalid request')
self.invalidRequestException()
return self.getType(self._args[1])
elif self._args[0] == GUI:
if nArgs != 2:
raise RequestError('invalid request')
self.invalidRequestException()
gui = self.getGui(self._args[1])
return sorted(gui, key=lambda f: f['gui']['order'])
elif self._args[1] == LOG:
if nArgs != 2:
self.invalidRequestException()
try:
item = self.model.objects.filter(pk=self._args[0])[0]
except:
self.invalidItemException()
return self.getLogs(item)
# If has detail and is requesting detail
if self.detail is not None:
return self.processDetail()
raise RequestError('invalid request')
self.invalidRequestException()
def post(self):
# right now
@ -421,7 +449,7 @@ class ModelHandler(BaseModelHandler):
if self._args[0] == 'test':
return 'tested'
raise NotFound('Method not found')
self.invalidMethodException()
def put(self):
logger.debug('method PUT for {0}, {1}'.format(self.__class__.__name__, self._args))

View File

@ -169,6 +169,7 @@ function BasicModelRest(path, options) {
// Requests paths
this.path = path;
this.getPath = options.getPath || path;
this.logPath = options.logPath || path;
this.putPath = options.putPath || path;
this.testPath = options.testPath || (path + '/test');
this.delPath = options.delPath || path;
@ -248,6 +249,19 @@ BasicModelRest.prototype = {
fail: fail_fnc
});
},
// -------------
// Log methods
// -------------
getLogs: function(itemId, success_fnc, fail_fnc) {
"use strict";
var path = this.logPath + '/' + itemId + '/' + 'log';
return this._requestPath(path, {
cacheKey: '.',
success: success_fnc,
fail: fail_fnc
});
},
// -------------
@ -374,6 +388,25 @@ DetailModelRestApi.prototype = {
"use strict";
return this.base.get(success_fnc, fail_fnc);
},
list: function(success_fnc, fail_fnc) { // This is "almost" an alias for get
"use strict";
return this.base.list(success_fnc, fail_fnc);
},
overview: function(success_fnc, fail_fnc) {
"use strict";
return this.base.overview(success_fnc, fail_fnc);
},
item: function(itemId, success_fnc, fail_fnc) {
"use strict";
return this.base.item(itemId, success_fnc, fail_fnc);
},
// -------------
// Log methods
// -------------
getLogs: function(itemId, success_fnc, fail_fnc) {
"use strict";
return this.base.getLogs(itemId, success_fnc, fail_fnc);
},
put: function(data, options) {
"use strict";
return this.base.put(data, options);
@ -412,18 +445,6 @@ DetailModelRestApi.prototype = {
"use strict";
return this.base.tableInfo(success_fnc, fail_fnc);
},
list: function(success_fnc, fail_fnc) { // This is "almost" an alias for get
"use strict";
return this.base.list(success_fnc, fail_fnc);
},
overview: function(success_fnc, fail_fnc) {
"use strict";
return this.base.overview(success_fnc, fail_fnc);
},
item: function(itemId, success_fnc, fail_fnc) {
"use strict";
return this.base.item(itemId, success_fnc, fail_fnc);
},
types: function(success_fnc, fail_fnc) {
"use strict";
if( this.options.types ) {

View File

@ -38,13 +38,39 @@ gui.providers.link = function(event) {
},
};
var serviceLogTable;
var prevTables = [];
var clearDetails = function() {
gui.doLog('Clearing details');
$.each(prevTables, function(undefined, tbl){
var $tbl = $(tbl).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
});
if( serviceLogTable ) {
var $tbl = $(serviceLogTable).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
$('#services-log-placeholder').empty();
serviceLogTable = undefined;
}
prevTables = [];
$('#services-placeholder').empty();
$('#logs-placeholder').empty();
$('#services-log-placeholder').empty();
$('#detail-placeholder').addClass('hidden');
};
api.templates.get('providers', function(tmpl) {
gui.clearWorkspace();
gui.appendToWorkspace(api.templates.evaluate(tmpl, {
providers : 'providers-placeholder',
services : 'services-placeholder',
services_log : 'services-log-placeholder',
logs: 'logs-placeholder',
}));
gui.setLinksEvents();
@ -61,17 +87,14 @@ gui.providers.link = function(event) {
}*/
return true;
},
onRowDeselect: function() {
clearDetails();
},
onRowSelect : function(selected) {
gui.tools.blockUI();
gui.doLog(selected[0]);
$.each(prevTables, function(undefined, tbl){
var $tbl = $(tbl).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
});
prevTables = [];
$('#services-placeholder').empty();
clearDetails();
$('#detail-placeholder').removeClass('hidden');
var id = selected[0].id;
// Giving the name compossed with type, will ensure that only styles will be reattached once
@ -80,6 +103,33 @@ gui.providers.link = function(event) {
var servicesTable = services.table({
container : 'services-placeholder',
rowSelect : 'single',
onRowSelect: function(sselected) {
gui.tools.blockUI();
var sId = sselected[0].id;
if( serviceLogTable ) {
var $tbl = $(serviceLogTable).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
$('#services-log-placeholder').empty();
}
serviceLogTable = services.logTable(sId, {
container: 'services-log-placeholder',
onLoad: function() {
gui.tools.unblockUI();
}
});
},
onRowDeselect : function() {
if( serviceLogTable ) {
var $tbl = $(serviceLogTable).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
$('#services-log-placeholder').empty();
}
serviceLogTable = undefined;
},
onCheck: function(check, items) {
if( check == 'delete' ) {
for( var i in items ) {
@ -100,7 +150,12 @@ gui.providers.link = function(event) {
},
});
var logTable = gui.providers.logTable(id, {
container : 'logs-placeholder',
});
prevTables.push(servicesTable);
prevTables.push(logTable);
},
buttons : [ 'new', 'edit', 'delete', 'xls' ],
onNew : gui.methods.typedNew(gui.providers, gettext('New provider'), gettext('Error creating provider'), testButton),
@ -129,6 +184,21 @@ gui.authenticators.link = function(event) {
};
var prevTables = [];
var clearDetails = function() {
$.each(prevTables, function(undefined, tbl){
var $tbl = $(tbl).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
});
$('#users-placeholder').empty();
$('#groups-placeholder').empty();
$('#logs-placeholder').empty();
$('#detail-placeholder').addClass('hidden');
prevTables = [];
};
gui.doLog('enter auths');
api.templates.get('authenticators', function(tmpl) {
@ -137,28 +207,24 @@ gui.authenticators.link = function(event) {
auths : 'auths-placeholder',
users : 'users-placeholder',
groups: 'groups-placeholder',
logs: 'logs-placeholder',
}));
gui.setLinksEvents();
gui.authenticators.table({
var tableId = gui.authenticators.table({
container : 'auths-placeholder',
rowSelect : 'single',
buttons : [ 'new', 'edit', 'delete', 'xls' ],
onRowDeselect: function() {
clearDetails();
},
onRowSelect : function(selected) {
// We can have lots of users, so memory can grow up rapidly if we do not keep thins clena
// To do so, we empty previous table contents before storing new table contents
// Anyway, TabletTools will keep "leaking" memory, but we can handle a little "leak" that will be fixed as soon as we change the section
$.each(prevTables, function(undefined, tbl){
var $tbl = $(tbl).dataTable();
$tbl.fnClearTable();
$tbl.fnDestroy();
});
$('#users-placeholder').empty();
$('#groups-placeholder').empty();
prevTables = [];
clearDetails();
$('#detail-placeholder').removeClass('hidden');
gui.tools.blockUI();
var id = selected[0].id;
@ -184,9 +250,14 @@ gui.authenticators.link = function(event) {
},
});
var logTable = gui.authenticators.logTable(id, {
container : 'logs-placeholder',
});
// So we can destroy the tables beforing adding new ones
prevTables.push(grpTable);
prevTables.push(usrTable);
prevTables.push(logTable);
return false;
},

View File

@ -50,7 +50,7 @@ GuiElement.prototype = {
// buttons: array of visible buttons (strings), valid are [ 'new', 'edit', 'refresh', 'delete', 'xls' ],
// rowSelect: type of allowed row selection, valid values are 'single' and 'multi'
// scrollToTable: if True, will scroll page to show table
// deferedRender: if True, datatable will be created with "bDeferRender": true, that will improve a lot creation
// deferedRender: if True, datatable will be created with "bDeferRender": true, that will improve a lot creation of huge tables
//
// onLoad: Event (function). If defined, will be invoked when table is fully loaded.
// Receives 1 parameter, that is the gui element (GuiElement) used to render table
@ -85,7 +85,7 @@ GuiElement.prototype = {
// 4.- the DataTable that raised the event
table : function(options) {
"use strict";
gui.doLog('Types: ', this.types);
options = options || {};
gui.doLog('Composing table for ' + this.name);
var tableId = this.name + '-table';
@ -102,13 +102,6 @@ GuiElement.prototype = {
return data;
};
// Datetime renderer (with specified format)
var renderDate = function(format) {
return function(data, type, full) {
return api.tools.strftime(format, new Date(data*1000));
};
};
// Icon renderer, based on type (created on init methods in styles)
var renderTypeIcon = function(data, type, value){
if( type == 'display' ) {
@ -160,14 +153,14 @@ GuiElement.prototype = {
switch(opts.type) {
case 'date':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT')));
column.mRender = gui.tools.renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT')));
break;
case 'datetime':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATETIME_FORMAT')));
column.mRender = gui.tools.renderDate(api.tools.djangoFormat(get_format('SHORT_DATETIME_FORMAT')));
break;
case 'time':
column.mRender = renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
column.mRender = gui.tools.renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
break;
case 'iconType':
//columnt.sType = 'html'; // html is default, so this is not needed
@ -192,7 +185,7 @@ GuiElement.prototype = {
});
// Responsive style for tables, using tables.css and this code generates the "titles" for vertical display on small sizes
$('#style-' + tableId).remove(); // Remove existing style for table before adding new one
$(api.templates.evaluate('tmpl_responsive_table', {
$(api.templates.evaluate('tmpl_comp_responsive_table', {
tableId: tableId,
columns: columns,
})).appendTo('head');
@ -392,19 +385,19 @@ GuiElement.prototype = {
// Initializes oTableTools
var oTableTools = {
"aButtons" : btns,
"sRowSelect": options.rowSelect || 'single',
"sRowSelect": options.rowSelect || 'none',
};
if (options.onRowSelect) {
var rowSelectedFnc = options.onRowSelect;
oTableTools.fnRowSelected = function() {
rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), self);
};
}
if (options.onRowDeselect) {
var rowDeselectedFnc = options.onRowDeselect;
oTableTools.fnRowDeselected = function() {
rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), self);
};
}
@ -425,6 +418,7 @@ GuiElement.prototype = {
$('#' + tableId + '_filter input').addClass('form-control');
// Add refresh action to panel
$(table.refreshSelector).click(refreshFnc);
// Add tooltips to "new" buttons
$('.DTTT_dropdown [data-toggle="tooltip"]').tooltip({
container:'body',
@ -443,6 +437,80 @@ GuiElement.prototype = {
}); // End Overview data
}); // End Tableinfo data
return '#' + tableId;
},
logTable: function(itemId, options) {
"use strict";
options = options || {};
gui.doLog('Composing log for ' + this.name);
var tableId = this.name + '-table-log';
var self = this; // Store this for child functions
// Renderers for columns
var columns = [
{
"mData" : 'date',
"sTitle" : gettext('Date'),
"mRender" : gui.tools.renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT') + ' ' + get_format('TIME_FORMAT'))),
"bSortable" : true,
"bSearchable" : true,
},
{
"mData" : 'level',
"sTitle" : gettext('level'),
"mRender" : gui.tools.renderLogLovel(),
"sWidth" : "5em",
"bSortable" : true,
"bSearchable" : true,
},
{
"mData" : 'source',
"sTitle" : gettext('source'),
"sWidth" : "5em",
"bSortable" : true,
"bSearchable" : true,
},
{
"mData" : 'message',
"sTitle" : gettext('message'),
"bSortable" : true,
"bSearchable" : true,
},
];
var table = gui.table(options.title || gettext('Logs'), tableId);
if (options.container === undefined) {
gui.appendToWorkspace('<div class="row"><div class="col-lg-12">' + table.text + '</div></div>');
} else {
$('#' + options.container).empty();
$('#' + options.container).append(table.text);
}
// Responsive style for tables, using tables.css and this code generates the "titles" for vertical display on small sizes
$('#style-' + tableId).remove(); // Remove existing style for table before adding new one
$(api.templates.evaluate('tmpl_comp_responsive_table', {
tableId: tableId,
columns: columns,
})).appendTo('head');
self.rest.getLogs(itemId, function(data){
gui.doLog(data);
$('#' + tableId).dataTable({
"aaData" : data,
"oTableTools" : {"aButtons" : [],},
"aoColumns" : columns,
"oLanguage" : gui.config.dataTablesLanguage,
"sDom" : "<'row'<'col-xs-8'T><'col-xs-4'f>r>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
"bDeferRender": options.deferedRender || false,
});
// if table rendered event
if( options.onLoad ) {
options.onLoad(self);
}
});
return '#' + tableId;
},
};

View File

@ -47,6 +47,28 @@
});
});
},
// Datetime renderer (with specified format)
renderDate : function(format) {
return function(data, type, full) {
return api.tools.strftime(format, new Date(data*1000));
};
},
// Log level rendererer
renderLogLovel : function() {
var levels = {
10000 : 'OTHER',
20000 : 'DEBUG',
30000 : 'INFO',
40000 : 'WARN',
50000 : 'ERROR',
60000 : 'FATAL'
};
return function(data, type, full) {
return levels[data] || 'OTHER';
}
},
};
}(window.gui = window.gui || {}, jQuery));

View File

@ -45,10 +45,6 @@
text: '<span class="fa fa-eraser"></span> <span class="label-tbl-button">' + gettext('Delete') + '</span>',
css: 'disabled btn3d-default btn3d btn3d-tables',
},
'refresh': {
text: '<span class="fa fa-refresh"></span> <span class="label-tbl-button">' + gettext('Refresh') + '</span>',
css: 'btn3d-primary btn3d btn3d-tables',
},
'xls': {
text: '<span class="fa fa-save"></span> <span class="label-tbl-button">' + gettext('Xls') + '</span>',
css: 'btn3d-info btn3d btn3d-tables',
@ -61,7 +57,7 @@
var panelId = 'panel-' + table_id;
return {
text: api.templates.evaluate('tmpl_table', {
text: api.templates.evaluate('tmpl_comp_table', {
panelId: panelId,
icon: options.icon || 'table',
size: options.size || 12,
@ -85,21 +81,9 @@
return '<div class="row"><div class="col-lg-12"><ol class="breadcrumb">' + list + "</ol></div></div>";
};
gui.minimizePanel = function(panelId) {
var title = $(panelId).attr('data-minimized');
$(panelId).hide('slow', function(){
$('<span class="label label-primary panel-icon"><b class="fa fa-plus-square-o"></b> ' + title + '</span>')
.appendTo('#minimized')
.click(function(){
this.remove();
$(panelId).show('slow');
});
});
};
gui.modal = function(id, title, content, options) {
options = options || {};
return api.templates.evaluate('tmpl_modal', {
return api.templates.evaluate('tmpl_comp_modal', {
id: id,
title: title,
content: content,

View File

@ -15,14 +15,14 @@
<div id="detail-placeholder" class="row hidden">
<div class="col-xs-12">
<ul class="nav nav-tabs">
<li><a href="#{{ users }}" data-toggle="tab">Users</a></li>
<li><a href="#{{ groups }}" data-toggle="tab">Groups</a></li>
<li><a href="#{{ logs }}" data-toggle="tab">Logs</a></li>
<li class="active"><a href="#{{ users }}" data-toggle="tab">{% endverbatim %}{% trans 'Users' %}{% verbatim %}</a></li>
<li><a href="#{{ groups }}" data-toggle="tab">{% endverbatim %}{% trans 'Groups' %}{% verbatim %}</a></li>
<li><a href="#{{ logs }}" data-toggle="tab">{% endverbatim %}{% trans 'Logs' %}{% verbatim %}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="{{ users }}"></div>
<div class="tab-pane fade" id="{{ groups }}"></div>
<div class="tab-pane fade" id="{{ logs }}"></div>
<div class="tab-pane fade" id="{{ logs }}">...</div>
</div>
</div>
</div>

View File

@ -12,7 +12,26 @@
<div class="row">
<div id="{{ providers }}" class="col-xs-12"></div>
</div>
<div class="row">
<div id="{{ services }}" class="col-xs-12"></div>
<div id="detail-placeholder" class="row hidden">
<div class="col-xs-12">
<ul class="nav nav-tabs">
<li class="active"><a href="#{{ services }}_tab" data-toggle="tab">{% endverbatim %}{% trans 'Services' %}{% verbatim %}</a></li>
<li><a href="#{{ logs }}" data-toggle="tab">{% endverbatim %}{% trans 'Logs' %}{% verbatim %}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="{{ services }}_tab">
<div class="row">
<div class="col-xs-12" id="{{ services }}">
</div>
</div>
<div class="row">
<div class="col-xs-12" id="{{ services_log }}">
</div>
</div>
</div>
<div class="tab-pane fade" id="{{ logs }}">...</div>
</div>
</div>
</div>
{% endverbatim %}