diff --git a/server/src/uds/REST/__init__.py b/server/src/uds/REST/__init__.py
index 3b8c90117..e0fda0421 100644
--- a/server/src/uds/REST/__init__.py
+++ b/server/src/uds/REST/__init__.py
@@ -37,7 +37,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, activate
from django.conf import settings
-from handlers import Handler, HandlerError, AccessDenied, NotFound
+from handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError
import time
import logging
@@ -148,12 +148,14 @@ class Dispatcher(View):
for k, v in handler.headers().iteritems():
response[k] = v
return response
- except HandlerError as e:
- return http.HttpResponseBadRequest(unicode(e))
+ except RequestError as e:
+ return http.HttpResponseServerError(unicode(e))
except AccessDenied as e:
return http.HttpResponseForbidden(unicode(e))
except NotFound as e:
return http.Http404(unicode(e))
+ except HandlerError as e:
+ return http.HttpResponseBadRequest(unicode(e))
except Exception as e:
logger.exception('Error processing request')
return http.HttpResponseServerError(unicode(e))
diff --git a/server/src/uds/REST/handlers.py b/server/src/uds/REST/handlers.py
index 58c4314b9..26e14cb22 100644
--- a/server/src/uds/REST/handlers.py
+++ b/server/src/uds/REST/handlers.py
@@ -32,7 +32,6 @@
'''
from __future__ import unicode_literals
from django.contrib.sessions.backends.db import SessionStore
-from django.utils.translation import activate
from django.conf import settings
from uds.core.util.Config import GlobalConfig
@@ -52,6 +51,9 @@ class NotFound(HandlerError):
class AccessDenied(HandlerError):
pass
+class RequestError(HandlerError):
+ pass
+
class Handler(object):
raw = False # If true, Handler will return directly an HttpResponse Object
name = None # If name is not used, name will be the class name in lower case
diff --git a/server/src/uds/REST/methods/transports.py b/server/src/uds/REST/methods/transports.py
index 4c61e008b..b1394e230 100644
--- a/server/src/uds/REST/methods/transports.py
+++ b/server/src/uds/REST/methods/transports.py
@@ -32,7 +32,7 @@
'''
from __future__ import unicode_literals
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import Transport
from uds.core.transports import factory
@@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
class Transports(ModelHandlerMixin, Handler):
model = Transport
+ save_fields = ['name', 'comments', 'priority', 'nets_positive']
def item_as_dict(self, item):
type_ = item.getType()
@@ -58,16 +59,27 @@ class Transports(ModelHandlerMixin, Handler):
'deployed_count': item.deployedServices.count(),
'type': type_.type(),
}
+
+
class Types(ModelTypeHandlerMixin, Handler):
path = 'transports'
+ has_comments = True
def enum_types(self):
return factory().providers().values()
def getGui(self, type_):
try:
- return factory().lookup(type_).guiDescription()
+ return self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments']), {
+ 'name': 'priority',
+ 'required': True,
+ 'value': '1',
+ 'label': ugettext('Priority'),
+ 'tooltip': ugettext('Priority of this transport'),
+ 'type': 'numeric',
+ 'order': 100, # At end
+ })
except:
raise NotFound('type not found')
@@ -78,5 +90,6 @@ class TableInfo(ModelTableHandlerMixin, Handler):
fields = [
{ 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } },
{ 'comments': {'title': _('Comments')}},
+ { 'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em' }},
{ 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
]
diff --git a/server/src/uds/REST/mixins.py b/server/src/uds/REST/mixins.py
index dbd9e7e13..5db4d3078 100644
--- a/server/src/uds/REST/mixins.py
+++ b/server/src/uds/REST/mixins.py
@@ -32,7 +32,7 @@
'''
from __future__ import unicode_literals
-from handlers import NotFound
+from handlers import NotFound, RequestError
from django.utils.translation import ugettext as _
import logging
@@ -65,6 +65,8 @@ class ModelHandlerMixin(object):
needs_staff = True
detail = None # Dictionary containing detail routing
model = None
+ save_fields = []
+
def item_as_dict(self, item):
pass
@@ -117,6 +119,37 @@ class ModelHandlerMixin(object):
return res
except:
raise NotFound('item not found')
+
+ def put(self):
+ logger.debug('method PUT for {0}, {1}'.format(self.__class__.__name__, self._args))
+ args = {}
+ try:
+ for key in self.save_fields:
+ args[key] = self._params[key]
+ del self._params[key]
+ except KeyError as e:
+ raise RequestError('needed parameter not found in data {0}'.format(unicode(e)))
+ try:
+ item = self.model.objects.create(**args);
+ except: # Duplicate key probably
+ raise RequestError('Element already exists (duplicate key error)')
+
+ try:
+ if self._params.has_key('data_type'): # Needs to store instance
+ item.data_type = self._params['data_type']
+ item.data = item.getInstance(self._params).serialize()
+ item.save()
+ except Exception as e:
+ item.delete() # Remove pre-saved element
+ raise RequestError(unicode(e))
+
+ return {'id': item.id }
+
+ def delete(self):
+ logger.debug('method DELETE for {0}, {1}'.format(self.__class__.__name__, self._args))
+ if len(self._args) != 1:
+ raise RequestError('Delete need an argument')
+ return 'deleted'
class ModelTypeHandlerMixin(object):
'''
@@ -125,6 +158,7 @@ class ModelTypeHandlerMixin(object):
'''
authenticated = True
needs_staff = True
+ has_comments = False
def enum_types(self):
pass
@@ -140,6 +174,46 @@ class ModelTypeHandlerMixin(object):
for type_ in self.enum_types():
yield self.type_as_dict(type_)
+ def addField(self, gui, field):
+ gui.append({
+ 'name': field.get('name', ''),
+ 'value': '',
+ 'gui': {
+ 'required': field.get('required', False),
+ 'defvalue': field.get('value', ''),
+ 'value': field.get('value', ''),
+ 'label': field.get('label', ''),
+ 'length': field.get('length', 128),
+ 'multiline': field.get('multiline', 0),
+ 'tooltip': field.get('tooltip', ''),
+ 'rdonly': field.get('rdonly', False),
+ 'type': field.get('type', 'text'),
+ 'order': field.get('order', 0),
+ 'values': field.get('values', [])
+ }
+ })
+ return gui
+
+ def addDefaultFields(self, gui, flds):
+ if 'name' in flds:
+ self.addField(gui, {
+ 'name': 'name',
+ 'required': True,
+ 'label': _('Name'),
+ 'tooltip': _('Name of this element'),
+ 'order': -2,
+ })
+ # And maybe comments (only if model has this field)
+ if 'comments' in flds:
+ self.addField(gui, {
+ 'name': 'comments',
+ 'label': _('Comments'),
+ 'tooltip': _('Comments for this element'),
+ 'length': 256,
+ 'order': -1,
+ })
+ return gui
+
def get(self):
logger.debug(self._args)
nArgs = len(self._args)
@@ -162,42 +236,7 @@ class ModelTypeHandlerMixin(object):
if self._args[1] == 'gui':
gui = self.getGui(self._args[0])
# Add name default description, at top of form
- gui.append({
- 'name': 'name',
- 'value':'',
- 'gui': {
- 'required':True,
- 'defvalue':'',
- 'value':'',
- 'label': _('Name'),
- 'length': 128,
- 'multiline': 0,
- 'tooltip': _('Name of this element'),
- 'rdonly': False,
- 'type': 'text',
- 'order': -2
- }
- })
- # And comments
- gui.append({
- 'name': 'comments',
- 'value':'',
- 'gui': {
- 'required':False,
- 'defvalue':'',
- 'value':'',
- 'label': _('Comments'),
- 'length': 256,
- 'multiline': 0,
- 'tooltip': _('Comments for this element'),
- 'rdonly': False,
- 'type': 'text',
- 'order': -1
- }
- })
-
-
logger.debug("GUI: {0}".format(gui))
return sorted(gui, key=lambda f: f['gui']['order']);
diff --git a/server/src/uds/core/ui/UserInterface.py b/server/src/uds/core/ui/UserInterface.py
index 9dde74292..08020ffa2 100644
--- a/server/src/uds/core/ui/UserInterface.py
+++ b/server/src/uds/core/ui/UserInterface.py
@@ -125,7 +125,9 @@ class gui(object):
Returns:
True if the string is "true" (case insensitive), False else.
'''
- if str_.lower() == gui.TRUE:
+ if isinstance(str_, bool):
+ return str_
+ if unicode(str_).lower() == gui.TRUE:
return True
return False
diff --git a/server/src/uds/static/adm/js/api.js b/server/src/uds/static/adm/js/api.js
index 13e15efed..90c1b8048 100644
--- a/server/src/uds/static/adm/js/api.js
+++ b/server/src/uds/static/adm/js/api.js
@@ -40,9 +40,15 @@
}
};
+ // Default fail function
+ api.defaultFail = function(jqXHR, textStatus, errorThrown) {
+ api.doLog(jqXHR, ', ', textStatus, ', ', errorThrown);
+ };
+
api.getJson = function(path, options) {
options = options || {};
- var success_fnc = options.success || function(){};
+ var success_fnc = options.success || function(){};
+ var fail_fnc = options.fail || api.defaultFail;
var url = api.url_for(path);
api.doLog('Ajax GET Json for "' + url + '"');
@@ -51,10 +57,41 @@
type : "GET",
dataType : "json",
success : function(data) {
- api.doLog('Success on "' + url + '".');
- api.doLog('Received ' + JSON.stringify(data));
+ api.doLog('Success on GET "' + url + '".');
+ api.doLog('Received ', data);
success_fnc(data);
},
+ error: function(jqXHR, textStatus, errorThrown) {
+ api.doLog('Error on GET "' + url + '". ', textStatus, ', ', errorThrown);
+ fail_fnc(jqXHR, textStatus, errorThrown);
+ },
+ beforeSend : function(request) {
+ request.setRequestHeader(api.config.auth_header, api.config.token);
+ },
+ });
+ };
+
+ api.putJson = function(path, data, options) {
+ options = options || {};
+ var success_fnc = options.success || function(){};
+ var fail_fnc = options.fail || api.defaultFail;
+
+ var url = api.url_for(path);
+ api.doLog('Ajax PUT Json for "' + url + '"');
+ $.ajax({
+ url : url,
+ type : "PUT",
+ dataType : "json",
+ data: JSON.stringify(data),
+ success: function(data) {
+ api.doLog('Success on PUT "' + url + '".');
+ api.doLog('Received ', data);
+ success_fnc(data);
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ api.doLog('Error on PUT "' + url + '". ', textStatus, ', ', errorThrown);
+ fail_fnc(jqXHR, textStatus, errorThrown);
+ },
beforeSend : function(request) {
request.setRequestHeader(api.config.auth_header, api.config.token);
},
@@ -62,7 +99,7 @@
};
// Public attributes
- api.debug = false;
+ api.debug = true;
}(window.api = window.api || {}, jQuery));
@@ -104,6 +141,7 @@ function BasicModelRest(path, options) {
// Requests paths
this.path = path;
this.getPath = options.getPath || path;
+ this.putPath = options.putPath || path;
this.typesPath = options.typesPath || (path + '/types');
this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo');
this.cache = api.cache('bmr'+path);
@@ -117,7 +155,8 @@ BasicModelRest.prototype = {
_requestPath: function(path, options) {
"use strict";
options = options || {};
- var success_fnc = options.success || function(){api.doLog('success not provided for '+path);};
+ var success_fnc = options.success || function(){api.doLog('success function not provided for '+path);};
+ var fail_fnc = options.fail;
var cacheKey = options.cacheKey || path;
if( path == '.' ) {
@@ -136,10 +175,11 @@ BasicModelRest.prototype = {
}
success_fnc(data);
},
+ fail: fail_fnc,
});
}
},
- get : function(success_fnc, options) {
+ get: function(options) {
"use strict";
options = options || {};
@@ -148,61 +188,103 @@ BasicModelRest.prototype = {
path += '/' + options.id;
return this._requestPath(path, {
cacheKey: '.', // Right now, do not cache any "get" method
- success: success_fnc,
+ success: options.success,
+ fail: options.fail
});
},
- list: function(success_fnc, options) { // This is "almost" an alias for get
+ list: function(success_fnc, fail_fnc) { // This is "almost" an alias for get
"use strict";
- options = options || {};
- return this.get(success_fnc, {
+ return this.get({
id: '',
+ success: success_fnc,
+ fail: fail_fnc
});
},
- overview: function(success_fnc, options) {
+ overview: function(success_fnc, fail_fnc) {
"use strict";
- options = options || {};
- return this.get(success_fnc, {
+ return this.get({
id: 'overview',
+ success: success_fnc,
+ fail: fail_fnc
});
},
- item: function(itemId, success_fnc, options) {
+ item: function(itemId, success_fnc, fail_fnc) {
"use strict";
- options = options || {};
- return this.get(success_fnc, {
+ return this.get({
id: itemId,
+ success: success_fnc,
+ fail: fail_fnc
});
},
- types : function(success_fnc, options) {
+
+ // -------------
+ // Put methods
+ // -------------
+
+ put: function(data, options) {
"use strict";
options = options || {};
+
+ var path = this.putPath;
+ if ( options.id )
+ path += '/' + options.id;
+
+ api.putJson(path, data, {
+ success: options.success,
+ fail: options.fail
+ });
+ },
+ create: function(data, success_fnc, fail_fnc) {
+ "use strict";
+
+ return this.put(data, {
+ success: success_fnc,
+ fail: fail_fnc
+ });
+ },
+ save: function(data, success_fnc, fail_fnc) {
+ "use strict";
+
+ return this.put(data, {
+ id: data.id,
+ success: success_fnc,
+ fail: fail_fnc
+ });
+ },
+
+ // --------------
+ // Types methods
+ // --------------
+ types : function(success_fnc, fail_fnc) {
+ "use strict";
return this._requestPath(this.typesPath, {
cacheKey: 'type',
success: success_fnc,
});
},
- gui: function(typeName, success_fnc, options) {
+ gui: function(typeName, success_fnc, fail_fnc) {
// GUI returns a dict, that contains:
// name: Name of the field
// value: value of the field (selected element in choice, text for inputs, etc....)
// gui: Description of the field (type, value or values, defvalue, ....
"use strict";
- options = options || {};
var path = [this.typesPath, typeName, 'gui'].join('/');
return this._requestPath(path, {
cacheKey: typeName + '-gui',
success: success_fnc,
+ fail: fail_fnc,
});
},
- tableInfo : function(success_fnc, options) {
+ tableInfo : function(success_fnc, fail_fnc) {
"use strict";
- options = options || {};
success_fnc = success_fnc || function(){api.doLog('success not provided for tableInfo');};
var path = this.tableInfoPath;
this._requestPath(path, {
success: success_fnc,
+ fail: fail_fnc,
});
},
diff --git a/server/src/uds/static/adm/js/gui-elements.js b/server/src/uds/static/adm/js/gui-definition.js
similarity index 89%
rename from server/src/uds/static/adm/js/gui-elements.js
rename to server/src/uds/static/adm/js/gui-definition.js
index bb027ac22..23a4492b6 100644
--- a/server/src/uds/static/adm/js/gui-elements.js
+++ b/server/src/uds/static/adm/js/gui-definition.js
@@ -153,12 +153,20 @@ gui.connectivity.link = function(event) {
});
});
},
- onNew: function(type) {
+ onNew: function(type, table, refreshFnc) {
gui.connectivity.transports.rest.gui(type, function(itemGui) {
var form = gui.fields(itemGui);
- gui.launchModalForm(gettext('New transport'), form, function(form_selector) {
+ gui.launchModalForm(gettext('New transport'), form, function(form_selector, closeFnc) {
var fields = gui.fields.read(form_selector);
- return false;
+ // Append "own" fields, in this case data_type
+ fields.data_type = type;
+ fields.nets_positive = false;
+ gui.connectivity.transports.rest.create(fields, function(data) { // Success on put
+ closeFnc();
+ refreshFnc();
+ }, function(jqXHR, textStatus, errorThrown) { // fail on put
+ gui.launchModal(gettext('Error creating transport'), jqXHR.responseText, ' ');
+ });
});
});
},
diff --git a/server/src/uds/static/adm/js/gui-element.js b/server/src/uds/static/adm/js/gui-element.js
new file mode 100644
index 000000000..7f598047c
--- /dev/null
+++ b/server/src/uds/static/adm/js/gui-element.js
@@ -0,0 +1,428 @@
+/* jshint strict: true */
+function BasicGuiElement(name) {
+ "use strict";
+ this.name = name;
+}
+
+function GuiElement(restItem, name) {
+ "use strict";
+ this.rest = restItem;
+ this.name = name;
+ this.types = {};
+ this.init();
+}
+
+// all gui elements has, at least, name && type
+// Types must include, at least: type, icon
+GuiElement.prototype = {
+ init : function() {
+ "use strict";
+ gui.doLog('Initializing ' + this.name);
+ var $this = this;
+ this.rest.types(function(data) {
+ var styles = '';
+ $.each(data, function(index, value) {
+ var className = $this.name + '-' + value.type;
+ $this.types[value.type] = {
+ css : className,
+ name : value.name || '',
+ description : value.description || ''
+ };
+ gui.doLog('Creating style for ' + className);
+ var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
+ value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
+ styles += style;
+ });
+ if (styles !== '') {
+ styles = '';
+ $(styles).appendTo('head');
+ }
+ });
+ },
+ // Options: dictionary
+ // container: container ID of parent for the table. If undefined, table will be appended to workspace
+ // 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
+ //
+ // 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
+ // onRowSelect: Event (function). If defined, will be invoked when a row of table is selected
+ // Receives 3 parameters:
+ // 1.- the array of selected items data (objects, as got from api...get)
+ // 2.- the DataTable that raised the event
+ // 3.- the DataTableTools that raised the event
+ // onRowDeselect: Event (function). If defined, will be invoked when a row of table is deselected
+ // Receives 3 parameters:
+ // 1.- the array of selected items data (objects, as got from api...get)
+ // 2.- the DataTable that raised the event
+ // onNew: Event (function). If defined, will be invoked when "new" button is pressed
+ // Receives 4 parameters:
+ // 1.- the selected item data (single object, as got from api...get)
+ // 2.- the event that fired this (new, delete, edit, ..)
+ // 3.- the DataTable that raised the event
+ // onEdit: Event (function). If defined, will be invoked when "edit" button is pressed
+ // Receives 4 parameters:
+ // 1.- the selected item data (single object, as got from api...get)
+ // 2.- the event that fired this (new, delete, edit, ..)
+ // 3.- the DataTable that raised the event
+ // onDelete: Event (function). If defined, will be invoked when "delete" button is pressed
+ // Receives 4 parameters:
+ // 1.- the selected item data (single object, as got from api...get)
+ // 2.- the event that fired this (new, delete, edit, ..)
+ // 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';
+ var $this = this; // Store this for child functions
+
+ // Empty cells transform
+ var renderEmptyCell = function(data) {
+ if( data === '' )
+ return '-';
+ 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' ) {
+ var css = $this.types[value.type].css;
+ return ' ' + renderEmptyCell(data);
+ } else {
+ return renderEmptyCell(data);
+ }
+ };
+
+ // Custom icon renderer, in fact span with defined class
+ var renderIcon = function(icon) {
+ return function(data, type, full) {
+ if( type == 'display' ) {
+ return ' ' + renderEmptyCell(data);
+ } else {
+ return renderEmptyCell(data);
+ }
+ };
+ };
+ // Text transformation, dictionary based
+ var renderTextTransform = function(dict) {
+ return function(data, type, full) {
+ return dict[data] || renderEmptyCell('');
+ };
+ };
+ this.rest.tableInfo(function(data) {
+ var title = data.title;
+ var columns = [];
+ $.each(data.fields, function(index, value) {
+ for ( var v in value) {
+ var opts = value[v];
+ var column = {
+ mData : v,
+ };
+ column.sTitle = opts.title;
+ column.mRender = renderEmptyCell;
+ if (opts.width)
+ column.sWidth = opts.width;
+ column.bVisible = opts.visible === undefined ? true : opts.visible;
+ if (opts.sortable !== undefined)
+ column.bSortable = opts.sortable;
+ if (opts.searchable !== undefined)
+ column.bSearchable = opts.searchable;
+
+ if (opts.type && column.bVisible ) {
+ switch(opts.type) {
+ case 'date':
+ column.sType = 'date';
+ column.mRender = 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')));
+ break;
+ case 'time':
+ column.mRender = renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
+ break;
+ case 'iconType':
+ //columnt.sType = 'html'; // html is default, so this is not needed
+ column.mRender = renderTypeIcon;
+ break;
+ case 'icon':
+ if( opts.icon !== undefined ) {
+ column.mRender = renderIcon(opts.icon);
+ }
+ break;
+ case 'dict':
+ if( opts.dict !== undefined ) {
+ column.mRender = renderTextTransform(opts.dict);
+ }
+ break;
+ default:
+ column.sType = opts.type;
+ }
+ }
+ columns.push(column);
+ }
+ });
+ // 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', {
+ tableId: tableId,
+ columns: columns,
+ })).appendTo('head');
+
+ $this.rest.overview(function(data) {
+ var table = gui.table(title, tableId);
+ if (options.container === undefined) {
+ gui.appendToWorkspace('
');
+ } else {
+ $('#' + options.container).empty();
+ $('#' + options.container).append(table.text);
+ }
+
+ // What execute on refresh button push
+ var onRefresh = options.onRefresh || function(){};
+
+ var refreshFnc = function() {
+ // Refreshes table content
+ var tbl = $('#' + tableId).dataTable();
+ // Clears selection first
+ TableTools.fnGetInstance(tableId).fnSelectNone();
+ if( data.length > 1000 )
+ api.tools.blockUI();
+
+ $this.rest.overview(function(data) {
+ /*$(btn).removeClass('disabled').width('').html(saved);*/
+ setTimeout( function() {
+ tbl.fnClearTable();
+ tbl.fnAddData(data);
+ onRefresh($this);
+ api.tools.unblockUI();
+ }, 0);
+ });
+ return false; // This may be used on button or href, better disable execution of it
+ };
+
+ var btns = [];
+
+ if (options.buttons) {
+ var clickHandlerFor = function(handler, action, newHandler) {
+ var handleFnc = handler || function(val, action, tbl) {gui.doLog('Default handler called for ', action);};
+ return function(btn) {
+ var tbl = $('#' + tableId).dataTable();
+ var val = this.fnGetSelectedData()[0];
+ setTimeout(function() {
+ if( newHandler ) {
+ handleFnc(action, tbl, refreshFnc);
+ } else {
+ handleFnc(val, action, tbl, refreshFnc);
+ }
+ }, 0);
+ };
+ };
+
+ // methods for buttons on row select
+ var editSelected = function(btn, obj, node) {
+ var sel = this.fnGetSelectedData();
+ if (sel.length == 1) {
+ $(btn).removeClass('disabled').addClass('btn3d-success');
+ } else {
+ $(btn).removeClass('btn3d-success').addClass('disabled');
+ }
+ };
+ var deleteSelected = function(btn, obj, node) {
+ var sel = this.fnGetSelectedData();
+ if (sel.length > 0) {
+ $(btn).removeClass('disabled').addClass('btn3d-warning');
+ } else {
+ $(btn).removeClass('btn3d-warning').addClass('disabled');
+ }
+ };
+
+ $.each(options.buttons, function(index, value) {
+ var btn;
+ switch (value) {
+ case 'new':
+ if(Object.keys($this.types).length === 0) {
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtons['new'].text,
+ "fnClick" : clickHandlerFor(options.onNew, 'new'),
+ "sButtonClass" : gui.config.dataTableButtons['new'].css,
+ };
+ } else {
+ // This table has "types, so we create a dropdown with Types
+ var newButtons = [];
+ // Order buttons by name, much more easy for users... :-)
+ var order = [];
+ $.each($this.types, function(k, v){
+ order.push({
+ type: k,
+ css: v.css,
+ name: v.name,
+ description: v.description,
+ });
+ });
+ $.each(order.sort(function(a,b){return a.name.localeCompare(b.name);}), function(i, val){
+ newButtons.push({
+ "sExtends" : "text",
+ "sButtonText" : ' ' + val.name + '',
+ "fnClick" : clickHandlerFor(options.onNew, val.type, true),
+ });
+ });
+ btn = {
+ "sExtends" : "collection",
+ "aButtons": newButtons,
+ "sButtonText" : gui.config.dataTableButtons['new'].text,
+ "sButtonClass" : gui.config.dataTableButtons['new'].css,
+ };
+ }
+ break;
+ case 'edit':
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtons.edit.text,
+ "fnSelect" : editSelected,
+ "fnClick" : clickHandlerFor(options.onEdit, 'edit'),
+ "sButtonClass" : gui.config.dataTableButtons.edit.css,
+ };
+ break;
+ case 'delete':
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtons['delete'].text,
+ "fnSelect" : deleteSelected,
+ "fnClick" : clickHandlerFor(options.onDelete, 'delete'),
+ "sButtonClass" : gui.config.dataTableButtons['delete'].css,
+ };
+ break;
+ case 'refresh':
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtons.refresh.text,
+ "fnClick" : refreshFnc,
+ "sButtonClass" : gui.config.dataTableButtons.refresh.css,
+ };
+ break;
+ case 'xls':
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtons.xls.text,
+ "fnClick" : function(){
+ api.templates.get('spreadsheet', function(tmpl) {
+ var styles = { 'bold': 's21', };
+ var uri = 'data:application/vnd.ms-excel;base64,',
+ base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))); };
+
+ var headings = [], rows = [];
+ $.each(columns, function(index, heading){
+ if( heading.bVisible === false ) {
+ return;
+ }
+ headings.push(api.spreadsheet.cell(heading.sTitle, 'String', styles.bold));
+ });
+ rows.push(api.spreadsheet.row(headings));
+ $.each(data, function(index, row) {
+ var cells = [];
+ $.each(columns, function(index, col){
+ if( col.bVisible === false ) {
+ return;
+ }
+ var type = col.sType == 'numeric' ? 'Number':'String';
+ cells.push(api.spreadsheet.cell(row[col.mData], type));
+ });
+ rows.push(api.spreadsheet.row(cells));
+ });
+
+ var ctx = {
+ creation_date: (new Date()).toISOString(),
+ worksheet: title,
+ columns_count: headings.length,
+ rows_count: rows.length,
+ rows: rows.join('\n')
+ };
+ setTimeout( function() {
+ saveAs(new Blob([api.templates.evaluate(tmpl, ctx)],
+ {type: 'application/vnd.ms-excel'} ), title + '.xls');
+ }, 20);
+ });
+ },
+ "sButtonClass" : gui.config.dataTableButtons.xls.css,
+ };
+ }
+
+ if(btn) {
+ btns.push(btn);
+ }
+ });
+ }
+
+ // Initializes oTableTools
+ var oTableTools = {
+ "aButtons" : btns
+ };
+
+ // Type of row selection
+ if (options.rowSelect) {
+ oTableTools.sRowSelect = options.rowSelect;
+ }
+
+ if (options.onRowSelect) {
+ var rowSelectedFnc = options.onRowSelect;
+ oTableTools.fnRowSelected = function() {
+ rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
+ };
+ }
+ if (options.onRowDeselect) {
+ var rowDeselectedFnc = options.onRowDeselect;
+ oTableTools.fnRowDeselected = function() {
+ rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
+ };
+ }
+
+ $('#' + tableId).dataTable({
+ "aaData" : data,
+ "aoColumns" : columns,
+ "oLanguage" : gui.config.dataTablesLanguage,
+ "oTableTools" : oTableTools,
+ // First is upper row,
+ // second row is lower
+ // (pagination) row
+ "sDom" : "<'row'<'col-xs-8'T><'col-xs-4'f>r>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
+
+ });
+ // Fix 3dbuttons
+ api.tools.fix3dButtons('#' + tableId + '_wrapper .btn-group-3d');
+ // Fix form
+ $('#' + 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',
+ delay: { show: 1000, hide: 100},
+ placement: 'auto right',
+ });
+
+ if (options.scrollToTable === true ) {
+ var tableTop = $('#' + tableId).offset().top;
+ $('html, body').scrollTop(tableTop);
+ }
+ // if table rendered event
+ if( options.onLoad ) {
+ options.onLoad($this);
+ }
+ });
+ });
+ return '#' + tableId;
+ }
+
+};
diff --git a/server/src/uds/static/adm/js/gui.js b/server/src/uds/static/adm/js/gui.js
index ebb155c3e..0c362e0e5 100644
--- a/server/src/uds/static/adm/js/gui.js
+++ b/server/src/uds/static/adm/js/gui.js
@@ -97,11 +97,24 @@
});
};
- gui.modal = function(id, title, content) {
+ gui.modal = function(id, title, content, actionButton, closeButton) {
return api.templates.evaluate('tmpl_modal', {
id: id,
title: title,
- content: content
+ content: content,
+ button1: closeButton,
+ button2: actionButton
+ });
+ };
+
+ gui.launchModal = function(title, content, actionButton, closeButton) {
+ var id = Math.random().toString().split('.')[1]; // Get a random ID for this modal
+ gui.appendToWorkspace(gui.modal(id, title, content, actionButton, closeButton));
+ id = '#' + id; // for jQuery
+
+ $(id).modal()
+ .on('hidden.bs.modal', function () {
+ $(id).remove();
});
};
@@ -143,10 +156,12 @@
if( !$form.valid() )
return;
if( onSuccess ) {
- if( onSuccess(id + ' form') === false ) // Some error may have ocurred, do not close dialog
+ onSuccess(id + ' form', function(){$(id).modal('hide');}); // Delegate close to to onSuccess
return;
+ } else {
+ $(id).modal('hide');
}
- $(id).modal('hide');
+
});
// Launch modal
@@ -156,6 +171,16 @@
});
};
+ gui.failRequestMessageFnc = function(jqXHR, textStatus, errorThrown) {
+ api.templates.get('request_failed', function(tmpl) {
+ gui.clearWorkspace();
+ gui.appendToWorkspace(api.templates.evaluate(tmpl, {
+ error: jqXHR.responseText,
+ }));
+ });
+ gui.setLinksEvents();
+ };
+
gui.clearWorkspace = function() {
$('#content').empty();
$('#minimized').empty();
@@ -233,432 +258,3 @@
gui.debug = true;
}(window.gui = window.gui || {}, jQuery));
-function BasicGuiElement(name) {
- "use strict";
- this.name = name;
-}
-
-function GuiElement(restItem, name) {
- "use strict";
- this.rest = restItem;
- this.name = name;
- this.types = {};
- this.init();
-}
-
-// all gui elements has, at least, name && type
-// Types must include, at least: type, icon
-GuiElement.prototype = {
- init : function() {
- "use strict";
- gui.doLog('Initializing ' + this.name);
- var $this = this;
- this.rest.types(function(data) {
- var styles = '';
- $.each(data, function(index, value) {
- var className = $this.name + '-' + value.type;
- $this.types[value.type] = {
- css : className,
- name : value.name || '',
- description : value.description || ''
- };
- gui.doLog('Creating style for ' + className);
- var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
- value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
- styles += style;
- });
- if (styles !== '') {
- styles = '';
- $(styles).appendTo('head');
- }
- });
- },
- // Options: dictionary
- // container: container ID of parent for the table. If undefined, table will be appended to workspace
- // 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
- //
- // 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
- // onRowSelect: Event (function). If defined, will be invoked when a row of table is selected
- // Receives 3 parameters:
- // 1.- the array of selected items data (objects, as got from api...get)
- // 2.- the DataTable that raised the event
- // 3.- the DataTableTools that raised the event
- // onRowDeselect: Event (function). If defined, will be invoked when a row of table is deselected
- // Receives 3 parameters:
- // 1.- the array of selected items data (objects, as got from api...get)
- // 2.- the DataTable that raised the event
- // onNew: Event (function). If defined, will be invoked when "new" button is pressed
- // Receives 4 parameters:
- // 1.- the selected item data (single object, as got from api...get)
- // 2.- the event that fired this (new, delete, edit, ..)
- // 3.- the DataTable that raised the event
- // onEdit: Event (function). If defined, will be invoked when "edit" button is pressed
- // Receives 4 parameters:
- // 1.- the selected item data (single object, as got from api...get)
- // 2.- the event that fired this (new, delete, edit, ..)
- // 3.- the DataTable that raised the event
- // onDelete: Event (function). If defined, will be invoked when "delete" button is pressed
- // Receives 4 parameters:
- // 1.- the selected item data (single object, as got from api...get)
- // 2.- the event that fired this (new, delete, edit, ..)
- // 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';
- var $this = this; // Store this for child functions
-
- // Empty cells transform
- var renderEmptyCell = function(data) {
- if( data === '' )
- return '-';
- 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' ) {
- var css = $this.types[value.type].css;
- return ' ' + renderEmptyCell(data);
- } else {
- return renderEmptyCell(data);
- }
- };
-
- // Custom icon renderer, in fact span with defined class
- var renderIcon = function(icon) {
- return function(data, type, full) {
- if( type == 'display' ) {
- return ' ' + renderEmptyCell(data);
- } else {
- return renderEmptyCell(data);
- }
- };
- };
- // Text transformation, dictionary based
- var renderTextTransform = function(dict) {
- return function(data, type, full) {
- return dict[data] || renderEmptyCell('');
- };
- };
- this.rest.tableInfo(function(data) {
- var title = data.title;
- var columns = [];
- $.each(data.fields, function(index, value) {
- for ( var v in value) {
- var opts = value[v];
- var column = {
- mData : v,
- };
- column.sTitle = opts.title;
- column.mRender = renderEmptyCell;
- if (opts.width)
- column.sWidth = opts.width;
- column.bVisible = opts.visible === undefined ? true : opts.visible;
- if (opts.sortable !== undefined)
- column.bSortable = opts.sortable;
- if (opts.searchable !== undefined)
- column.bSearchable = opts.searchable;
-
- if (opts.type && column.bVisible ) {
- switch(opts.type) {
- case 'date':
- column.sType = 'date';
- column.mRender = 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')));
- break;
- case 'time':
- column.mRender = renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
- break;
- case 'iconType':
- //columnt.sType = 'html'; // html is default, so this is not needed
- column.mRender = renderTypeIcon;
- break;
- case 'icon':
- if( opts.icon !== undefined ) {
- column.mRender = renderIcon(opts.icon);
- }
- break;
- case 'dict':
- if( opts.dict !== undefined ) {
- column.mRender = renderTextTransform(opts.dict);
- }
- break;
- default:
- column.sType = opts.type;
- }
- }
- columns.push(column);
- }
- });
- // 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', {
- tableId: tableId,
- columns: columns,
- })).appendTo('head');
-
- $this.rest.overview(function(data) {
- var table = gui.table(title, tableId);
- if (options.container === undefined) {
- gui.appendToWorkspace('');
- } else {
- $('#' + options.container).empty();
- $('#' + options.container).append(table.text);
- }
-
- // What execute on refresh button push
- var onRefresh = options.onRefresh || function(){};
-
- var refreshFnc = function() {
- // Refreshes table content
- var tbl = $('#' + tableId).dataTable();
- // Clears selection first
- TableTools.fnGetInstance(tableId).fnSelectNone();
- if( data.length > 1000 )
- api.tools.blockUI();
-
- $this.rest.overview(function(data) {
- /*$(btn).removeClass('disabled').width('').html(saved);*/
- setTimeout( function() {
- tbl.fnClearTable();
- tbl.fnAddData(data);
- onRefresh($this);
- api.tools.unblockUI();
- }, 0);
- });
- return false; // This may be used on button or href, better disable execution of it
- };
-
- var btns = [];
-
- if (options.buttons) {
- var clickHandlerFor = function(handler, action, newHandler) {
- var handleFnc = handler || function(val, action, tbl) {gui.doLog('Default handler called for ', action);};
- return function(btn) {
- var tbl = $('#' + tableId).dataTable();
- var val = this.fnGetSelectedData()[0];
- setTimeout(function() {
- if( newHandler ) {
- if( handleFnc(action, tbl) === true ) // Reload table?
- refreshFnc();
- } else {
- if( handleFnc(val, action, tbl) === true ) // Reload table?
- refreshFnc();
- }
- }, 0);
- };
- };
-
- // methods for buttons on row select
- var editSelected = function(btn, obj, node) {
- var sel = this.fnGetSelectedData();
- if (sel.length == 1) {
- $(btn).removeClass('disabled').addClass('btn3d-success');
- } else {
- $(btn).removeClass('btn3d-success').addClass('disabled');
- }
- };
- var deleteSelected = function(btn, obj, node) {
- var sel = this.fnGetSelectedData();
- if (sel.length > 0) {
- $(btn).removeClass('disabled').addClass('btn3d-warning');
- } else {
- $(btn).removeClass('btn3d-warning').addClass('disabled');
- }
- };
-
- $.each(options.buttons, function(index, value) {
- var btn;
- switch (value) {
- case 'new':
- if(Object.keys($this.types).length === 0) {
- btn = {
- "sExtends" : "text",
- "sButtonText" : gui.config.dataTableButtons['new'].text,
- "fnClick" : clickHandlerFor(options.onNew, 'new'),
- "sButtonClass" : gui.config.dataTableButtons['new'].css,
- };
- } else {
- // This table has "types, so we create a dropdown with Types
- var newButtons = [];
- // Order buttons by name, much more easy for users... :-)
- var order = [];
- $.each($this.types, function(k, v){
- order.push({
- type: k,
- css: v.css,
- name: v.name,
- description: v.description,
- });
- });
- $.each(order.sort(function(a,b){return a.name.localeCompare(b.name);}), function(i, val){
- newButtons.push({
- "sExtends" : "text",
- "sButtonText" : ' ' + val.name + '',
- "fnClick" : clickHandlerFor(options.onNew, val.type, true),
- });
- });
- btn = {
- "sExtends" : "collection",
- "aButtons": newButtons,
- "sButtonText" : gui.config.dataTableButtons['new'].text,
- "sButtonClass" : gui.config.dataTableButtons['new'].css,
- };
- }
- break;
- case 'edit':
- btn = {
- "sExtends" : "text",
- "sButtonText" : gui.config.dataTableButtons.edit.text,
- "fnSelect" : editSelected,
- "fnClick" : clickHandlerFor(options.onEdit, 'edit'),
- "sButtonClass" : gui.config.dataTableButtons.edit.css,
- };
- break;
- case 'delete':
- btn = {
- "sExtends" : "text",
- "sButtonText" : gui.config.dataTableButtons['delete'].text,
- "fnSelect" : deleteSelected,
- "fnClick" : clickHandlerFor(options.onDelete, 'delete'),
- "sButtonClass" : gui.config.dataTableButtons['delete'].css,
- };
- break;
- case 'refresh':
- btn = {
- "sExtends" : "text",
- "sButtonText" : gui.config.dataTableButtons.refresh.text,
- "fnClick" : refreshFnc,
- "sButtonClass" : gui.config.dataTableButtons.refresh.css,
- };
- break;
- case 'xls':
- btn = {
- "sExtends" : "text",
- "sButtonText" : gui.config.dataTableButtons.xls.text,
- "fnClick" : function(){
- api.templates.get('spreadsheet', function(tmpl) {
- var styles = { 'bold': 's21', };
- var uri = 'data:application/vnd.ms-excel;base64,',
- base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))); };
-
- var headings = [], rows = [];
- $.each(columns, function(index, heading){
- if( heading.bVisible === false ) {
- return;
- }
- headings.push(api.spreadsheet.cell(heading.sTitle, 'String', styles.bold));
- });
- rows.push(api.spreadsheet.row(headings));
- $.each(data, function(index, row) {
- var cells = [];
- $.each(columns, function(index, col){
- if( col.bVisible === false ) {
- return;
- }
- var type = col.sType == 'numeric' ? 'Number':'String';
- cells.push(api.spreadsheet.cell(row[col.mData], type));
- });
- rows.push(api.spreadsheet.row(cells));
- });
-
- var ctx = {
- creation_date: (new Date()).toISOString(),
- worksheet: title,
- columns_count: headings.length,
- rows_count: rows.length,
- rows: rows.join('\n')
- };
- setTimeout( function() {
- saveAs(new Blob([api.templates.evaluate(tmpl, ctx)],
- {type: 'application/vnd.ms-excel'} ), title + '.xls');
- }, 20);
- });
- },
- "sButtonClass" : gui.config.dataTableButtons.xls.css,
- };
- }
-
- if(btn) {
- btns.push(btn);
- }
- });
- }
-
- // Initializes oTableTools
- var oTableTools = {
- "aButtons" : btns
- };
-
- // Type of row selection
- if (options.rowSelect) {
- oTableTools.sRowSelect = options.rowSelect;
- }
-
- if (options.onRowSelect) {
- var rowSelectedFnc = options.onRowSelect;
- oTableTools.fnRowSelected = function() {
- rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
- };
- }
- if (options.onRowDeselect) {
- var rowDeselectedFnc = options.onRowDeselect;
- oTableTools.fnRowDeselected = function() {
- rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
- };
- }
-
- $('#' + tableId).dataTable({
- "aaData" : data,
- "aoColumns" : columns,
- "oLanguage" : gui.config.dataTablesLanguage,
- "oTableTools" : oTableTools,
- // First is upper row,
- // second row is lower
- // (pagination) row
- "sDom" : "<'row'<'col-xs-8'T><'col-xs-4'f>r>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
-
- });
- // Fix 3dbuttons
- api.tools.fix3dButtons('#' + tableId + '_wrapper .btn-group-3d');
- // Fix form
- $('#' + 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',
- delay: { show: 1000, hide: 100},
- placement: 'auto right',
- });
-
- if (options.scrollToTable === true ) {
- var tableTop = $('#' + tableId).offset().top;
- $('html, body').scrollTop(tableTop);
- }
- // if table rendered event
- if( options.onLoad ) {
- options.onLoad($this);
- }
- });
- });
- return '#' + tableId;
- }
-
-};
diff --git a/server/src/uds/templates/uds/admin/index.html b/server/src/uds/templates/uds/admin/index.html
index 58b81d6e3..183b726dc 100644
--- a/server/src/uds/templates/uds/admin/index.html
+++ b/server/src/uds/templates/uds/admin/index.html
@@ -51,7 +51,7 @@
-
+
@@ -99,15 +99,17 @@
+
-
+
{% block js %}{% endblock %}
@@ -116,6 +118,8 @@
{% js_template 'dashboard' %}
{% js_template 'authenticators' %}
+
+ {% js_template 'request_failed' %}
{% js_template 'table' %}
{% js_template 'modal' %}
diff --git a/server/src/uds/templates/uds/admin/tmpl/modal.html b/server/src/uds/templates/uds/admin/tmpl/modal.html
index 36c99c5a6..ffae71abf 100644
--- a/server/src/uds/templates/uds/admin/tmpl/modal.html
+++ b/server/src/uds/templates/uds/admin/tmpl/modal.html
@@ -19,7 +19,7 @@
{% verbatim %}
{{/ if }}
{{# if button2 }}
- {{{ button1 }}}
+ {{{ button2 }}}
{{ else }}
{% endverbatim %}
diff --git a/server/src/uds/templates/uds/admin/tmpl/request_failed.html b/server/src/uds/templates/uds/admin/tmpl/request_failed.html
new file mode 100644
index 000000000..7f1b70943
--- /dev/null
+++ b/server/src/uds/templates/uds/admin/tmpl/request_failed.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+
+
+
{% trans 'Error on request' %}
+
+ {% verbatim %}
{{ error }}
{% endverbatim %}
+
{% trans 'There was an error requesting data from server, please, try again' %}
+
+
{% trans "Dashboard" %}
+