forked from shaba/openuds
* Implemented services REST part
* Added possibility of creating own types for DetailApi (useful for provicers/services, where services is a detail but also has types) More refactoring
This commit is contained in:
parent
d46400c1f7
commit
7ea7086ba9
@ -15,6 +15,7 @@ encoding//src/uds/REST/methods/login_logout.py=utf-8
|
||||
encoding//src/uds/REST/methods/networks.py=utf-8
|
||||
encoding//src/uds/REST/methods/osmanagers.py=utf-8
|
||||
encoding//src/uds/REST/methods/providers.py=utf-8
|
||||
encoding//src/uds/REST/methods/services.py=utf-8
|
||||
encoding//src/uds/REST/methods/transports.py=utf-8
|
||||
encoding//src/uds/REST/methods/users_groups.py=utf-8
|
||||
encoding//src/uds/REST/mixins.py=utf-8
|
||||
|
@ -34,6 +34,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from uds.models import Provider
|
||||
from services import Services
|
||||
from uds.core import services
|
||||
|
||||
from uds.REST import Handler, NotFound
|
||||
@ -46,12 +47,23 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Providers(ModelHandlerMixin, Handler):
|
||||
model = Provider
|
||||
detail = { 'services': Services }
|
||||
save_fields = ['name', 'comments']
|
||||
|
||||
def item_as_dict(self, provider):
|
||||
type_ = provider.getType()
|
||||
|
||||
# Icon can have a lot of data (1-2 Kbytes), but it's not expected to have a lot of services providers, and even so, this will work fine
|
||||
offers = [{
|
||||
'name' : ugettext(t.name()),
|
||||
'type' : t.type(),
|
||||
'description' : ugettext(t.description()),
|
||||
'icon' : t.icon().replace('\n', '') } for t in type_.getServicesTypes()]
|
||||
|
||||
return { 'id': provider.id,
|
||||
'name': provider.name,
|
||||
'services_count': provider.services.count(),
|
||||
'offers': offers,
|
||||
'type': type_.type(),
|
||||
'comments': provider.comments,
|
||||
}
|
||||
@ -64,13 +76,15 @@ class Types(ModelTypeHandlerMixin, Handler):
|
||||
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return services.factory().lookup(type_).guiDescription()
|
||||
return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments'])
|
||||
except:
|
||||
raise NotFound('type not found')
|
||||
|
||||
class TableInfo(ModelTableHandlerMixin, Handler):
|
||||
path = 'providers'
|
||||
detail = { 'services': Services }
|
||||
title = _('Current service providers')
|
||||
|
||||
fields = [
|
||||
{ 'name': {'title': _('Name'), 'type': 'iconType' } },
|
||||
{ 'comments': {'title': _('Comments')}},
|
||||
|
84
server/src/uds/REST/methods/services.py
Normal file
84
server/src/uds/REST/methods/services.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@provideror: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from uds.models import Provider
|
||||
|
||||
from uds.REST.mixins import DetailHandler
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Services(DetailHandler):
|
||||
|
||||
def get(self):
|
||||
# Extract providerenticator
|
||||
provider = self._kwargs['parent']
|
||||
|
||||
try:
|
||||
if len(self._args) == 0:
|
||||
return [{
|
||||
'id':k.id,
|
||||
'name': k.name,
|
||||
'comments': k.comments,
|
||||
'type': k.data_type,
|
||||
'typeName' : _(k.getType().name())
|
||||
} for k in provider.services.all() ]
|
||||
else:
|
||||
with provider.get(pk=self._args[0]) as k:
|
||||
return {
|
||||
'id':k.id,
|
||||
'name': k.name,
|
||||
'comments': k.comments,
|
||||
'type': k.data_type,
|
||||
'typeName' : _(k.getType().name())
|
||||
}
|
||||
except:
|
||||
logger.exception('En services')
|
||||
return { 'error': 'not found' }
|
||||
|
||||
def getTitle(self):
|
||||
try:
|
||||
return _('Services of {0}').format(Provider.objects.get(pk=self._kwargs['parent_id']).name)
|
||||
except:
|
||||
return _('Current services')
|
||||
|
||||
def getFields(self):
|
||||
return [
|
||||
{ 'name': {'title': _('Service name'), 'visible': True, 'type': 'iconType' } },
|
||||
{ 'comments': { 'title': _('Comments') } },
|
||||
{ 'type': {'title': _('Type') } }
|
||||
]
|
@ -56,7 +56,7 @@ class Transports(ModelHandlerMixin, Handler):
|
||||
'comments': item.comments,
|
||||
'priority': item.priority,
|
||||
'nets_positive': item.nets_positive,
|
||||
'networks': [ {'id': k.id} for k in item.networks.all() ],
|
||||
'networks': [ n.id for n in item.networks.all() ],
|
||||
'deployed_count': item.deployedServices.count(),
|
||||
'type': type_.type(),
|
||||
}
|
||||
@ -64,7 +64,6 @@ class Transports(ModelHandlerMixin, Handler):
|
||||
|
||||
class Types(ModelTypeHandlerMixin, Handler):
|
||||
path = 'transports'
|
||||
has_comments = True
|
||||
|
||||
def enum_types(self):
|
||||
return factory().providers().values()
|
||||
|
@ -183,7 +183,6 @@ class ModelTypeHandlerMixin(object):
|
||||
'''
|
||||
authenticated = True
|
||||
needs_staff = True
|
||||
has_comments = False
|
||||
|
||||
def enum_types(self):
|
||||
pass
|
||||
|
@ -767,6 +767,10 @@ class UserInterface(object):
|
||||
val = '\001' + cPickle.dumps(v.value)
|
||||
else:
|
||||
val = v.value
|
||||
if val is True:
|
||||
val = gui.TRUE
|
||||
elif val is False:
|
||||
val = gui.FALSE
|
||||
arr.append(k + '\003' + val)
|
||||
return '\002'.join(arr).encode('zip')
|
||||
|
||||
|
@ -59,12 +59,16 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
/* modal dialogs & related*/
|
||||
.modal-dialog {
|
||||
/* new custom width */
|
||||
width: 60%;
|
||||
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.tooltip {
|
||||
z-index: 2014;
|
||||
}
|
||||
|
@ -331,16 +331,18 @@ BasicModelRest.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
detail: function(id, child) {
|
||||
detail: function(id, child, options) {
|
||||
"use strict";
|
||||
return new DetailModelRestApi(this, id, child);
|
||||
options = options || {};
|
||||
return new DetailModelRestApi(this, id, child, options);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// For REST of type /auth/[id]/users, /services/[id]/users, ...
|
||||
function DetailModelRestApi(parentApi, parentId, model) {
|
||||
function DetailModelRestApi(parentApi, parentId, model, options) {
|
||||
"use strict";
|
||||
this.options = options;
|
||||
this.base = new BasicModelRest(undefined, {
|
||||
getPath: [parentApi.path, parentId, model].join('/'),
|
||||
typesPath: '.', // We do not has this on details
|
||||
@ -350,29 +352,33 @@ function DetailModelRestApi(parentApi, parentId, model) {
|
||||
|
||||
DetailModelRestApi.prototype = {
|
||||
// Generates a basic model with fixed methods for "detail" models
|
||||
get: function(options) {
|
||||
get: function(success_fnc, fail_fnc) {
|
||||
"use strict";
|
||||
return this.base.get(options);
|
||||
return this.base.get(success_fnc, fail_fnc);
|
||||
},
|
||||
tableInfo: function(options) {
|
||||
tableInfo: function(success_fnc, fail_fnc) {
|
||||
"use strict";
|
||||
return this.base.tableInfo(options);
|
||||
return this.base.tableInfo(success_fnc, fail_fnc);
|
||||
},
|
||||
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";
|
||||
return this.base.list(success_fnc, options);
|
||||
return this.base.list(success_fnc, fail_fnc);
|
||||
},
|
||||
overview: function(success_fnc, options) {
|
||||
overview: function(success_fnc, fail_fnc) {
|
||||
"use strict";
|
||||
return this.base.overview(success_fnc, options);
|
||||
return this.base.overview(success_fnc, fail_fnc);
|
||||
},
|
||||
item: function(itemId, success_fnc, options) {
|
||||
item: function(itemId, success_fnc, fail_fnc) {
|
||||
"use strict";
|
||||
return this.base.item(success_fnc, options);
|
||||
return this.base.item(success_fnc, fail_fnc);
|
||||
},
|
||||
types: function(options) {
|
||||
types: function(success_fnc, fail_fnc) {
|
||||
"use strict";
|
||||
return this.base.types(options);
|
||||
if( this.options.types ) {
|
||||
this.options.types(success_fnc, fail_fnc);
|
||||
} else {
|
||||
return this.base.types(success_fnc, fail_fnc);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -29,22 +29,60 @@ gui.dashboard.link = function(event) {
|
||||
gui.providers = new GuiElement(api.providers, 'provi');
|
||||
gui.providers.link = function(event) {
|
||||
"use strict";
|
||||
gui.clearWorkspace();
|
||||
gui.appendToWorkspace(gui.breadcrumbs(gettext('Service Providers')));
|
||||
api.templates.get('providers', function(tmpl) {
|
||||
gui.clearWorkspace();
|
||||
gui.appendToWorkspace(api.templates.evaluate(tmpl, {
|
||||
providers : 'providers-placeholder',
|
||||
services : 'services-placeholder',
|
||||
}));
|
||||
gui.setLinksEvents();
|
||||
|
||||
var tableId = gui.providers.table({
|
||||
rowSelect : 'single',
|
||||
onEdit: function(value, event, table) {
|
||||
gui.providers.rest.gui(value.type, function(data) {
|
||||
var form = gui.fields(data);
|
||||
gui.appendToWorkspace(gui.modal('edit_modal', gettext('Edit service provider'), form));
|
||||
$('#edit_modal').modal()
|
||||
.on('hidden.bs.modal', function () {
|
||||
$('#edit_modal').remove();
|
||||
var tableId = gui.providers.table({
|
||||
container : 'providers-placeholder',
|
||||
rowSelect : 'single',
|
||||
onRowSelect : function(selected) {
|
||||
api.tools.blockUI();
|
||||
gui.doLog(selected[0]);
|
||||
var id = selected[0].id;
|
||||
// Options for detail, to initialize types correctly
|
||||
var detail_options = {
|
||||
types: function(success_fnc, fail_fnc) {
|
||||
success_fnc(selected[0].offers);
|
||||
}
|
||||
};
|
||||
// Giving the name compossed with type, will ensure that only styles will be reattached once
|
||||
var services = new GuiElement(api.providers.detail(id, 'services', detail_options), 'services-'+selected[0].type);
|
||||
|
||||
services.table({
|
||||
container : 'services-placeholder',
|
||||
rowSelect : 'single',
|
||||
buttons : [ 'new', 'edit', 'delete', 'xls' ],
|
||||
scrollToTable : false,
|
||||
onLoad: function(k) {
|
||||
api.tools.unblockUI();
|
||||
},
|
||||
});
|
||||
return false;
|
||||
},
|
||||
buttons : [ 'new', 'edit', 'delete', 'xls' ],
|
||||
onEdit: function(value, event, table, refreshFnc) {
|
||||
gui.providers.rest.gui(value.type, function(itemGui){
|
||||
gui.providers.rest.item(value.id, function(item) {
|
||||
gui.forms.launchModal(gettext('Edit Service Provider')+' '+value.name, itemGui, item, function(form_selector, closeFnc) {
|
||||
var fields = gui.forms.read(form_selector);
|
||||
fields.data_type = value.type;
|
||||
fields.nets_positive = false;
|
||||
gui.providers.rest.save(fields, function(data) { // Success on put
|
||||
closeFnc();
|
||||
refreshFnc();
|
||||
}, gui.failRequestModalFnc(gettext('Error creating Service Provider')) // Fail on put, show modal message
|
||||
);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
buttons : [ 'edit', 'delete', 'xls' ],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
@ -157,9 +195,8 @@ gui.connectivity.link = function(event) {
|
||||
},
|
||||
]
|
||||
};
|
||||
var form = gui.form.fromFields(tabs, item);
|
||||
gui.launchModalForm(gettext('Edit transport')+' '+value.name,form, function(form_selector, closeFnc) {
|
||||
var fields = gui.form.read(form_selector);
|
||||
gui.forms.launchModal(gettext('Edit transport')+' '+value.name, tabs, item, function(form_selector, closeFnc) {
|
||||
var fields = gui.forms.read(form_selector);
|
||||
fields.data_type = value.type;
|
||||
fields.nets_positive = false;
|
||||
gui.connectivity.transports.rest.save(fields, function(data) { // Success on put
|
||||
@ -182,12 +219,17 @@ gui.connectivity.link = function(event) {
|
||||
},
|
||||
{
|
||||
title: 'Networks',
|
||||
fields: [],
|
||||
fields: [
|
||||
gui.forms.guiField('networks', 'multichoice', gettext('Available for networks'),
|
||||
gettext('Select networks that will see this transport'), [], []),
|
||||
gui.forms.guiField('nets_positive', 'checkbox', gettext('Transport active for selected networks'),
|
||||
gettext('If active, transport will only be available on selected networks. If inactive, transport will be available form any net EXCEPT selected networks'),
|
||||
true)
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
var form = gui.form.fromFields(tabs);
|
||||
gui.launchModalForm(gettext('New transport'), form, function(form_selector, closeFnc) {
|
||||
gui.forms.launchModal(gettext('New transport'), tabs, undefined, function(form_selector, closeFnc) {
|
||||
var fields = gui.form.read(form_selector);
|
||||
// Append "own" fields, in this case data_type
|
||||
fields.data_type = type;
|
||||
|
@ -5,7 +5,7 @@ function BasicGuiElement(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
function GuiElement(restItem, name) {
|
||||
function GuiElement(restItem, name, typesFunction) {
|
||||
"use strict";
|
||||
this.rest = restItem;
|
||||
this.name = name;
|
||||
@ -22,6 +22,7 @@ GuiElement.prototype = {
|
||||
var self = this;
|
||||
this.rest.types(function(data) {
|
||||
var styles = '';
|
||||
var alreadyAttached = $('#gui-style-'+self.name).length !== 0;
|
||||
$.each(data, function(index, value) {
|
||||
var className = self.name + '-' + value.type;
|
||||
self.types[value.type] = {
|
||||
@ -30,12 +31,15 @@ GuiElement.prototype = {
|
||||
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( !alreadyAttached ) {
|
||||
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 = '<style media="screen">' + styles + '</style>';
|
||||
// If style already attached, do not re-attach it
|
||||
styles = '<style id="gui-style-' + self.name + '" media="screen">' + styles + '</style>';
|
||||
$(styles).appendTo('head');
|
||||
}
|
||||
});
|
||||
@ -101,7 +105,8 @@ GuiElement.prototype = {
|
||||
// Icon renderer, based on type (created on init methods in styles)
|
||||
var renderTypeIcon = function(data, type, value){
|
||||
if( type == 'display' ) {
|
||||
var css = self.types[value.type].css;
|
||||
self.types[value.type] = self.types[value.type] || {}
|
||||
var css = self.types[value.type].css || 'fa fa-asterisk';
|
||||
return '<span class="' + css + '"></span> ' + renderEmptyCell(data);
|
||||
} else {
|
||||
return renderEmptyCell(data);
|
||||
|
@ -2,8 +2,10 @@
|
||||
(function(gui, $, undefined) {
|
||||
"use strict";
|
||||
|
||||
// Returns a form that will manage a gui description (new or edit)
|
||||
gui.fieldsToHtml = function(itemGui, item, editing) {
|
||||
gui.forms = {};
|
||||
|
||||
// Returns form fields that will manage a gui description (new or edit)
|
||||
gui.forms.fieldsToHtml = function(itemGui, item, editing) {
|
||||
var html = '';
|
||||
// itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted)
|
||||
$.each(itemGui, function(index, f){
|
||||
@ -30,9 +32,7 @@
|
||||
return html;
|
||||
};
|
||||
|
||||
gui.form = {};
|
||||
|
||||
gui.form.fromFields = function(fields, item) {
|
||||
gui.forms.fromFields = function(fields, item) {
|
||||
var editing = item !== undefined; // Locate real Editing
|
||||
item = item || {id:''};
|
||||
var form = '<form class="form-horizontal" role="form">' +
|
||||
@ -43,20 +43,20 @@
|
||||
var tabsContent = [];
|
||||
var active = ' active in' ;
|
||||
$.each(fields.tabs, function(index, tab){
|
||||
tabsContent.push('<div class="tab-pane fade' + active + '" id="' + id + index + '">' + gui.fieldsToHtml(tab.fields, item) + '</div>' );
|
||||
tabsContent.push('<div class="tab-pane fade' + active + '" id="' + id + index + '">' + gui.forms.fieldsToHtml(tab.fields, item) + '</div>' );
|
||||
tabs.push('<li><a href="#' + id + index + '" data-toggle="tab">' + tab.title + '</a></li>' );
|
||||
active = '';
|
||||
});
|
||||
form += '<ul class="nav nav-tabs">' + tabs.join('\n') + '</ul><div class="tab-content">' + tabsContent.join('\n') + '</div>';
|
||||
} else {
|
||||
form += gui.fieldsToHtml(fields, item, editing);
|
||||
form += gui.forms.fieldsToHtml(fields, item, editing);
|
||||
}
|
||||
form += '</form>';
|
||||
return form;
|
||||
};
|
||||
|
||||
// Reads fields from a form
|
||||
gui.form.read = function(formSelector) {
|
||||
gui.forms.read = function(formSelector) {
|
||||
var res = {};
|
||||
$(formSelector + ' .modal_field_data').each(function(i, field) {
|
||||
var $field = $(field);
|
||||
@ -72,6 +72,82 @@
|
||||
return res;
|
||||
};
|
||||
|
||||
gui.forms.launchModal = function(title, fields, item, onSuccess) {
|
||||
var id = 'modal-' + Math.random().toString().split('.')[1]; // Get a random ID for this modal
|
||||
gui.appendToWorkspace(gui.modal(id, title, gui.forms.fromFields(fields, item)));
|
||||
id = '#' + id; // for jQuery
|
||||
|
||||
// Get form
|
||||
var $form = $(id + ' form');
|
||||
|
||||
// For "beauty" switches, initialize them now
|
||||
$(id + ' .make-switch').bootstrapSwitch();
|
||||
// Activate "cool" selects
|
||||
$(id + ' .selectpicker').selectpicker();
|
||||
// TEST: cooller on mobile devices
|
||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
|
||||
$(id + ' .selectpicker').selectpicker('mobile');
|
||||
}
|
||||
// Activate tooltips
|
||||
$(id + ' [data-toggle="tooltip"]').tooltip({delay: {show: 1000, hide: 100}, placement: 'auto right'});
|
||||
|
||||
// Validation
|
||||
$form.validate({
|
||||
debug: true,
|
||||
errorClass: 'text-danger',
|
||||
validClass: 'has-success',
|
||||
highlight: function(element) {
|
||||
$(element).closest('.form-group').addClass('has-error');
|
||||
},
|
||||
success: function(element) {
|
||||
$(element).closest('.form-group').removeClass('has-error');
|
||||
$(element).remove();
|
||||
},
|
||||
});
|
||||
|
||||
// And catch "accept" (default is "Save" in fact) button click
|
||||
$(id + ' .button-accept').click(function(){
|
||||
if( !$form.valid() )
|
||||
return;
|
||||
if( onSuccess ) {
|
||||
onSuccess(id + ' form', function(){$(id).modal('hide');}); // Delegate close to to onSuccess
|
||||
return;
|
||||
} else {
|
||||
$(id).modal('hide');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Launch modal
|
||||
$(id).modal({keyboard: false})
|
||||
.on('hidden.bs.modal', function () {
|
||||
$(id).remove();
|
||||
});
|
||||
};
|
||||
|
||||
// simple gui generators
|
||||
gui.forms.guiField = function(name, type, label, tooltip, value, values, length, multiline, readonly, required) {
|
||||
length = length || 128;
|
||||
multiline = multiline !== undefined ? multiline : 0;
|
||||
readonly = readonly || false;
|
||||
required = required || false;
|
||||
return {
|
||||
name: name,
|
||||
gui: {
|
||||
defvalue: value,
|
||||
value: value,
|
||||
values: values,
|
||||
label: label,
|
||||
length: length,
|
||||
multiline: multiline,
|
||||
rdonly: readonly, // rdonly applies just to editing
|
||||
required: required,
|
||||
tooltip: tooltip,
|
||||
type: type,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
}(window.gui = window.gui || {}, jQuery));
|
||||
|
||||
|
@ -118,58 +118,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
gui.launchModalForm = function(title, form, onSuccess) {
|
||||
var id = 'modal-' + Math.random().toString().split('.')[1]; // Get a random ID for this modal
|
||||
gui.appendToWorkspace(gui.modal(id, title, form));
|
||||
id = '#' + id; // for jQuery
|
||||
|
||||
// Get form
|
||||
var $form = $(id + ' form');
|
||||
|
||||
// For "beauty" switches, initialize them now
|
||||
$(id + ' .make-switch').bootstrapSwitch();
|
||||
// Activate "cool" selects
|
||||
$(id + ' .selectpicker').selectpicker();
|
||||
// TEST: cooller on mobile devices
|
||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
|
||||
$(id + ' .selectpicker').selectpicker('mobile');
|
||||
}
|
||||
// Activate tooltips
|
||||
$(id + ' [data-toggle="tooltip"]').tooltip({delay: {show: 1000, hide: 100}, placement: 'auto right'});
|
||||
|
||||
// Validation
|
||||
$form.validate({
|
||||
debug: true,
|
||||
errorClass: 'text-danger',
|
||||
validClass: 'has-success',
|
||||
highlight: function(element) {
|
||||
$(element).closest('.form-group').addClass('has-error');
|
||||
},
|
||||
success: function(element) {
|
||||
$(element).closest('.form-group').removeClass('has-error');
|
||||
$(element).remove();
|
||||
},
|
||||
});
|
||||
|
||||
// And catch "accept" (default is "Save" in fact) button click
|
||||
$(id + ' .button-accept').click(function(){
|
||||
if( !$form.valid() )
|
||||
return;
|
||||
if( onSuccess ) {
|
||||
onSuccess(id + ' form', function(){$(id).modal('hide');}); // Delegate close to to onSuccess
|
||||
return;
|
||||
} else {
|
||||
$(id).modal('hide');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Launch modal
|
||||
$(id).modal({keyboard: false})
|
||||
.on('hidden.bs.modal', function () {
|
||||
$(id).remove();
|
||||
});
|
||||
};
|
||||
|
||||
gui.failRequestMessageFnc = function(jqXHR, textStatus, errorThrown) {
|
||||
api.templates.get('request_failed', function(tmpl) {
|
||||
|
18
server/src/uds/templates/uds/admin/tmpl/providers.html
Normal file
18
server/src/uds/templates/uds/admin/tmpl/providers.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% load i18n %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h1>{% trans 'Providers' %}</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a class="lnk-dashboard" href="#"><i class="fa fa-dashboard"></i> Dashboard</a></li>
|
||||
<li>{% trans 'Providers' %}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
{% verbatim %}
|
||||
<div class="row">
|
||||
<div id="{{ providers }}" class="col-xs-12"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="{{ services }}" class="col-xs-12"></div>
|
||||
</div>
|
||||
{% endverbatim %}
|
Loading…
Reference in New Issue
Block a user