diff --git a/server/src/uds/REST/methods/providers.py b/server/src/uds/REST/methods/providers.py
index ad0585312..58b02b9b2 100644
--- a/server/src/uds/REST/methods/providers.py
+++ b/server/src/uds/REST/methods/providers.py
@@ -36,7 +36,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
from uds.models import Provider
from uds.core import services
-from uds.REST import Handler, HandlerError
+from uds.REST import Handler, NotFound
from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin
import logging
@@ -62,12 +62,18 @@ class Types(ModelTypeHandlerMixin, Handler):
def enum_types(self):
return services.factory().providers().values()
+ def getGui(self, type_):
+ try:
+ return services.factory().lookup(type_).guiDescription()
+ except:
+ raise NotFound('type not found')
+
class TableInfo(ModelTableHandlerMixin, Handler):
path = 'providers'
title = _('Current service providers')
fields = [
{ 'name': {'title': _('Name'), 'type': 'iconType' } },
{ 'comments': {'title': _('Comments')}},
- { 'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}}
+ { 'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}},
]
diff --git a/server/src/uds/REST/mixins.py b/server/src/uds/REST/mixins.py
index 43b98f268..581a63c86 100644
--- a/server/src/uds/REST/mixins.py
+++ b/server/src/uds/REST/mixins.py
@@ -145,6 +145,41 @@ class ModelTypeHandlerMixin(object):
if self._args[1] == 'gui':
gui = self.getGui(self._args[0])
+ # Add name default description, at top of form
+ gui.insert(0, {
+ 'name': 'name',
+ 'value':'',
+ 'gui': {
+ 'required':True,
+ 'defvalue':'',
+ 'value':'',
+ 'label': _('Name'),
+ 'length': 128,
+ 'multiline': 0,
+ 'tooltip': _('Name of this element'),
+ 'rdonly': False,
+ 'type': 'text',
+ 'order': 1
+ }
+ })
+ # And comments
+ gui.insert(1, {
+ 'name': 'comments',
+ 'value':'',
+ 'gui': {
+ 'required':True,
+ '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 gui
diff --git a/server/src/uds/static/adm/css/uds-admin.css b/server/src/uds/static/adm/css/uds-admin.css
index 83232c2a9..cb127137e 100644
--- a/server/src/uds/static/adm/css/uds-admin.css
+++ b/server/src/uds/static/adm/css/uds-admin.css
@@ -92,7 +92,9 @@ table.tablesorter thead tr th:hover {
margin-bottom:12px;
}
-
+.label-tbl-button {
+ display: none;
+}
/* Edit Below to Customize Widths > 768px */
@media (min-width:768px) {
@@ -166,4 +168,9 @@ table.tablesorter thead tr th:hover {
white-space: normal;
}
+ .label-tbl-button {
+ display: inline-block;
+ }
+}
+
}
diff --git a/server/src/uds/static/adm/js/spreadsheet.js b/server/src/uds/static/adm/js/api-spreadsheet.js
similarity index 100%
rename from server/src/uds/static/adm/js/spreadsheet.js
rename to server/src/uds/static/adm/js/api-spreadsheet.js
diff --git a/server/src/uds/static/adm/js/tools.js b/server/src/uds/static/adm/js/api-tools.js
similarity index 100%
rename from server/src/uds/static/adm/js/tools.js
rename to server/src/uds/static/adm/js/api-tools.js
diff --git a/server/src/uds/static/adm/js/gui-elements.js b/server/src/uds/static/adm/js/gui-elements.js
index afd12f2d7..e85bf34b4 100644
--- a/server/src/uds/static/adm/js/gui-elements.js
+++ b/server/src/uds/static/adm/js/gui-elements.js
@@ -34,8 +34,8 @@ gui.providers.link = function(event) {
var tableId = gui.providers.table({
rowSelect : 'multi',
- rowSelectFnc : function(nodes) {
- gui.doLog(nodes);
+ rowSelectFnc : function(data) {
+ gui.doLog(data);
gui.doLog(this);
gui.doLog(this.fnGetSelectedData());
},
@@ -65,15 +65,15 @@ gui.authenticators.link = function(event) {
container : 'auths-placeholder',
rowSelect : 'single',
buttons : [ 'edit', 'refresh', 'delete', 'xls' ],
- onRowSelect : function(nodes) {
+ onRowSelect : function(selected) {
api.tools.blockUI();
- var id = this.fnGetSelectedData()[0].id;
+ var id = selected[0].id;
var user = new GuiElement(api.authenticators.detail(id, 'users'), 'users');
user.table({
container : 'users-placeholder',
rowSelect : 'multi',
buttons : [ 'edit', 'refresh', 'delete', 'xls' ],
- scroll : true,
+ scrollToTable : true,
onLoad: function(k) {
api.tools.unblockUI();
},
diff --git a/server/src/uds/static/adm/js/gui.js b/server/src/uds/static/adm/js/gui.js
index afbb70dc4..929351dd9 100644
--- a/server/src/uds/static/adm/js/gui.js
+++ b/server/src/uds/static/adm/js/gui.js
@@ -12,9 +12,11 @@
}
};
-
- // Several convenience "constants"
- gui.dataTablesLanguage = {
+
+ gui.config = gui.config || {};
+
+ // Several convenience "constants" for tables
+ gui.config.dataTablesLanguage = {
"sLengthMenu" : gettext("_MENU_ records per page"),
"sZeroRecords" : gettext("Empty"),
"sInfo" : gettext("Records _START_ to _END_ of _TOTAL_"),
@@ -30,6 +32,14 @@
"sPrevious" : gettext("Previous"),
}
};
+
+ gui.config.dataTableButtonsText = {
+ 'new': ' ' + gettext('New') + '',
+ 'edit': ' ' + gettext('Edit') + '',
+ 'delete': ' ' + gettext('Delete') + '',
+ 'refresh': ' ' + gettext('Refresh') + '',
+ 'xls': ' ' + gettext('Xls') + '',
+ };
gui.table = function(title, table_id, options) {
if (options === undefined)
@@ -158,19 +168,48 @@ GuiElement.prototype = {
},
});
},
+ // 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
+ // 3.- the DataTableTools 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
+ // 4.- the DataTableTools 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
+ // 4.- the DataTableTools 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
+ // 5.- the DataTableTools that raised the event
table : function(options) {
"use strict";
- // Options (all are optionals)
- // rowSelect: 'single' or 'multi'
- // container: ID of the element that will hold this table (will be
- // emptied)
- // rowSelectFnc: function to invoke on row selection. receives 1 array -
- // node : TR elements that were selected
- // rowDeselectFnc: function to invoke on row deselection. receives 1
- // array - node : TR elements that were selected
+ options = options || {};
gui.doLog('Composing table for ' + this.name);
var tableId = this.name + '-table';
- var $this = this;
+ var $this = this; // Store this for child functions
// Empty cells transform
var renderEmptyCell = function(data) {
@@ -212,21 +251,28 @@ GuiElement.prototype = {
return dict[data] || renderEmptyCell('');
};
};
-
this.rest.tableInfo({
success: function(data) {
var title = data.title;
var columns = [];
$.each(data.fields, function(index, value) {
for ( var v in value) {
- var options = value[v];
+ var opts = value[v];
var column = {
mData : v,
};
- column.sTitle = options.title;
+ column.sTitle = opts.title;
column.mRender = renderEmptyCell;
- if (options.type !== undefined) {
- switch(options.type) {
+ 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 !== undefined && column.bVisible ) {
+ switch(opts.type) {
case 'date':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT')));
@@ -243,31 +289,23 @@ GuiElement.prototype = {
column.mRender = renderTypeIcon;
break;
case 'icon':
- if( options.icon !== undefined ) {
- column.mRender = renderIcon(options.icon);
+ if( opts.icon !== undefined ) {
+ column.mRender = renderIcon(opts.icon);
}
break;
case 'dict':
- if( options.dict !== undefined ) {
- column.mRender = renderTextTransform(options.dict);
+ if( opts.dict !== undefined ) {
+ column.mRender = renderTextTransform(opts.dict);
}
break;
default:
- column.sType = options.type;
+ column.sType = opts.type;
}
}
- if (options.width)
- column.sWidth = options.width;
- if (options.visible !== undefined)
- column.bVisible = options.visible;
- if (options.sortable !== undefined)
- column.bSortable = options.sortable;
- if (options.searchable !== undefined)
- column.bSearchable = options.searchable;
columns.push(column);
}
});
- // Generate styles for responsibe table, just the name of fields
+ // Generate styles for responsible table, just the name of fields
var respStyles = [];
var counter = 0;
$.each(columns, function(col, value) {
@@ -295,17 +333,18 @@ GuiElement.prototype = {
var btns = [];
if (options.buttons) {
+ var clickHandlerFor = function(handler, action) {
+ var handleFnc = handler || function(val, action, tbl, tbltools) {gui.doLog('Default handler called for ' + action + ': ' + JSON.stringify(val));};
+ return function(btn) {
+ var tblTools = this;
+ var table = $('#' + tableId).dataTable();
+ var val = this.fnGetSelectedData()[0];
+ setTimeout(function() {
+ handleFnc(val, action, table, tblTools);
+ }, 0);
+ };
+ };
- // methods for buttons click
- var editFnc = function() {
- gui.doLog('Edit');
- gui.doLog(this);
- };
- var deleteFnc = function() {
- gui.doLog('Delete');
- gui.doLog(this);
- };
-
// What execute on refresh button push
var onRefresh = options.onRefresh || function(){};
@@ -354,28 +393,37 @@ GuiElement.prototype = {
$.each(options.buttons, function(index, value) {
var btn;
switch (value) {
+ case 'new':
+ btn = {
+ "sExtends" : "text",
+ "sButtonText" : gui.config.dataTableButtonsText['new'],
+ "fnSelect" : deleteSelected,
+ "fnClick" : clickHandlerFor(options.onDelete, 'delete'),
+ "sButtonClass" : "disabled btn3d btn3d-tables"
+ };
+ break;
case 'edit':
btn = {
"sExtends" : "text",
- "sButtonText" : gettext('Edit'),
+ "sButtonText" : gui.config.dataTableButtonsText['edit'],
"fnSelect" : editSelected,
- "fnClick" : editFnc,
+ "fnClick" : clickHandlerFor(options.onEdit, 'edit'),
"sButtonClass" : "disabled btn3d btn3d-tables"
};
break;
case 'delete':
btn = {
"sExtends" : "text",
- "sButtonText" : gettext('Delete'),
+ "sButtonText" : gui.config.dataTableButtonsText['delete'],
"fnSelect" : deleteSelected,
- "fnClick" : deleteFnc,
+ "fnClick" : clickHandlerFor(options.onDelete, 'delete'),
"sButtonClass" : "disabled btn3d btn3d-tables"
};
break;
case 'refresh':
btn = {
"sExtends" : "text",
- "sButtonText" : gettext('Refresh'),
+ "sButtonText" : gui.config.dataTableButtonsText['refresh'],
"fnClick" : refreshFnc,
"sButtonClass" : "btn3d-primary btn3d btn3d-tables"
};
@@ -383,7 +431,7 @@ GuiElement.prototype = {
case 'xls':
btn = {
"sExtends" : "text",
- "sButtonText" : 'xls',
+ "sButtonText" : gui.config.dataTableButtonsText['xls'],
"fnClick" : function(){
api.templates.get('spreadsheet', function(tmpl) {
var styles = { 'bold': 's21', };
@@ -417,7 +465,6 @@ GuiElement.prototype = {
rows_count: rows.length,
rows: rows.join('\n')
};
- // window.location.href = uri + base64(api.templates.evaluate(tmpl, ctx));
setTimeout( function() {
saveAs(new Blob([api.templates.evaluate(tmpl, ctx)],
{type: 'application/vnd.ms-excel'} ), title + '.xls');
@@ -437,20 +484,29 @@ GuiElement.prototype = {
var oTableTools = {
"aButtons" : btns
};
+
+ // Type of row selection
if (options.rowSelect) {
oTableTools.sRowSelect = options.rowSelect;
}
+
if (options.onRowSelect) {
- oTableTools.fnRowSelected = options.onRowSelect;
+ var rowSelectedFnc = options.onRowSelect;
+ oTableTools.fnRowSelected = function() {
+ rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
+ };
}
if (options.onRowDeselect) {
- oTableTools.fnRowDeselected = options.onRowDeselect;
+ var rowDeselectedFnc = options.onRowDeselect;
+ oTableTools.fnRowDeselected = function() {
+ rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
+ };
}
$('#' + tableId).dataTable({
"aaData" : data,
"aoColumns" : columns,
- "oLanguage" : gui.dataTablesLanguage,
+ "oLanguage" : gui.config.dataTablesLanguage,
"oTableTools" : oTableTools,
// First is upper row,
// second row is lower
@@ -462,7 +518,7 @@ GuiElement.prototype = {
api.tools.fix3dButtons('#' + tableId + '_wrapper .btn-group-3d');
// Fix form
//$('#' + tableId + '_filter input').addClass('form-control');
- if (options.scroll !== undefined ) {
+ if (options.scrollToTable === true ) {
var tableTop = $('#' + tableId).offset().top;
$('html, body').scrollTop(tableTop);
}
diff --git a/server/src/uds/static/adm/js/strftime.js b/server/src/uds/static/adm/js/strftime.js
deleted file mode 100644
index 293edf205..000000000
--- a/server/src/uds/static/adm/js/strftime.js
+++ /dev/null
@@ -1,392 +0,0 @@
-//
-// strftime
-// github.com/samsonjs/strftime
-// @_sjs
-//
-// Copyright 2010 - 2013 Sami Samhuri
-//
-// MIT License
-// http://sjs.mit-license.org
-//
-
-;(function() {
-
- var namespace = api.tools;
-
- var dayNames = [ gettext('Sunday'), gettext('Monday'), gettext('Tuesday'), gettext('Wednesday'),
- gettext('Thursday'), gettext('Friday'), gettext('Saturday') ];
- var monthNames = [ gettext('January'), gettext('February'), gettext('March'), gettext('April'), gettext('May'),
- gettext('June'), gettext('July'), gettext('August'), gettext('September'), gettext('October'),
- gettext('November'), gettext('December') ];
-
- function initialsOf(arr) {
- var res = [];
- for ( var v in arr) {
- res.push(arr[v].substr(0, 3));
- }
- return res;
- }
-
- var DefaultLocale = {
- days : dayNames,
- shortDays : initialsOf(dayNames),
- months : monthNames,
- shortMonths : initialsOf(monthNames),
- AM : 'AM',
- PM : 'PM',
- am : 'am',
- pm : 'pm',
- };
-
- // Added this to convert django format strings to c format string
- // This is ofc, a "simplified" version, aimed to use date format used by
- // DJANGO
- namespace.djangoFormat = function(format) {
- return format.replace(/./g, function(c) {
- switch (c) {
- case 'a':
- case 'A':
- return '%p';
- case 'b':
- case 'd':
- case 'm':
- case 'w':
- case 'W':
- case 'y':
- case 'Y':
- return '%' + c;
- case 'c':
- return '%FT%TZ';
- case 'D':
- return '%a';
- case 'e':
- return '%z';
- case 'f':
- return '%I:%M';
- case 'F':
- return '%F';
- case 'h':
- case 'g':
- return '%I';
- case 'H':
- case 'G':
- return '%H';
- case 'i':
- return '%M';
- case 'I':
- return ''; // daylight saving
- case 'j':
- return '%d';
- case 'l':
- return '%A';
- case 'L':
- return ''; // if it is leap year
- case 'M':
- return '%b';
- case 'n':
- return '%m';
- case 'N':
- return '%b';
- case 'o':
- return '%W'; // Not so sure, not important i thing anyway :-)
- case 'O':
- return '%z';
- case 'P':
- return '%R %p';
- case 'r':
- return '%a, %d %b %Y %T %z';
- case 's':
- return '%S';
- case 'S':
- return ''; // english ordinal suffix for day of month
- case 't':
- return ''; // number of days of specified month, not important
- case 'T':
- return '%Z';
- case 'u':
- return '0'; // microseconds
- case 'U':
- return ''; // Seconds since EPOCH, not used
- case 'z':
- return '%j';
- case 'Z':
- return 'z'; // Time zone offset in seconds, replaced by offset
- // in ours/minutes :-)
- default:
- return c;
- }
-
- });
- };
-
- namespace.strftime = strftime;
- function strftime(fmt, d, locale) {
- return _strftime(fmt, d, locale);
- }
-
- // locale is optional
- namespace.strftimeTZ = strftime.strftimeTZ = strftimeTZ;
- function strftimeTZ(fmt, d, locale, timezone) {
- if (typeof locale == 'number' && timezone === null) {
- timezone = locale;
- locale = undefined;
- }
- return _strftime(fmt, d, locale, {
- timezone : timezone
- });
- }
-
- namespace.strftimeUTC = strftime.strftimeUTC = strftimeUTC;
- function strftimeUTC(fmt, d, locale) {
- return _strftime(fmt, d, locale, {
- utc : true
- });
- }
-
- namespace.localizedStrftime = strftime.localizedStrftime = localizedStrftime;
- function localizedStrftime(locale) {
- return function(fmt, d, options) {
- return strftime(fmt, d, locale, options);
- };
- }
-
- // d, locale, and options are optional, but you can't leave
- // holes in the argument list. If you pass options you have to pass
- // in all the preceding args as well.
- //
- // options:
- // - locale [object] an object with the same structure as DefaultLocale
- // - timezone [number] timezone offset in minutes from GMT
- function _strftime(fmt, d, locale, options) {
- options = options || {};
-
- // d and locale are optional so check if d is really the locale
- if (d && !quacksLikeDate(d)) {
- locale = d;
- d = undefined;
- }
- d = d || new Date();
-
- locale = locale || DefaultLocale;
- locale.formats = locale.formats || {};
-
- // Hang on to this Unix timestamp because we might mess with it directly
- // below.
- var timestamp = d.getTime();
-
- if (options.utc || typeof options.timezone == 'number') {
- d = dateToUTC(d);
- }
-
- if (typeof options.timezone == 'number') {
- d = new Date(d.getTime() + (options.timezone * 60000));
- }
-
- // Most of the specifiers supported by C's strftime, and some from Ruby.
- // Some other syntax extensions from Ruby are supported: %-, %_, and %0
- // to pad with nothing, space, or zero (respectively).
- return fmt.replace(/%([-_0]?.)/g, function(_, c) {
- var mod, padding;
- if (c.length == 2) {
- mod = c[0];
- // omit padding
- if (mod == '-') {
- padding = '';
- }
- // pad with space
- else if (mod == '_') {
- padding = ' ';
- }
- // pad with zero
- else if (mod == '0') {
- padding = '0';
- } else {
- // unrecognized, return the format
- return _;
- }
- c = c[1];
- }
- switch (c) {
- case 'A':
- return locale.days[d.getDay()];
- case 'a':
- return locale.shortDays[d.getDay()];
- case 'B':
- return locale.months[d.getMonth()];
- case 'b':
- return locale.shortMonths[d.getMonth()];
- case 'C':
- return pad(Math.floor(d.getFullYear() / 100), padding);
- case 'D':
- return _strftime(locale.formats.D || '%m/%d/%y', d, locale);
- case 'd':
- return pad(d.getDate(), padding);
- case 'e':
- return d.getDate();
- case 'F':
- return _strftime(locale.formats.F || '%Y-%m-%d', d, locale);
- case 'H':
- return pad(d.getHours(), padding);
- case 'h':
- return locale.shortMonths[d.getMonth()];
- case 'I':
- return pad(hours12(d), padding);
- case 'j':
- var y = new Date(d.getFullYear(), 0, 1);
- var day = Math.ceil((d.getTime() - y.getTime()) / (1000 * 60 * 60 * 24));
- return pad(day, 3);
- case 'k':
- return pad(d.getHours(), padding === null ? ' ' : padding);
- case 'L':
- return pad(Math.floor(timestamp % 1000), 3);
- case 'l':
- return pad(hours12(d), padding === null ? ' ' : padding);
- case 'M':
- return pad(d.getMinutes(), padding);
- case 'm':
- return pad(d.getMonth() + 1, padding);
- case 'n':
- return '\n';
- case 'o':
- return String(d.getDate()) + ordinal(d.getDate());
- case 'P':
- return d.getHours() < 12 ? locale.am : locale.pm;
- case 'p':
- return d.getHours() < 12 ? locale.AM : locale.PM;
- case 'R':
- return _strftime(locale.formats.R || '%H:%M', d, locale);
- case 'r':
- return _strftime(locale.formats.r || '%I:%M:%S %p', d, locale);
- case 'S':
- return pad(d.getSeconds(), padding);
- case 's':
- return Math.floor(timestamp / 1000);
- case 'T':
- return _strftime(locale.formats.T || '%H:%M:%S', d, locale);
- case 't':
- return '\t';
- case 'U':
- return pad(weekNumber(d, 'sunday'), padding);
- case 'u':
- var dayu = d.getDay();
- return dayu === 0 ? 7 : dayu; // 1 - 7, Monday is first day of the
- // week
- case 'v':
- return _strftime(locale.formats.v || '%e-%b-%Y', d, locale);
- case 'W':
- return pad(weekNumber(d, 'monday'), padding);
- case 'w':
- return d.getDay(); // 0 - 6, Sunday is first day of the
- // week
- case 'Y':
- return d.getFullYear();
- case 'y':
- var yy = String(d.getFullYear());
- return yy.slice(yy.length - 2);
- case 'Z':
- if (options.utc) {
- return "GMT";
- } else {
- var tz = d.toString().match(/\((\w+)\)/);
- return tz && tz[1] || '';
- }
- break;
- case 'z':
- if (options.utc) {
- return "+0000";
- } else {
- var off = typeof options.timezone == 'number' ? options.timezone : -d.getTimezoneOffset();
- return (off < 0 ? '-' : '+') + pad(Math.abs(off / 60)) + pad(off % 60);
- }
- break;
- default:
- return c;
- }
- });
- }
-
- function dateToUTC(d) {
- var msDelta = (d.getTimezoneOffset() || 0) * 60000;
- return new Date(d.getTime() + msDelta);
- }
-
- var RequiredDateMethods = [ 'getTime', 'getTimezoneOffset', 'getDay', 'getDate', 'getMonth', 'getFullYear',
- 'getYear', 'getHours', 'getMinutes', 'getSeconds' ];
- function quacksLikeDate(x) {
- var i = 0, n = RequiredDateMethods.length;
- for (i = 0; i < n; ++i) {
- if (typeof x[RequiredDateMethods[i]] != 'function') {
- return false;
- }
- }
- return true;
- }
-
- // Default padding is '0' and default length is 2, both are optional.
- function pad(n, padding, length) {
- // pad(n, )
- if (typeof padding === 'number') {
- length = padding;
- padding = '0';
- }
-
- // Defaults handle pad(n) and pad(n, )
- if (padding === null) {
- padding = '0';
- }
- length = length || 2;
-
- var s = String(n);
- // padding may be an empty string, don't loop forever if it is
- if (padding) {
- while (s.length < length)
- s = padding + s;
- }
- return s;
- }
-
- function hours12(d) {
- var hour = d.getHours();
- if (hour === 0)
- hour = 12;
- else if (hour > 12)
- hour -= 12;
- return hour;
- }
-
- // Get the ordinal suffix for a number: st, nd, rd, or th
- function ordinal(n) {
- var i = n % 10, ii = n % 100;
- if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) {
- return 'th';
- }
- switch (i) {
- case 1:
- return 'st';
- case 2:
- return 'nd';
- case 3:
- return 'rd';
- }
- }
-
- // firstWeekday: 'sunday' or 'monday', default is 'sunday'
- //
- // Pilfered & ported from Ruby's strftime implementation.
- function weekNumber(d, firstWeekday) {
- firstWeekday = firstWeekday || 'sunday';
-
- // This works by shifting the weekday back by one day if we
- // are treating Monday as the first day of the week.
- var wday = d.getDay();
- if (firstWeekday == 'monday') {
- if (wday === 0) // Sunday
- wday = 6;
- else
- wday--;
- }
- var firstDayOfYear = new Date(d.getFullYear(), 0, 1), yday = (d - firstDayOfYear) / 86400000, weekNum = (yday + 7 - wday) / 7;
- return Math.floor(weekNum);
- }
-
-}());
diff --git a/server/src/uds/templates/uds/admin/index.html b/server/src/uds/templates/uds/admin/index.html
index 31ecaeab2..a09b38e6b 100644
--- a/server/src/uds/templates/uds/admin/index.html
+++ b/server/src/uds/templates/uds/admin/index.html
@@ -55,10 +55,6 @@
-
-
-
-
@@ -80,18 +76,22 @@
+
+
+
-
+
-
-
+
+
+