From: Pedro, Carlos and Rezende Date: Fri, 24 Oct 2014 18:00:22 +0000 (-0200) Subject: Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into fibre X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=d9c4cf9cd7f3269068e048563b2975d26af2ae70;hp=aad516bfc9396270961493b33a875f7cd2a3f941;p=myslice.git Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into fibre Conflicts: portal/homeview.py portal/sliceresourceview.py portal/templates/slice-resource-view.html --- diff --git a/apache/myslice.wsgi b/apache/myslice.wsgi new file mode 120000 index 00000000..69ee4920 --- /dev/null +++ b/apache/myslice.wsgi @@ -0,0 +1 @@ +unfold.wsgi \ No newline at end of file diff --git a/apache/sp-fibre/default b/apache/sp-fibre/default new file mode 100644 index 00000000..ec902c62 --- /dev/null +++ b/apache/sp-fibre/default @@ -0,0 +1,24 @@ +NameVirtualHost *:80 + + ServerName 200.130.15.182 + ServerSignature Off + + # Redirecionamento para SSL + #RewriteEngine on + #RewriteCond %{HTTPS} !=on + #RewriteRule ^(.*) https://%{SERVER_NAME}/$1 [R,L] + + DocumentRoot /var/www/ + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + + + ErrorLog /var/log/apache2/error.log + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel info + CustomLog /var/log/apache2/sp-access-80.log combined + diff --git a/apache/sp-fibre/myslice-django b/apache/sp-fibre/myslice-django new file mode 100644 index 00000000..bafed8fb --- /dev/null +++ b/apache/sp-fibre/myslice-django @@ -0,0 +1,56 @@ +NameVirtualHost *:443 + + + ServerAdmin support-myslice@myslice.info + ServerName portal.fibre.br + + DocumentRoot /var/www/myslice + WSGIScriptAlias / /var/www/myslice/apache/unfold.wsgi + + + Options Indexes FollowSymLinks MultiViews + AllowOverride All + Order allow,deny + allow from all + + + Alias /static/ /var/www/myslice/static/ + + Order deny,allow + Allow from all + + + #Alias /certificates/ /var/certificates-f4f/ + ErrorLog ${APACHE_LOG_DIR}/error.log + + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined + + # SSL + SSLEngine on + SSLProtocol all -SSLv2 + SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW + SSLCertificateKeyFile /etc/ssl/private/fibre.key + SSLCertificateFile /etc/ssl/certs/fibre.crt + SSLOptions +StdEnvVars +ExportCertData + SSLCACertificatePath /etc/sfa/trusted_roots + SSLVerifyClient optional_no_ca + #SSLVerifyClient require + SSLVerifyDepth 5 + ServerSignature On + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + + diff --git a/apache/sp-fibre/shibboleth-myslice b/apache/sp-fibre/shibboleth-myslice new file mode 100644 index 00000000..fc39e546 --- /dev/null +++ b/apache/sp-fibre/shibboleth-myslice @@ -0,0 +1,61 @@ + + ServerName 200.130.15.182 + ServerSignature Off + + SSLEngine on + #SSLCertificateFile /etc/unfold/trusted_roots + SSLCertificateFile /etc/ssl/certs/fibre.crt + SSLCertificateKeyFile /etc/ssl/private/fibre.key + + #ShibURLScheme https + + DocumentRoot /var/www/ + + Options -Indexes FollowSymLinks -MultiViews + AllowOverride None + Order deny,allow + Allow from all + + +# WSGIScriptAlias / /usr/share/unfold/apache/myslice.wsgi +# +# +# Order deny,allow +# Allow from all +# +# +# Alias /static/ /usr/share/unfold/static/ +# +# Order deny,allow +# Allow from all +# + + +# +# AuthType Shibboleth +# ShibRequestSetting requireSession true +# ShibRequestSetting applicationId default +# Require valid-user +# + + + SetHandler shib + + + SetHandler shib + + + +# +# AuthType shibboleth +# ShibRequireSession On +# require valid-user +# + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + ErrorLog /var/log/apache2/error.log + LogLevel info + CustomLog /var/log/apache2/sp-access-443.log combined + + diff --git a/apache/unfold.conf b/apache/unfold.conf index 357db7ee..fed763e5 100644 --- a/apache/unfold.conf +++ b/apache/unfold.conf @@ -1,19 +1,19 @@ # see also unfold-ssl.conf - WSGIDaemonProcess unfold processes=2 threads=25 - WSGIProcessGroup unfold - CustomLog ${APACHE_LOG_DIR}/unfold-access.log common - ErrorLog ${APACHE_LOG_DIR}/unfold-error.log - WSGIScriptAlias / /usr/share/unfold/apache/unfold.wsgi - - + WSGIDaemonProcess myslice processes=2 threads=25 + WSGIProcessGroup myslice + CustomLog ${APACHE_LOG_DIR}/myslice-access.log common + ErrorLog ${APACHE_LOG_DIR}/myslice-error.log + WSGIScriptAlias / /var/www/myslice/apache/myslice.wsgi + + Order deny,allow Allow from all - Alias /static/ /usr/share/unfold/static/ - + Alias /static/ /var/www/myslice/myslice/static/ + Order deny,allow Allow from all diff --git a/apache/unfold.wsgi b/apache/unfold.wsgi index 4af2e9f9..34ffb364 100644 --- a/apache/unfold.wsgi +++ b/apache/unfold.wsgi @@ -1,12 +1,14 @@ import os import sys -path = '/var/myslice-f4f' +#site.addsitedir('/var/www/myslice') + +path = '/var/www/myslice' if path not in sys.path: - sys.path.append(path) + sys.path = [path] + sys.path +print sys.path os.environ['DJANGO_SETTINGS_MODULE'] = 'myslice.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() - diff --git a/auth/manifoldbackend-130314.py b/auth/manifoldbackend-130314.py new file mode 100644 index 00000000..ee80113e --- /dev/null +++ b/auth/manifoldbackend-130314.py @@ -0,0 +1,228 @@ +import time + +# import ldap for LDAP authentication - Edelberto +import ldap + +from django.contrib.auth.models import User + +from manifoldapi.manifoldapi import ManifoldAPI, ManifoldException, ManifoldResult +from manifold.core.query import Query + +# Name my backend 'ManifoldBackend' +class ManifoldBackend: + + + # Create an authentication method + # This is called by the standard Django login procedure + def authenticate(self, token=None): + + # LDAP local/global var + checkldap = None + + if not token: + return None + + try: + #usernameldap is from LDAP user form. If it is filled - See portal/homeview.py too + usernameldap = token['usernameldap'] + username = token['username'] + password = token['password'] + request = token['request'] + + # if data are not from LDAP form then normal (local) login + if not usernameldap: + auth = {'AuthMethod': 'password', 'Username': username, 'AuthString': password} + api = ManifoldAPI(auth) + sessions_result = api.forward(Query.create('local:session').to_dict()) + print "result" + sessions = sessions_result.ok_value() + print "ok" + if not sessions: + print "GetSession failed", sessions_result.error() + return + print "first", sessions + session = sessions[0] + + # Change to session authentication + api.auth = {'AuthMethod': 'session', 'session': session['session']} + self.api = api + + # Get account details + # the new API would expect Get('local:user') instead + persons_result = api.forward(Query.get('local:user').to_dict()) + persons = persons_result.ok_value() + if not persons: + print "GetPersons failed",persons_result.error() + return + person = persons[0] + print "PERSON=", person + + request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + ################################ + # Edelberto LDAP authentication + # if data are from LDAP form, so + else: + # XXX UGLY + # Needing to create an specific entries at settings.py (or myslice.ini) for these vars + ################################################## + # Edelberto - UFF - esilva@ic.uff.br + # v1 - ldap authentication module + # Note: focus on LDAP FIBRE-BR for DN + # if uses other DN, configuration are needed + ################################################### + #Searching an LDAP Directory + + try: + #uid = "debora@uff.br" + + # Receiving an email address, how can we split and mount it in DN format? + #mail = "debora@uff.br" + mail = usernameldap + login = mail.split('@')[0] + org = mail.split('@')[1] + o = org.split('.')[0] + dc = org.split('.')[1] + ''' + print mail + print login + print org + print o + print dc + ''' + + # DN format to authenticate - IMPORTANT! + #FIBRE-BR format + uid = "uid="+mail+",ou=people,o="+o+",dc="+dc + #uid = "uid=debora@uff.br,ou=people,o=uff,dc=br" + # User password from LDAP form + #userPassword = "fibre" + userPassword = password + + # testing with: + # wrong password for test + # userPassword = "fibre2" + + # Parameters to connect on LDAP + ldap.set_option(ldap.OPT_REFERRALS, 0) + # LDAP Server Address + l = ldap.open("127.0.0.1") + # LDAP version + l.protocol_version = ldap.VERSION3 + + #l.simple_bind(uid, userPassword) + # l.bind_s is necessary to do the authentication with a normal LDAP user + l.bind_s(uid, userPassword, ldap.AUTH_SIMPLE) + #print l.bind_s(uid, userPassword, ldap.AUTH_SIMPLE) + + # DN base - Our root dc (dc=br) + baseDN="dc="+dc + searchScope = ldap.SCOPE_SUBTREE + retrieveAttributes = None + # User only can see its credentials. He search only his attributes + searchFilter = "uid="+mail + + # Getting all attributes + try: + ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes) + result_set = [] + # while exist attributes, save them in a list! + while 1: + # print l.result(ldap_result_id, 0) + result_type, result_data = l.result(ldap_result_id, 0) + if (result_data == []): + #print ("User %s don't allowed to bind in LDAP", uid) + break + else: + ## Appendng to a list + if result_type == ldap.RES_SEARCH_ENTRY: + result_set.append(result_data) + # print result_set + except ldap.LDAPError, e: + print e + + # Matching if the user is really who his say + #checkldap = None + if l.compare_s(uid, 'uid', mail): + # DEBUG + checkldap = True + print "match" + + # Now, based on default Manifold Auth + auth = {'AuthMethod': 'password', 'Username': usernameldap, 'AuthString': password} + api = ManifoldAPI(auth) + sessions_result = api.forward(Query.create('local:session').to_dict()) + print "result" + sessions = sessions_result.ok_value() + print "ok" + if not sessions: + print "GetSession failed", sessions_result.error() + return + print "first", sessions + session = sessions[0] + + # Change to session authentication + api.auth = {'AuthMethod': 'session', 'session': session['session']} + self.api = api + + # Get account details + # the new API would expect Get('local:user') instead + persons_result = api.forward(Query.get('local:user').to_dict()) + persons = persons_result.ok_value() + if not persons: + print "GetPersons failed",persons_result.error() + return + person = persons[0] + print "PERSON=", person + + request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + + else: + print "no match. User doesnt allowed" + checkldap = False + + except ldap.LDAPError, e: + print "E: LDAP Search user", e + # end of LDAP + + # Follow the same of Manifold + except ManifoldException, e: + print "ManifoldBackend.authenticate caught ManifoldException, returning corresponding ManifoldResult" + return e.manifold_result + except Exception, e: + print "E: manifoldbackend", e + import traceback + traceback.print_exc() + return None + + if not usernameldap: + try: + # Check if the user exists in Django's local database + user = User.objects.get(username=username) + except User.DoesNotExist: + # Create a user in Django's local database + user = User.objects.create_user(username, usernamep, 'passworddoesntmatter') + user.first_name = "DUMMY_FIRST_NAME" #person['first_name'] + user.last_name = "DUMMY LAST NAME" # person['last_name'] + user.email = person['email'] + return user + else: + if checkldap: + try: + # Check if the user exists in Django's local database + user = User.objects.get(username=usernameldap) + except User.DoesNotExist: + # Create a user in Django's local database + user = User.objects.create_user(username, usernameldap, 'passworddoesntmatter') + user.first_name = "DUMMY_FIRST_NAME" #person['first_name'] + user.last_name = "DUMMY LAST NAME" # person['last_name'] + user.email = person['email'] + return user + + # Required for your backend to work properly - unchanged in most scenarios + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None + + diff --git a/auth/manifoldbackend.py b/auth/manifoldbackend.py index afb87821..0522653b 100644 --- a/auth/manifoldbackend.py +++ b/auth/manifoldbackend.py @@ -1,5 +1,8 @@ import time +# import ldap for LDAP authentication - Edelberto +import ldap + from django.contrib.auth.models import User from manifoldapi.manifoldapi import ManifoldAPI, ManifoldException, ManifoldResult @@ -8,44 +11,188 @@ from manifold.core.query import Query # Name my backend 'ManifoldBackend' class ManifoldBackend: + # Create an authentication method # This is called by the standard Django login procedure def authenticate(self, token=None): + + # LDAP local/global var + checkldap = None + if not token: return None try: + print "ManifoldBackend authenticate()" + # Mandatory fields in token username = token['username'] - password = token['password'] request = token['request'] - auth = {'AuthMethod': 'password', 'Username': username, 'AuthString': password} - api = ManifoldAPI(auth) - sessions_result = api.forward(Query.create('local:session').to_dict()) - print "result" - sessions = sessions_result.ok_value() - print "ok" - if not sessions: - print "GetSession failed", sessions_result.error() - return - print "first", sessions - session = sessions[0] - - # Change to session authentication - api.auth = {'AuthMethod': 'session', 'session': session['session']} - self.api = api - - # Get account details - # the new API would expect Get('local:user') instead - persons_result = api.forward(Query.get('local:user').to_dict()) - persons = persons_result.ok_value() - if not persons: - print "GetPersons failed",persons_result.error() - return - person = persons[0] - print "PERSON=", person - - request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + # usernameldap is optional - from LDAP user form. + # If it is filled - See portal/homeview.py too + if 'usernameldap' in token: + usernameldap = token['usernameldap'] + else: + usernameldap = None + password = token['password'] + # if data are not from LDAP form then normal (local) login + if not usernameldap: + print "not userldap ManifoldBackend authenticate()" + auth = {'AuthMethod': 'password', 'Username': username, 'AuthString': password} + api = ManifoldAPI(auth) + sessions_result = api.forward(Query.create('local:session').to_dict()) + print "result" + sessions = sessions_result.ok_value() + print "ok" + if not sessions: + print "GetSession failed", sessions_result.error() + return + print "first", sessions + session = sessions[0] + + # Change to session authentication + api.auth = {'AuthMethod': 'session', 'session': session['session']} + self.api = api + + # Get account details + # the new API would expect Get('local:user') instead + persons_result = api.forward(Query.get('local:user').to_dict()) + persons = persons_result.ok_value() + if not persons: + print "GetPersons failed",persons_result.error() + return + person = persons[0] + print "PERSON=", person + + request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + ################################ + # Edelberto LDAP authentication + # if data are from LDAP form, so + else: + print "userldap ManifoldBackend authenticate()" + # XXX UGLY + # Needing to create an specific entries at settings.py (or myslice.ini) for these vars + ################################################## + # Edelberto - UFF - esilva@ic.uff.br + # v1 - ldap authentication module + # Note: focus on LDAP FIBRE-BR for DN + # if uses other DN, configuration are needed + ################################################### + #Searching an LDAP Directory + + try: + #uid = "debora@uff.br" + + # Receiving an email address, how can we split and mount it in DN format? + #mail = "debora@uff.br" + mail = usernameldap + login = mail.split('@')[0] + org = mail.split('@')[1] + o = org.split('.')[0] + dc = org.split('.')[1] + ''' + print mail + print login + print org + print o + print dc + ''' + + # DN format to authenticate - IMPORTANT! + #FIBRE-BR format + uid = "uid="+mail+",ou=people,o="+o+",dc="+dc + #uid = "uid=debora@uff.br,ou=people,o=uff,dc=br" + # User password from LDAP form + #userPassword = "fibre" + userPassword = password + + # testing with: + # wrong password for test + # userPassword = "fibre2" + + # Parameters to connect on LDAP + ldap.set_option(ldap.OPT_REFERRALS, 0) + # LDAP Server Address + l = ldap.open("127.0.0.1") + # LDAP version + l.protocol_version = ldap.VERSION3 + + #l.simple_bind(uid, userPassword) + # l.bind_s is necessary to do the authentication with a normal LDAP user + l.bind_s(uid, userPassword, ldap.AUTH_SIMPLE) + #print l.bind_s(uid, userPassword, ldap.AUTH_SIMPLE) + + # DN base - Our root dc (dc=br) + baseDN="dc="+dc + searchScope = ldap.SCOPE_SUBTREE + retrieveAttributes = None + # User only can see its credentials. He search only his attributes + searchFilter = "uid="+mail + + # Getting all attributes + try: + ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes) + result_set = [] + # while exist attributes, save them in a list! + while 1: + # print l.result(ldap_result_id, 0) + result_type, result_data = l.result(ldap_result_id, 0) + if (result_data == []): + #print ("User %s don't allowed to bind in LDAP", uid) + break + else: + ## Appendng to a list + if result_type == ldap.RES_SEARCH_ENTRY: + result_set.append(result_data) + # print result_set + except ldap.LDAPError, e: + print e + + # Matching if the user is really who his say + #checkldap = None + if l.compare_s(uid, 'uid', mail): + # DEBUG + checkldap = True + print "match" + + # Now, based on default Manifold Auth + auth = {'AuthMethod': 'password', 'Username': usernameldap, 'AuthString': password} + api = ManifoldAPI(auth) + sessions_result = api.forward(Query.create('local:session').to_dict()) + print "result" + sessions = sessions_result.ok_value() + print "ok" + if not sessions: + print "GetSession failed", sessions_result.error() + return + print "first", sessions + session = sessions[0] + + # Change to session authentication + api.auth = {'AuthMethod': 'session', 'session': session['session']} + self.api = api + + # Get account details + # the new API would expect Get('local:user') instead + persons_result = api.forward(Query.get('local:user').to_dict()) + persons = persons_result.ok_value() + if not persons: + print "GetPersons failed",persons_result.error() + return + person = persons[0] + print "PERSON=", person + + request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + + else: + print "no match. User doesnt allowed" + checkldap = False + + except ldap.LDAPError, e: + print "E: LDAP Search user", e + # end of LDAP + + # Follow the same of Manifold except ManifoldException, e: print "ManifoldBackend.authenticate caught ManifoldException, returning corresponding ManifoldResult" return e.manifold_result @@ -54,14 +201,24 @@ class ManifoldBackend: import traceback traceback.print_exc() return None - - try: - # Check if the user exists in Django's local database - user = User.objects.get(username=username) - except User.DoesNotExist: - # Create a user in Django's local database - user = User.objects.create_user(username, username, 'passworddoesntmatter') - user.email = person['email'] + + if not usernameldap: + try: + # Check if the user exists in Django's local database + user = User.objects.get(username=username) + except User.DoesNotExist: + # Create a user in Django's local database + user = User.objects.create_user(username, usernamep, 'passworddoesntmatter') + user.email = person['email'] + else: + if checkldap: + try: + # Check if the user exists in Django's local database + user = User.objects.get(username=usernameldap) + except User.DoesNotExist: + # Create a user in Django's local database + user = User.objects.create_user(username, usernameldap, 'passworddoesntmatter') + user.email = person['email'] if 'firstname' in person: user.first_name = person['firstname'] @@ -69,7 +226,6 @@ class ManifoldBackend: user.last_name = person['lastname'] return user - # Required for your backend to work properly - unchanged in most scenarios def get_user(self, user_id): try: diff --git a/auth/manifoldbackend.py.original b/auth/manifoldbackend.py.original new file mode 100644 index 00000000..eb87ab87 --- /dev/null +++ b/auth/manifoldbackend.py.original @@ -0,0 +1,76 @@ +import time + +from django.contrib.auth.models import User + +from manifoldapi.manifoldapi import ManifoldAPI, ManifoldException, ManifoldResult +from manifold.core.query import Query + +# Name my backend 'ManifoldBackend' +class ManifoldBackend: + + # Create an authentication method + # This is called by the standard Django login procedure + def authenticate(self, token=None): + if not token: + return None + + try: + username = token['username'] + password = token['password'] + request = token['request'] + + auth = {'AuthMethod': 'password', 'Username': username, 'AuthString': password} + api = ManifoldAPI(auth) + sessions_result = api.forward(Query.create('local:session').to_dict()) + print "result" + sessions = sessions_result.ok_value() + print "ok" + if not sessions: + print "GetSession failed", sessions_result.error() + return + print "first", sessions + session = sessions[0] + + # Change to session authentication + api.auth = {'AuthMethod': 'session', 'session': session['session']} + self.api = api + + # Get account details + # the new API would expect Get('local:user') instead + persons_result = api.forward(Query.get('local:user').to_dict()) + persons = persons_result.ok_value() + if not persons: + print "GetPersons failed",persons_result.error() + return + person = persons[0] + print "PERSON=", person + + request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': session['expires']} + except ManifoldException, e: + print "ManifoldBackend.authenticate caught ManifoldException, returning corresponding ManifoldResult" + return e.manifold_result + except Exception, e: + print "E: manifoldbackend", e + import traceback + traceback.print_exc() + return None + + try: + # Check if the user exists in Django's local database + user = User.objects.get(username=username) + except User.DoesNotExist: + # Create a user in Django's local database + user = User.objects.create_user(username, username, 'passworddoesntmatter') + user.first_name = "DUMMY_FIRST_NAME" #person['first_name'] + user.last_name = "DUMMY LAST NAME" # person['last_name'] + user.email = person['email'] + return user + + # Required for your backend to work properly - unchanged in most scenarios + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None + + diff --git a/manifoldapi/static/js/manifold.js b/manifoldapi/static/js/manifold.js index ca111de7..01b48f51 100644 --- a/manifoldapi/static/js/manifold.js +++ b/manifoldapi/static/js/manifold.js @@ -33,13 +33,13 @@ Array.prototype.equals = function (array) { if (this[i] instanceof Array && array[i] instanceof Array) { // recurse into the nested arrays if (!this[i].equals(array[i])) - return false; - } - else if (this[i] != array[i]) { + return false; + } + else if (this[i] != array[i]) { // Warning - two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } + return false; + } + } return true; } @@ -360,6 +360,7 @@ function QueryStore() { //var key = manifold.metadata.get_key(query_ext.query.object); var record_key_value = manifold.record_get_value(record, record_key); + query_ext.records.put(record_key_value, record); if (!(query_ext.state.get(record_key_value))) @@ -1115,6 +1116,9 @@ var manifold = { * otherwise, publish the main object as well as subqueries * XXX how much recursive are we ? */ + if (records == null){ + records = Array(); + } if (manifold.pubsub_debug) messages.debug (">>>>> publish_result_rec " + query.object); if (manifold.query_expects_unique_result(query)) { diff --git a/myslice/configengine.py b/myslice/configengine.py index 5ce52605..1a46e6d3 100644 --- a/myslice/configengine.py +++ b/myslice/configengine.py @@ -33,7 +33,7 @@ class ConfigEngine(object): default_manifold_admin_user = 'admin' default_manifold_admin_password = 'demo' - default_myslice_theme = 'onelab' + default_myslice_theme = 'fibre' def __init__ (self): @@ -48,7 +48,9 @@ class ConfigEngine(object): parser.add_section('googlemap') parser.set ('googlemap','api_key', None) + print os.path.join(ROOT,'myslice/myslice.ini') parser.read (os.path.join(ROOT,'myslice/myslice.ini')) + print parser self.config_parser=parser def __getattr__(self, section): diff --git a/myslice/settings.py b/myslice/settings.py index 235a18ca..89565ddb 100644 --- a/myslice/settings.py +++ b/myslice/settings.py @@ -42,9 +42,9 @@ except: # when deployed from a package # this code is run by collectstatic too, so we cannot # assume we have ./static present already -HTTPROOT="/var/myslice-f4f" +HTTPROOT="/var/www/myslice" # the place to store local data, like e.g. the sqlite db -DATAROOT="/var/unfold" +DATAROOT="/var/www/myslice" # if not there, then we assume it's from a devel tree if not os.path.isdir (os.path.join(HTTPROOT,"static")): HTTPROOT=ROOT @@ -199,6 +199,7 @@ ROOT_URLCONF = 'myslice.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'unfold.wsgi.application' +#WSGI_APPLICATION = 'myslice.wsgi.application' TEMPLATE_DIRS = [ ] # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". @@ -290,8 +291,10 @@ CSRF_FAILURE_VIEW = 'manifoldapi.manifoldproxy.csrf_failure' # put stuff under static/ # IA_MEDIA_PREFIX = '/code/' +SESSION_ENGINE = 'django.contrib.sessions.backends.file' + ####SLA##### SLA_MANAGER_URL = "http://157.193.215.125:4000/sla-service" SLA_MANAGER_USER = "normal_user" -SLA_MANAGER_PASSWORD = "password" \ No newline at end of file +SLA_MANAGER_PASSWORD = "password" diff --git a/myslice/urls.py b/myslice/urls.py index 91511b7d..7b8dea1e 100644 --- a/myslice/urls.py +++ b/myslice/urls.py @@ -17,6 +17,8 @@ import portal.dashboardview import portal.homeview import portal.newsview +import plugins.cafe.edelberto + from portal.about import AboutView from portal.registrationview import RegistrationView from portal.accountview import AccountView, account_process @@ -134,3 +136,10 @@ for aux in auxiliaries: urls.append ( url ( r'^%s/'%aux, include ('%s.urls'%aux ))) urlpatterns = patterns(*urls) + +# Shibboleth - Edelberto +urlpatterns += patterns('', + url(r'^cafe/', plugins.cafe.edelberto.EdelbertoView.as_view()), + #url(r'^cafe/', 'plugins.cafe.edelberto.index'), +) + diff --git a/old.unfold.sqlite3-110314 b/old.unfold.sqlite3-110314 new file mode 100644 index 00000000..7c680dec Binary files /dev/null and b/old.unfold.sqlite3-110314 differ diff --git a/plugins/scheduler2/templates/scheduler.html b/plugins/scheduler2/templates/scheduler.html index 259723b1..e554f2b1 100755 --- a/plugins/scheduler2/templates/scheduler.html +++ b/plugins/scheduler2/templates/scheduler.html @@ -137,3 +137,4 @@ //alert("1"); + diff --git a/portal/actions-100314.py b/portal/actions-100314.py new file mode 100644 index 00000000..62f95bc4 --- /dev/null +++ b/portal/actions-100314.py @@ -0,0 +1,602 @@ +from django.http import HttpResponse +from manifold.core.query import Query +from manifoldapi.manifoldapi import execute_query,execute_admin_query +from portal.models import PendingUser, PendingSlice, PendingAuthority +import json + +from django.contrib.auth.models import User +from django.template.loader import render_to_string +from django.core.mail import EmailMultiAlternatives + +from theme import ThemeView + +theme = ThemeView() + +# Thierry: moving this right into the code so +# most people can use myslice without having to install sfa +# XXX tmp sfa dependency, should be moved to SFA gateway +#from sfa.util.xrn import Xrn + + +# Get the list of authorities + +def authority_get_pis(request, authority_hrn): + query = Query.get('authority').filter_by('authority_hrn', '==', authority_hrn).select('pi_users') + results = execute_admin_query(request, query) + # NOTE: temporarily commented. Because results is giving empty list. + # Needs more debugging + #if not results: + # raise Exception, "Authority not found: %s" % authority_hrn + #result, = results + #return result['pi_users'] + return results + +def authority_get_pi_emails(request, authority_hrn): + pi_users = authority_get_pis(request,authority_hrn) + if any(d['pi_users'] == None for d in pi_users): + theme.template_name = 'email_default_recipients.txt' + default_email = render_to_string(theme.template, request) + default_email = default_email.replace('\n', '') + return default_email + else: + pi_user_hrns = [ hrn for x in pi_users for hrn in x['pi_users'] ] + query = Query.get('user').filter_by('user_hrn', 'included', pi_user_hrns).select('user_email') + results = execute_admin_query(request, query) + return [result['user_email'] for result in results] + +def is_pi(wsgi_request, user_hrn, authority_hrn): + # XXX could be done in a single query ! + + # select pi_authorities from user where user_hrn == "ple.upmc.jordan_auge" + query = Query.get('user').filter_by('user_hrn', '==', user_hrn).select('pi_authorities') + results = execute_admin_query(wsgi_request, query) + if not results: + # XXX Warning ? + return False + result = results[0] + user_authority_hrns = result.get('pi_authorities', []) + return authority_hrn in user_authority_hrns + +# SFA get record + +def sfa_get_user(request, user_hrn, pub): + query_sfa_user = Query.get('user').filter_by('user_hrn', '==', user_hrn) + result_sfa_user = execute_query(request, query_sfa_user) + return result_sfa_user + +def sfa_update_user(request, user_hrn, user_params): + # user_params: keys [public_key] + if 'email' in user_params: + user_params['user_email'] = user_params['email'] + query = Query.update('user').filter_by('user_hrn', '==', user_hrn).set(user_params).select('user_hrn') + results = execute_query(request,query) + return results + +def sfa_add_authority(request, authority_params): + query = Query.create('authority').set(authority_params).select('authority_hrn') + results = execute_query(request, query) + print "sfa_add_auth results=",results + if not results: + raise Exception, "Could not create %s. Already exists ?" % authority_params['hrn'] + return results + +def sfa_add_user_to_slice(request, user_hrn, slice_params): +# UPDATE myslice:slice SET researcher=['ple.upmc.jordan_auge','ple.inria.thierry_parmentelat','ple.upmc.loic_baron','ple.upmc.ciro_scognamiglio','ple.upmc.mohammed-yasin_rahman','ple.upmc.azerty'] where slice_hrn=='ple.upmc.myslicedemo' + query_current_users = Query.get('slice').select('user').filter_by('slice_hrn','==',slice_params['hrn']) + results_current_users = execute_query(request, query_current_users) + slice_params['researcher'] = slice_params['researcher'] | results_current_users + query = Query.update('slice').filter_by('user_hrn', '==', user_hrn).set(slice_params).select('slice_hrn') + results = execute_query(request, query) +# Also possible but not supported yet +# UPDATE myslice:user SET slice=['ple.upmc.agent','ple.upmc.myslicedemo','ple.upmc.tophat'] where user_hrn=='ple.upmc.azerty' + if not results: + raise Exception, "Could not create %s. Already exists ?" % slice_params['hrn'] + return results + +# Propose hrn + +def manifold_add_user(wsgi_request, request): + """Add a Manifold user corresponding to a user request. + + Args: + wsgi_request: a WSGIRequest instance + request (dict): a dictionary containing the user request built from the + form. + + Returns: + The user_id of the inserted user. + + Raises: + ? + + """ + USER_CONFIG = '{"firstname": "%(first_name)s", "lastname": "%(last_name)s", "authority": "%(authority_hrn)s"}' + + user_params = { + 'email' : request['email'], + 'password' : request['password'], + 'config' : USER_CONFIG % request, + 'status' : 1, + } + + query = Query.create('local:user').set(user_params).select('email') + results = execute_admin_query(request, query) + if not results: + raise Exception, "Failed creating manifold user: %s" % user_params['email'] + result = results[0] + return result['email'] + +def manifold_update_user(request, email, user_params): + # user_params: password, config e.g., + query = Query.update('local:user').filter_by('email', '==', email).set(user_params).select('email') + results = execute_admin_query(request,query) + # NOTE: results remains empty and goes to Exception. However, it updates the manifold DB. + # That's why I commented the exception part. -- Yasin + #if not results: + # raise Exception, "Failed updating manifold user: %s" % user_params['email'] + #result, = results + return results + +def manifold_add_account(request, account_params): + query = Query.create('local:account').set(account_params).select(['user', 'platform']) + results = execute_admin_query(request,query) + if not results: + raise Exception, "Failed creating manifold account on platform %s for user: %s" % (account_params['platform'], account_params['user']) + result, = results + return result['user_id'] + +def manifold_update_account(request,user_id,account_params): + # account_params: config + query = Query.update('local:account').filter_by('platform', '==', 'myslice').filter_by('user_id', '==', user_id).set(account_params).select('user_id') + results = execute_admin_query(request,query) + return results + +#explicitly mention the platform_id +def manifold_delete_account(request, platform_id, user_id, account_params): + query = Query.delete('local:account').filter_by('platform_id', '==', platform_id).filter_by('user_id', '==', user_id).set(account_params).select('user_id') + results = execute_admin_query(request,query) + return results + + +#not tested +def manifold_add_platform(request, platform_params): + query = Query.create('local:platform').set(platform_params).select(['user', 'platform']) + results = execute_admin_query(request,query) + if not results: + raise Exception, "Failed creating manifold platform %s for user: %s" % (platform_params['platform'], platform_params['user']) + result, = results + return result['platform_id'] + + +def make_request_user(user): + request = {} + request['type'] = 'user' + request['id'] = user.id + request['timestamp'] = user.created # XXX in DB ? + request['authority_hrn'] = user.authority_hrn + request['first_name'] = user.first_name + request['last_name'] = user.last_name + request['email'] = user.email + request['login'] = user.login + request['user_hrn'] = user.user_hrn + request['public_key'] = user.public_key + request['private_key'] = user.private_key + return request + +def make_request_slice(slice): + request = {} + request['type'] = 'slice' + request['id'] = slice.id + request['user_hrn'] = slice.user_hrn + request['timestamp'] = slice.created + request['authority_hrn'] = slice.authority_hrn + request['slice_name'] = slice.slice_name + request['number_of_nodes'] = slice.number_of_nodes + request['type_of_nodes'] = slice.type_of_nodes + request['purpose'] = slice.purpose + return request + +def make_request_authority(authority): + request = {} + request['type'] = 'authority' + request['id'] = authority.id + request['site_name'] = authority.site_name + request['site_latitude'] = authority.site_latitude + request['site_longitude'] = authority.site_longitude + request['site_url'] = authority.site_url + request['site_authority'] = authority.site_authority + request['site_abbreviated_name'] = authority.site_abbreviated_name + request['address_line1'] = authority.address_line1 + request['address_line2'] = authority.address_line2 + request['address_line3'] = authority.address_line3 + request['address_city'] = authority.address_city + request['address_postalcode'] = authority.address_postalcode + request['address_state'] = authority.address_state + request['address_country'] = authority.address_country + request['authority_hrn'] = authority.authority_hrn + request['timestamp'] = authority.created + return request + +def make_requests(pending_users, pending_slices, pending_authorities): + requests = [] + for user in pending_users: + requests.append(make_request_user(user)) + for slice in pending_slices: + requests.append(make_request_slice(slice)) + for authority in pending_authorities: + requests.append(make_request_authority(authority)) + return requests + +def get_request_by_id(ids): + sorted_ids = { 'user': [], 'slice': [], 'authority': [] } + for type__id in ids: + type, id = type__id.split('__') + sorted_ids[type].append(id) + + if not ids: + pending_users = PendingUser.objects.all() + pending_slices = PendingSlice.objects.all() + pending_authorities = PendingAuthority.objects.all() + else: + pending_users = PendingUser.objects.filter(id__in=sorted_ids['user']).all() + pending_slices = PendingSlice.objects.filter(id__in=sorted_ids['slice']).all() + pending_authorities = PendingAuthority.objects.filter(id__in=sorted_ids['authority']).all() + + return make_requests(pending_users, pending_slices, pending_authorities) + +def get_requests(authority_hrns=None): + print "get_request_by_authority auth_hrns = ", authority_hrns + if not authority_hrns: + pending_users = PendingUser.objects.all() + pending_slices = PendingSlice.objects.all() + pending_authorities = PendingAuthority.objects.all() + else: + pending_users = PendingUser.objects.filter(authority_hrn__in=authority_hrns).all() + pending_slices = PendingSlice.objects.filter(authority_hrn__in=authority_hrns).all() + pending_authorities = PendingAuthority.objects.filter(authority_hrn__in=authority_hrns).all() + + return make_requests(pending_users, pending_slices, pending_authorities) + +# XXX Is it in sync with the form fields ? + +def portal_validate_request(wsgi_request, request_ids): + status = {} + + if not isinstance(request_ids, list): + request_ids = [request_ids] + + requests = get_request_by_id(request_ids) + for request in requests: + # type, id, timestamp, details, allowed -- MISSING: authority_hrn + # CAREFUL about details + # user : first name, last name, email, password, keypair + # slice : number of nodes, type of nodes, purpose + + request_status = {} + + if request['type'] == 'user': + + try: + create_user(wsgi_request, request) + request_status['SFA user'] = {'status': True } + + except Exception, e: + request_status['SFA user'] = {'status': False, 'description': str(e)} + +# user_params = {'status':2} +# manifold_update_user(request, request['email'], user_params) + + # MANIFOLD user should be added beforehand, during registration + #try: + # manifold_user_params = { key: request[key] for key in MANIFOLD_USER_KEYS } + # # XXX # manifold_add_user(manifold_user_params) + # request_status['MySlice user'] = {'status': True } + #except Exception, e: + # request_status['MySlice user'] = {'status': False, 'description': str(e)} + + # XXX + #manifold_account_params = { key: request[key] for key in MANIFOLD_ACCOUNT_KEYS } + #manifold_add_account(manifold_account_params) + #request_status['MySlice testbed accounts'] = {'status': False } + + elif request['type'] == 'slice': + try: + create_slice(wsgi_request, request) + request_status['SFA slice'] = {'status': True } + + except Exception, e: + request_status['SFA slice'] = {'status': False, 'description': str(e)} + + elif request['type'] == 'authority': + try: + #hrn = "%s.%s" % (request['authority_hrn'], request['site_authority']) + hrn = request['site_authority'] + # XXX tmp sfa dependency + from sfa.util.xrn import Xrn + urn = Xrn(hrn, request['type']).get_urn() + + sfa_authority_params = { + 'hrn' : hrn, + 'urn' : urn, + 'type' : request['type'], + #'pi' : None, + 'enabled' : True + } + print "ADD Authority" + sfa_add_authority(wsgi_request, sfa_authority_params) + request_status['SFA authority'] = {'status': True } + + except Exception, e: + request_status['SFA authority'] = {'status': False, 'description': str(e)} + + # XXX Remove from Pendings in database + + status['%s__%s' % (request['type'], request['id'])] = request_status + + return status + + +def validate_action(request, **kwargs): + ids = filter(None, kwargs['id'].split('/')) + status = portal_validate_request(request, ids) + json_answer = json.dumps(status) + return HttpResponse (json_answer, mimetype="application/json") + +# Django and ajax +# http://djangosnippets.org/snippets/942/ + + + +#------------------------------------------------------------------------------- +# REQUESTS - Slices +#------------------------------------------------------------------------------- + +def create_slice(wsgi_request, request): + """ + Arguments: + wsgi_request (~ WSGIRequest) : + request (dict) : the slice request in our own dict format + + Raises: + Exception + """ + hrn = "%s.%s" % (request['authority_hrn'], request['slice_name']) + # XXX tmp sfa dependency + from sfa.util.xrn import Xrn + urn = Xrn(hrn, request['type']).get_urn() + + # Add User to Slice if we have the user_hrn in pendingslice table + user_hrn = request.get('user_hrn', None) + user_hrns = list([user_hrn]) if user_hrn else list() + + # XXX We should create a slice with Manifold terminology + slice_params = { + 'slice_hrn' : hrn, + 'slice_urn' : urn, + 'slice_type' : request['type'], + 'users' : user_hrns, + 'slice_enabled' : True + } + # ignored in request: id, timestamp, number_of_nodes, type_of_nodes, purpose + + query = Query.create('slice').set(slice_params).select('slice_hrn') + results = execute_query(wsgi_request, query) + if not results: + raise Exception, "Could not create %s. Already exists ?" % slice_params['hrn'] + return results + +def create_pending_slice(wsgi_request, request, email): + """ + """ + + # Insert an entry in the PendingSlice table + s = PendingSlice( + slice_name = request['slice_name'], + user_hrn = request['user_hrn'], + authority_hrn = request['authority_hrn'], + number_of_nodes = request['number_of_nodes'], + purpose = request['purpose'], + ) + s.save() + + try: + # Send an email: the recipients are the PI of the authority + recipients = authority_get_pi_emails(wsgi_request, request['authority_hrn']) + + theme.template_name = 'slice_request_email.txt' + text_content = render_to_string(theme.template, request) + + theme.template_name = 'slice_request_email.html' + html_content = render_to_string(theme.template, request) + + theme.template_name = 'slice_request_email_subject.txt' + subject = render_to_string(theme.template, request) + subject = subject.replace('\n', '') + + sender = email + msg = EmailMultiAlternatives(subject, text_content, sender, [recipients]) + print msg + msg.attach_alternative(html_content, "text/html") + msg.send() + except Exception, e: + print "Failed to send email, please check the mail templates and the SMTP configuration of your server" + +#------------------------------------------------------------------------------- +# REQUESTS - Users +#------------------------------------------------------------------------------- + +def manifold_add_reference_user_accounts(wsgi_request, request): + """When a new user is created, add reference accounts to the reference platform. + """ + # XXX XXX XXX The rest of this function has to be checked XXX XXX XXX + + # Retrieve user information + user_query = Query().get('local:user') \ + .select('user_id', 'config', 'email', 'status') \ + .filter_by('email', '==', request['email']) + user_details = execute_admin_query(wsgi_request, user_query) + + # USER MAIN ACCOUNT != reference + #print 'USER MAIN ACCOUNT != reference' + list_accounts_query = Query().get('local:account') \ + .select('user_id', 'platform_id', 'auth_type', 'config') \ + .filter_by('user_id', '==', user_details[0]['user_id']) \ + .filter_by('auth_type', '!=', 'reference') + list_accounts = execute_admin_query(wsgi_request, list_accounts_query) + + # XXX main_platform is being erased several times ??? + for account in list_accounts: + main_platform_query = Query().get('local:platform') \ + .select('platform_id', 'platform') \ + .filter_by('platform_id', '==', account['platform_id']) + main_platform = execute_admin_query(wsgi_request, main_platform_query) + + # Add reference accounts on SFA enabled platforms + platforms_query = Query().get('local:platform') \ + .filter_by('disabled', '==', '0') \ + .filter_by('gateway_type', '==', 'sfa') \ + .select('platform_id', 'gateway_type') + platforms = execute_admin_query(wsgi_request, platforms_query) + for platform in platforms: + #print "add reference to platform ",platform + manifold_account_params = { + 'user_id' : user_details[0]['user_id'], + 'platform_id' : platform['platform_id'], + 'auth_type' : 'reference', + 'config' : '{"reference_platform": "' + main_platform[0]['platform'] + '"}', + } + manifold_add_account(wsgi_request, manifold_account_params) + +def sfa_create_user(wsgi_request, request): + """ + Arguments: + wsgi_request (~ WSGIRequest) : + request (dict) : the user request in our own dict format + + Raises: + Exception + """ + from sfa.util.xrn import Xrn + + auth_pi = request.get('pi', None) + auth_pi = list([auth_pi]) if auth_pi else list() + + # We create a user request with Manifold terminology + sfa_user_params = { + 'user_hrn' : request['user_hrn'], + 'user_email' : request['email'], + 'user_urn' : Xrn(request['user_hrn'], request['type']).get_urn(), + 'user_type' : request['type'], + 'keys' : request['public_key'], + 'user_first_name' : request['first_name'], + 'user_last_name' : request['last_name'], + 'pi_authorities' : auth_pi, + 'user_enabled' : True + } + + query = Query.create('user').set(sfa_user_params).select('user_hrn') + results = execute_query(wsgi_request, query) + if not results: + raise Exception, "Could not create %s. Already exists ?" % sfa_user_params['user_hrn'] + return results + +#def ldap_create_user + +def create_user(wsgi_request, request): + + # XXX This has to be stored centrally + USER_STATUS_ENABLED = 2 + + # NOTE : if we were to create a user directly (just like we create slices, + # we would have to perform the steps in create_pending_user too + + # Add the user to the SFA registry + sfa_create_user(wsgi_request, request) + + # Update Manifold user status + manifold_update_user(wsgi_request, request['email'], {'status': USER_STATUS_ENABLED}) + + # Add reference accounts for platforms + manifold_add_reference_user_accounts(wsgi_request, request) + +def create_pending_user(wsgi_request, request, user_detail): + """ + """ + + # Insert an entry in the PendingUser table + b = PendingUser( + first_name = request['first_name'], + last_name = request['last_name'], + authority_hrn = request['authority_hrn'], + email = request['email'], + password = request['password'], + public_key = request['public_key'], + private_key = request['private_key'], + user_hrn = request['user_hrn'], + pi = '', # XXX Why not None ? + ) + b.save() + + # saves the user to django auth_user table [needed for password reset] + user = User.objects.create_user(request['email'], request['email'], request['password']) + + # Creating a manifold user + user_id = manifold_add_user(wsgi_request, request) + + # Creating a Manifold account on the MySlice platform + # Note the JSON representation of public and private keys already includes quotes + account_config = { + 'user_hrn' : request['user_hrn'], + 'user_public_key' : request['public_key'], + } + if request['private_key']: + account_config['user_private_key'] = request['private_key'] + + user_id = user_detail['user_id'] + 1 # the user_id for the newly created user in local:user + + # XXX TODO: Require a myslice platform + # ALERT: this will disapear with ROUTERV2 of Manifold + # We have to consider the case where several registries can be used + # Removed hardcoded platform = 5 + # This platform == 'myslice' is a TMP FIX !! + try: + reg_platform_query = Query().get('local:platform') \ + .filter_by('platform', '==', 'myslice') \ + .select('platform_id') + reg_platform = execute_admin_query(wsgi_request, reg_platform_query) + + registry_platform_id = reg_platform[0]['platform_id'] + account_params = { + 'platform_id' : reg_platform_id, # XXX ALERT !! + 'user_id' : user_id, + 'auth_type' : request['auth_type'], + 'config' : json.dumps(account_config), + } + manifold_add_account(wsgi_request, account_params) + except Exception, e: + print "Failed creating manifold account on platform %s for user: %s" % ('myslice', request['email']) + + try: + # Send an email: the recipients are the PI of the authority + # If No PI is defined for this Authority, send to a default email (different for each theme) + recipients = authority_get_pi_emails(wsgi_request, request['authority_hrn']) + + theme.template_name = 'user_request_email.html' + html_content = render_to_string(theme.template, request) + + theme.template_name = 'user_request_email.txt' + text_content = render_to_string(theme.template, request) + + theme.template_name = 'user_request_email_subject.txt' + subject = render_to_string(theme.template, request) + subject = subject.replace('\n', '') + + theme.template_name = 'email_default_sender.txt' + sender = render_to_string(theme.template, request) + sender = sender.replace('\n', '') + + msg = EmailMultiAlternatives(subject, text_content, sender, [recipients]) + msg.attach_alternative(html_content, "text/html") + msg.send() + except Exception, e: + print "Failed to send email, please check the mail templates and the SMTP configuration of your server" diff --git a/portal/actions.py b/portal/actions.py index 400f3a73..a76e3a79 100644 --- a/portal/actions.py +++ b/portal/actions.py @@ -12,6 +12,9 @@ from django.core.mail import EmailMultiAlternatives, send_mail from myslice.theme import ThemeView +# LS Client - By Bruno Soares (UFG) +from lsapiclient import LaboraSchedulerClient + theme = ThemeView() import activity.slice @@ -58,7 +61,7 @@ def clear_user_creds(request, user_email): try: user_query = Query().get('local:user').filter_by('email', '==', user_email).select('user_id','email','password','config') user_details = execute_admin_query(request, user_query) - + # getting the user_id from the session for user_detail in user_details: user_id = user_detail['user_id'] @@ -158,15 +161,21 @@ def manifold_add_user(wsgi_request, request): ? """ + + authority_hrn = request['authority_hrn'] + request['authority_hrn'] = authority_hrn.split(".")[0] + USER_CONFIG = '{"firstname": "%(first_name)s", "lastname": "%(last_name)s", "authority": "%(authority_hrn)s"}' user_params = { - 'email' : request['email'], + 'email' : request['username'], 'password' : request['password'], 'config' : USER_CONFIG % request, - 'status' : 1, + 'status' : 1 } + request['authority_hrn'] = authority_hrn + query = Query.create('local:user').set(user_params).select('email') results = execute_admin_query(request, query) if not results: @@ -234,6 +243,8 @@ def make_request_user(user): request['user_hrn'] = user.user_hrn request['public_key'] = user.public_key request['private_key'] = user.private_key + request['username'] = user.login + request['reasons'] = user.reasons return request def make_request_slice(slice): @@ -658,6 +669,21 @@ def create_pending_slice(wsgi_request, request, email): try: # Send an email: the recipients are the PI of the authority recipients = authority_get_pi_emails(wsgi_request, request['authority_hrn']) + + pis = authority_get_pis(request, request['authority_hrn']) + pi_emails = [] + for x in pis: + for e in x['pi_users']: + try: + u = e.split(".")[1] + y = User.objects.get(username = u) + print y.username + if y.username.count("@") != 0: + if y.username.split("@")[1] == request['user_hrn'].split("@")[1]: + pi_emails += [y.email] + print y.email + except: + print "fail" theme.template_name = 'slice_request_email.txt' text_content = render_to_string(theme.template, request) @@ -669,8 +695,12 @@ def create_pending_slice(wsgi_request, request, email): subject = render_to_string(theme.template, request) subject = subject.replace('\n', '') - sender = email - msg = EmailMultiAlternatives(subject, text_content, sender, recipients) + theme.template_name = 'email_default_sender.txt' + sender = render_to_string(theme.template, request) + sender = sender.replace('\n', '') + + #sender = email + msg = EmailMultiAlternatives(subject, text_content, sender, pi_emails) msg.attach_alternative(html_content, "text/html") msg.send() except Exception, e: @@ -688,7 +718,7 @@ def manifold_add_reference_user_accounts(wsgi_request, request): # Retrieve user information user_query = Query().get('local:user') \ .select('user_id', 'config', 'email', 'status') \ - .filter_by('email', '==', request['email']) + .filter_by('email', '==', request['username']) user_details = execute_admin_query(wsgi_request, user_query) # USER MAIN ACCOUNT != reference @@ -749,6 +779,10 @@ def sfa_create_user(wsgi_request, request, namespace = None, as_admin = False): 'user_enabled' : True } + ## Conflict + #query = Query.create('user').set(sfa_user_params).select('user_hrn') + #results = execute_query(wsgi_request, query) + if namespace is not None: query = Query.create('%s:user' % namespace).set(sfa_user_params).select('user_hrn') else: @@ -762,26 +796,64 @@ def sfa_create_user(wsgi_request, request, namespace = None, as_admin = False): if not results: raise Exception, "Could not create %s. Already exists ?" % sfa_user_params['user_hrn'] else: - try: - theme.template_name = 'user_request_validated.txt' - text_content = render_to_string(theme.template, request) - theme.template_name = 'user_request_validated.html' - html_content = render_to_string(theme.template, request) - - theme.template_name = 'email_default_sender.txt' - sender = render_to_string(theme.template, request) - sender = sender.replace('\n', '') + subject = 'User validated' + msg = 'A manager of your institution has validated your account. You have now full user access to the portal.' + send_mail(subject, msg, 'support@fibre.org.br',[request['email']], fail_silently=False) + return results +def ls_create_user(wsgi_request, request, user_detail): + organization = request['username'].split('@')[1] + lsClient = LaboraSchedulerClient( organization ) - subject = 'User validated' + orgGIDNumber = lsClient.get_testbed_info()['gidnumber'] + userHomeDirectory = "/home/" + organization + "/" + request['username'].split('@')[0] + userHomeDirectory = userHomeDirectory.encode('utf-8') + + userData = { + 'username' : request['username'], + 'email' : request['email'].encode('utf-8'), + 'password' : request['password'].encode('utf-8'), + 'name' : str( request['first_name'].encode('latin1') ) + ' ' + str( request['last_name'].encode('latin1') ), + 'gidnumber' : orgGIDNumber, + 'homedirectory' : userHomeDirectory, + 'created_by' : "myslice" + } + + # Add user in the island. + addUser = lsClient.add_user( userData ) + + # User successfully created, upload user public key. + if addUser: + ls_update_public_key( wsgi_request, request, lsClient, addUser ) + + return addUser - msg = EmailMultiAlternatives(subject, text_content, sender, [request['email']]) - msg.attach_alternative(html_content, "text/html") - msg.send() - except Exception, e: - print "Failed to send email, please check the mail templates and the SMTP configuration of your server" +def ls_validate_user(wsgi_request, request): + organization = request['username'].split('@')[1] + lsClient = LaboraSchedulerClient( organization ) + + userId = lsClient.get_user_id_by_username( { 'username': str( request['username'] ) } ) + + validate = False + if userId: + userData = { + 'user_id' : userId, + 'new_user_data' : { 'enable': 'TRUE' } + } + + validate = lsClient.update_user( userData ) + + return validate and addUserPublicKey - return results +def ls_update_public_key( wsgi_request, request, lsClient, userId ): + userPbKey = { + 'user_id' : userId, + 'public_key' : request['public_key'] + } + + addUserPublicKey = lsClient.add_user_public_key( userPbKey ) + + return addUserPublicKey def iotlab_create_user (wsgi_request, request, namespace = None, as_admin=False): @@ -825,15 +897,129 @@ def create_user(wsgi_request, request, namespace = None, as_admin = False): # NOTE : if we were to create a user directly (just like we create slices, # we would have to perform the steps in create_pending_user too + + # Edelberto - I put this more below + # Add the user to the SFA registry + #sfa_create_user(wsgi_request, request) + + # Update Manifold user status + manifold_update_user(wsgi_request, request['username'], {'status': USER_STATUS_ENABLED}) + # Add reference accounts for platforms + manifold_add_reference_user_accounts(wsgi_request, request) + + # Conflict + # sfa_create_user(wsgi_request, request) # Add the user to the SFA registry sfa_create_user(wsgi_request, request, namespace, as_admin) + + # Validate the user using the LS API ( By Bruno - UFG ): + try: + ls_validate_user( wsgi_request, request ) + except Exception, e: + "Error to validate the user in Labora Scheduler." + +def create_user_in_ldap(wsgi_request, request, user_detail): + """ + """ + + # saves the user to django auth_user table [needed for password reset] + user = User.objects.create_user(request['username'], request['email'], request['password']) + + # Creating a manifold user + user_id = manifold_add_user(wsgi_request, request) + + # Creating a Manifold account on the MySlice platform + # Note the JSON representation of public and private keys already includes quotes + account_config = { + 'user_hrn' : request['user_hrn'], + 'user_public_key' : request['public_key'], + } + if request['private_key']: + account_config['user_private_key'] = request['private_key'] + + user_id = user_detail['user_id'] + 1 # the user_id for the newly created user in local:user + + # XXX TODO: Require a myslice platform + # ALERT: this will disapear with ROUTERV2 of Manifold + # We have to consider the case where several registries can be used + # Removed hardcoded platform = 5 + # This platform == 'myslice' is a TMP FIX !! + try: + reg_platform_query = Query().get('local:platform') \ + .filter_by('platform', '==', 'myslice') \ + .select('platform_id') + reg_platform = execute_admin_query(wsgi_request, reg_platform_query) + reg_platform_id = reg_platform[0]['platform_id'] + account_params = { + 'platform_id' : reg_platform_id, # XXX ALERT !! + 'user_id' : user_id, + 'auth_type' : request['auth_type'], + 'config' : json.dumps(account_config), + } + manifold_add_account(wsgi_request, account_params) + except Exception, e: + print "Failed creating manifold account on platform %s for user: %s" % ('myslice', request['email']) + + # XXX This has to be stored centrally + USER_STATUS_ENABLED = 2 # Update Manifold user status - manifold_update_user(wsgi_request, request['email'], {'status': USER_STATUS_ENABLED}) + manifold_update_user(wsgi_request, request['username'], {'status': USER_STATUS_ENABLED}) # Add reference accounts for platforms manifold_add_reference_user_accounts(wsgi_request, request) + + organization = request['username'].split('@')[1] + lsClient = LaboraSchedulerClient( organization ) + + userId = lsClient.get_user_id_by_username( { 'username': str( request['username'] ) } ) + + ls_up_pkey = ls_update_public_key( wsgi_request, request, lsClient, userId ) + + if ls_up_pkey: + print "OK PKEY" + + from sfa.util.xrn import Xrn + + auth_pi = request.get('pi', None) + auth_pi = list([auth_pi]) if auth_pi else list() + + # We create a user request with Manifold terminology + sfa_user_params = { + 'user_hrn' : request['user_hrn'], + 'user_email' : request['email'], + 'user_urn' : Xrn(request['user_hrn'], request['type']).get_urn(), + 'user_type' : request['type'], + 'keys' : request['public_key'], + 'user_first_name' : request['first_name'], + 'user_last_name' : request['last_name'], + 'pi_authorities' : auth_pi, + 'user_enabled' : True + } + + print request['user_hrn'] + print request['email'] + print request['first_name'] + print request['last_name'] + print request['type'] + print request['public_key'] + + query = Query.create('user').set(sfa_user_params).select('user_hrn') + + print query + + results = execute_admin_query(wsgi_request, query) + + print results + + if not results: + raise Exception, "Could not create %s. Already exists ?" % sfa_user_params['user_hrn'] + else: + subject = 'User validated' + msg = 'A manager of your institution has validated your account. You have now full user access to the portal.' + send_mail(subject, msg, 'support@fibre.org.br',[request['email']], fail_silently=False) + return results # Add the user to iotlab portal if theme is set to onelab if theme.theme == 'onelab': @@ -849,15 +1035,18 @@ def create_pending_user(wsgi_request, request, user_detail): last_name = request['last_name'], authority_hrn = request['authority_hrn'], email = request['email'], + login = request['username'], password = request['password'], public_key = request['public_key'], private_key = request['private_key'], user_hrn = request['user_hrn'], pi = request['pi'], email_hash = request['email_hash'], + reasons = request['reasons'], status = 'False', ) b.save() + # sends email to user to activate the email theme.template_name = 'activate_user.html' html_content = render_to_string(theme.template, request) @@ -877,7 +1066,7 @@ def create_pending_user(wsgi_request, request, user_detail): msg.send() # saves the user to django auth_user table [needed for password reset] - user = User.objects.create_user(request['email'], request['email'], request['password']) + user = User.objects.create_user(request['username'], request['email'], request['password']) # Creating a manifold user user_id = manifold_add_user(wsgi_request, request) @@ -903,7 +1092,6 @@ def create_pending_user(wsgi_request, request, user_detail): .filter_by('platform', '==', 'myslice') \ .select('platform_id') reg_platform = execute_admin_query(wsgi_request, reg_platform_query) - reg_platform_id = reg_platform[0]['platform_id'] account_params = { 'platform_id' : reg_platform_id, # XXX ALERT !! @@ -913,13 +1101,32 @@ def create_pending_user(wsgi_request, request, user_detail): } manifold_add_account(wsgi_request, account_params) except Exception, e: - print "Failed creating manifold account on platform %s for user: %s" % ('myslice', request['email']) + print "Failed creating manifold account on platform %s for user: %s" % ('myslice', request['email']) + + # Add user to island using LS API ( By Bruno - UFG ) + ls_user_create = ls_create_user( wsgi_request, request, user_detail ) try: # Send an email: the recipients are the PI of the authority # If No PI is defined for this Authority, send to a default email (different for each theme) - recipients = authority_get_pi_emails(wsgi_request, request['authority_hrn']) - + + split_authority_hrn = request['authority_hrn'].split(".")[0] + + recipients = authority_get_pi_emails(wsgi_request, split_authority_hrn) + + pis = authority_get_pis(request, split_authority_hrn) + pi_emails = [] + for x in pis: + for e in x['pi_users']: + try: + u = e.split(".")[1] + y = User.objects.get(username = u) + if y.username.count("@") != 0: + if y.username.split("@")[1] == request['username'].split("@")[1]: + pi_emails += [y.email] + except: + print "fail" + theme.template_name = 'user_request_email.html' html_content = render_to_string(theme.template, request) @@ -934,9 +1141,11 @@ def create_pending_user(wsgi_request, request, user_detail): sender = render_to_string(theme.template, request) sender = sender.replace('\n', '') - msg = EmailMultiAlternatives(subject, text_content, sender, recipients) + msg = EmailMultiAlternatives(subject, text_content, sender, pi_emails) + msg.attach_alternative(html_content, "text/html") msg.send() + print pi_emails except Exception, e: print "Failed to send email, please check the mail templates and the SMTP configuration of your server" import traceback diff --git a/portal/contactview.py b/portal/contactview.py index 19cc1b94..24e06ebe 100644 --- a/portal/contactview.py +++ b/portal/contactview.py @@ -108,5 +108,6 @@ class ContactView (FreeAccessView, ThemeView): 'theme' : self.theme, 'username': username, 'pi': pi, - 'section': "Contact" + 'section': "Contact", + 'email': request.user.username }) diff --git a/portal/database.py b/portal/database.py new file mode 100644 index 00000000..376d18b0 --- /dev/null +++ b/portal/database.py @@ -0,0 +1,57 @@ +import sys +import psycopg2 +import psycopg2.extras + +class Database(): + + def __init__( self, DbConfigurations ): + self.connection = None + self.connect( DbConfigurations['dbHost'], DbConfigurations['dbUser'], + DbConfigurations['dbPassword'], DbConfigurations['dbName'] ) + + + def connect( self, host, user, password, database ): + result = True + try: + self.connection = psycopg2.connect( "host=" + host + " dbname=" + database + " user=" + + user + " password=" + password ) + except psycopg2.DatabaseError, e: + result = False + + return result + + + def close( self ): + if self.connection: + self.connection.close() + self.connection = None + + + def query( self, queryString ): + result = True + try: + cursor = self.connection.cursor() + cursor.execute( queryString ) + self.connection.commit() + except Exception, e: + result = False + self.rollback() + + return result + + + def rollback( self ): + if self.connection: + self.connection.rollback() + + + def fetchRows( self, queryString ): + rows = None + try: + cursor = self.connection.cursor( cursor_factory = psycopg2.extras.RealDictCursor ) + cursor.execute( queryString ) + rows = cursor.fetchall() + except Exception, e: + pass + + return rows diff --git a/portal/emailactivationview.py b/portal/emailactivationview.py index 533cccfa..3dc388be 100644 --- a/portal/emailactivationview.py +++ b/portal/emailactivationview.py @@ -2,7 +2,7 @@ from unfold.loginrequired import FreeAccessView # from manifold.core.query import Query from manifoldapi.manifoldapi import execute_query, execute_admin_query -from portal.actions import manifold_update_user, manifold_update_account, manifold_add_account, manifold_delete_account, sfa_update_user, authority_get_pi_emails, make_request_user, create_user +from portal.actions import manifold_update_user, manifold_update_account, manifold_add_account, manifold_delete_account, sfa_update_user, authority_get_pi_emails, make_request_user, create_user, authority_get_pis # from unfold.page import Page from ui.topmenu import topmenu_items_live, the_user @@ -14,7 +14,7 @@ from myslice.theme import ThemeView from portal.models import PendingUser, PendingAuthority from django.core.mail import EmailMultiAlternatives, send_mail from django.contrib.sites.models import Site - +from django.contrib.auth.models import User # import json, os, re, itertools @@ -52,6 +52,45 @@ class ActivateEmailView(FreeAccessView, ThemeView): hash_code=value if PendingUser.objects.filter(email_hash__iexact = hash_code).filter(status__iexact = 'False'): activation = 'success' + # sending email after activation success + try: + request = PendingUser.objects.filter(email_hash= hash_code) + split_authority_hrn = request[0].authority_hrn.split('.')[0] + pis = authority_get_pis(request, split_authority_hrn) + pi_emails = [] + for x in pis: + for e in x['pi_users']: + u = e.split('.')[1] + y = User.Objects.get(username = u) + if y.username.count("@") != 0: + if y.username.split("@")[1] == request[0].user_hrn.split("@")[1]: + pi_emails += [y.email] + subject = 'User email activated' + msg = 'The user %s has validated his/her email. Now you can validate his/her account' % (request[0].login) + send_mail(subject, msg, 'support@fibre.org.br', pi_emails, fail_silently = False) + except: + print "error sending the email!" + #try: + # Send an email: the recipients are the PI of the authority + # If No PI is defined for this Authority, send to a default email (different for each theme) + # recipients = authority_get_pi_emails(wsgi_request, authority_hrn) + # theme.template_name = 'user_request_email.html' + # html_content = render_to_string(theme.template, request) + # theme.template_name = 'user_request_email.txt' + # text_content = render_to_string(theme.template, request) + # theme.template_name = 'user_request_email_subject.txt' + # subject = render_to_string(theme.template, request) + # subject = subject.replace('\n', '') + # theme.template_name = 'email_default_sender.txt' + # sender = render_to_string(theme.template, request) + # sender = sender.replace('\n', '') + # msg = EmailMultiAlternatives(subject, text_content, sender, recipients) + # msg.attach_alternative(html_content, "text/html") + # msg.send() + # except Exception, e: + # print "Failed to send email, please check the mail templates and the SMTP configuration of your server" + # import traceback + # traceback.print_exc() # AUTO VALIDATION of PLE enabled users (only for OneLab Portal) if self.theme == "onelab": diff --git a/portal/homeview.py b/portal/homeview.py index 00b279d2..08f38183 100644 --- a/portal/homeview.py +++ b/portal/homeview.py @@ -1,7 +1,7 @@ # this somehow is not used anymore - should it not be ? from django.core.context_processors import csrf from django.http import HttpResponseRedirect -from django.contrib.auth import authenticate, login, logout +from django.contrib.auth import authenticate, login, logout, get_user_model from django.template import RequestContext from django.shortcuts import render_to_response from django.shortcuts import render @@ -11,7 +11,20 @@ import json from unfold.loginrequired import FreeAccessView from manifold.core.query import Query -from manifoldapi.manifoldapi import execute_query +#from manifoldapi.manifoldapi import execute_query +# LDAP query admin // If transfer this code to actions.py maybe don't need more execute_admin_query +from manifoldapi.manifoldapi import execute_query, execute_admin_query +# Edelberto - LDAP XXX +from portal.models import PendingUser +from django.contrib.auth.models import User #Pedro +from portal.actions import create_pending_user, create_user, create_user_in_ldap, clear_user_creds +from registrationview import RegistrationView +from random import randint +from hashlib import md5 +from django.contrib.sites.models import Site +import os.path, re +################## + from manifoldapi.manifoldresult import ManifoldResult from ui.topmenu import topmenu_items, the_user @@ -19,7 +32,10 @@ from myslice.configengine import ConfigEngine from myslice.theme import ThemeView -import activity.user +# Edelberto LDAP authentication XXX +import ldap + +#import activity.user class HomeView (FreeAccessView, ThemeView): template_name = 'home-view.html' @@ -35,86 +51,271 @@ class HomeView (FreeAccessView, ThemeView): env['theme'] = self.theme env['section'] = "Dashboard" - username = request.POST.get('username') + username = request.POST.get('username').lower() password = request.POST.get('password') - - # pass request within the token, so manifold session key can be attached to the request session. - token = {'username': username, 'password': password, 'request': request} - - # our authenticate function returns either - # . a ManifoldResult - when something has gone wrong, like e.g. backend is unreachable - # . a django User in case of success - # . or None if the backend could be reached but the authentication failed - auth_result = authenticate(token=token) - # use one or two columns for the layout - not logged in users will see the login prompt - # high-level errors, like connection refused or the like - if isinstance (auth_result, ManifoldResult): - manifoldresult = auth_result - # let's use ManifoldResult.__repr__ - env['state']="%s"%manifoldresult - - return render_to_response(self.template,env, context_instance=RequestContext(request)) - # user was authenticated at the backend - elif auth_result is not None: - user=auth_result - if user.is_active: - print "LOGGING IN" - login(request, user) - - if request.user.is_authenticated(): - env['person'] = self.request.user - env['username'] = self.request.user - - # log user activity - activity.user.login(self.request) - - ## check user is pi or not - platform_details = {} - account_details = {} - acc_auth_cred = {} - acc_user_cred = {} - platform_query = Query().get('local:platform').select('platform_id','platform','gateway_type','disabled') - account_query = Query().get('local:account').select('user_id','platform_id','auth_type','config') - platform_details = execute_query(self.request, platform_query) - account_details = execute_query(self.request, account_query) - if platform_details is not None and platform_details != {}: - for platform_detail in platform_details: - for account_detail in account_details: - if platform_detail['platform_id'] == account_detail['platform_id']: - if 'config' in account_detail and account_detail['config'] is not '': - account_config = json.loads(account_detail['config']) - if 'myslice' in platform_detail['platform']: - acc_auth_cred = account_config.get('delegated_authority_credentials','N/A') - acc_user_cred = account_config.get('delegated_user_credential','N/A') - # assigning values - if acc_auth_cred=={} or acc_auth_cred=='N/A': - pi = "is_not_pi" - else: - pi = "is_pi" - - # check if the user has creds or not - if acc_user_cred == {} or acc_user_cred == 'N/A': - user_cred = 'no_creds' - else: - user_cred = 'has_creds' - - - env['pi'] = pi - env['user_cred'] = user_cred - else: - env['person'] = None - return render_to_response(self.template,env, context_instance=RequestContext(request)) - else: - # log user activity - activity.user.login(self.request, "notactive") - env['state'] = "Your account is not active, please contact the site admin." - env['layout_1_or_2']="layout-unfold2.html" - - return render_to_response(self.template,env, context_instance=RequestContext(request)) - # otherwise + + # LDAP form - If FIBRE, then get the possibilite to authenticate using usernameldap + #if self.theme == 'fibre': + #usernameldap = request.POST.get('usernameldap') + #token = {'usernameldap': usernameldap, 'username': username ,'password': password, 'request': request} + + ################################################## + ########## XXX Edelberto 010914 XXX + ################################################# + ## first you must open a connection to the server + try: + # Connect to NOC + l = ldap.initialize("ldap://10.128.0.50:389") + # Bind/authenticate with a root user to search all objects + l.simple_bind_s("cn=Manager,dc=br,dc=fibre","fibre2013") + + l.protocol_version = ldap.VERSION3 + except ldap.LDAPError, e: + print e + + ## Base directory + baseDN = "dc=fibre" + searchScope = ldap.SCOPE_SUBTREE + ## retrieve all attributes + retrieveAttributes = None + #retrieveAttributes = ['userEnable'] + searchFilter = "uid=" + username + print searchFilter + + in_ldap = 0 + + try: + if username != "admin": + ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes) + result_set = [] + result_type, result_data = l.result(ldap_result_id, 0) + if (result_data == []): + print "User doesnt exist in LDAP" + in_ldap = 0 + else: + if result_type == ldap.RES_SEARCH_ENTRY: + result_set.append(result_data) + else: + result_set.append(result_data) + # TRUE or FALSE for userEnable attribute + userEnable = result_set[0][0][1]['userEnable'][0] + if userEnable == 'TRUE': + in_ldap = 1 + enabled = 1 + print "In LDAP and Enabled" + + dn = result_set[0][0][0] + try: + l.simple_bind_s(dn,password) + pwd = 1 + print "User password OK" + + except: + pwd = 0 + print "User password WRONG" + + if in_ldap and enabled and pwd: + ldap_mail = result_set[0][0][1]['mail'][0] + + user_exists = Query().get('local:user') \ + .select('status') \ + .filter_by('email', '==', username) + results = execute_admin_query(request, user_exists) + print "DEBUG: %s" % user_exists + if results: + print "DEBUG: user exists on MySlice DBs" + else: + print "DEBUG: user NOT exists on MySlice DBs" + + cn = result_set[0][0][1]['cn'][0] + sn = result_set[0][0][1]['sn'][0] + + fname=None + lname=None + + try: + fname = sn.split(' ')[0] + lname = sn.split(' ')[1] + except: + fname = sn + lname = "" + + #authority_hrn = 'fibre' + '.' + username.split('@')[1] + authority_hrn = 'fibre' + print authority_hrn + email = ldap_mail + print ldap_mail + username = username + print username + password = password + print password + # user_hrn = 'fibre' + '.' + username.split('@')[1] + '.' + username + user_hrn = 'fibre' + '.' + username + print user_hrn + + # Based on registrationview + + + # get the domain url + current_site = Site.objects.get_current() + current_site = current_site.domain + print current_site + + post_email = ldap_mail + salt = randint(1,100000) + email_hash = md5(str(salt)+post_email).hexdigest() + print email_hash + + user_request = { + 'first_name' : fname, + 'last_name' : lname, + 'organization' : authority_hrn, + 'authority_hrn' : authority_hrn, + 'email' : ldap_mail, + 'username' : username, + 'password' : password, + 'current_site' : current_site, + 'email_hash' : email_hash, + 'pi' : '', + 'user_hrn' : user_hrn, + 'reasons' : 'already exists in the LDAP', + 'type' : 'user', + 'validation_link': 'https://' + current_site + '/portal/email_activation/'+ email_hash + } + + # Validate input + errors = [] + UserModel = get_user_model() + if (re.search(r'^[\w+\s.@+-]+$', user_request['first_name']) == None): + errors.append('First name may contain only letters, numbers, spaces and @/./+/-/_ characters.') + if (re.search(r'^[\w+\s.@+-]+$', user_request['last_name']) == None): + errors.append('Last name may contain only letters, numbers, spaces and @/./+/-/_ characters.') + if (re.search(r'^[\w,]+$' , username) == None): + errors.append('Username may contain only letters,numbers and -/_ characters.') + # checking in django_db !! + if PendingUser.objects.filter(email__iexact = user_request['email']): + errors.append('Email is pending for validation. Please provide a new email address.') + if User.objects.filter(username__iexact = user_request['username']): + errors.append('This username is already in use, try another one') + # Does the user exist in Manifold? + user_query = Query().get('local:user').select('user_id','email') + user_details = execute_admin_query(request, user_query) + for user_detail in user_details: + if user_detail['email'] == user_request['email']: + errors.append('Email already registered in Manifold. Please provide a new email address.') + # Does the user exist in sfa? [query is very slow!!] + #user_query = Query().get('user').select('user_hrn','user_email') + # XXX Test based on the user_hrn is quick + #user_query = Query().get('user').select('user_hrn','user_email').filter_by('user_hrn','==',user_request['user_hrn']) + user_query = Query().get('user').select('user_hrn','user_email').filter_by('user_hrn','==',user_hrn) + user_details_sfa = execute_admin_query(request, user_query) + + #if 'generate' in wsgi_request.POST['question']: + user_request['auth_type'] = 'managed' + + # XXX Common code, dependency ? + from Crypto.PublicKey import RSA + private = RSA.generate(1024) + + # Example: private_key = '-----BEGIN RSA PRIVATE KEY-----\nMIIC...' + # Example: public_key = 'ssh-rsa AAAAB3...' + user_request['private_key'] = private.exportKey() + user_request['public_key'] = private.publickey().exportKey(format='OpenSSH') + + # XXX Verify if errors exist - After! + #if not errors: + create_user_in_ldap(request, user_request, user_detail) + #create_pending_user(request, user_request, user_detail) + + #create_user(request, user_request) + + env['state'] = "LDAP associated. Please, login again." + return render_to_response(self.template, env, context_instance=RequestContext(request)) + + + else: + env['state'] = "Access denied. Verify LDAP userEnable and password." + return render_to_response(self.template, env, context_instance=RequestContext(request)) + + else: + in_ldap = 1 + enabled = 0 + print "In LDAP but Disabled" + env['state'] = "Access denied. Verify LDAP userEnable." + return render_to_response(self.template, env, context_instance=RequestContext(request)) + + #print result_set + except ldap.LDAPError, e: + print e + + #else: + if in_ldap and enabled and pwd or username=="admin": + +################################################################################ +### XXX Edelberto LDAP auth end XXX +############################################################################### + # Follow original code + ## pass request within the token, so manifold session key can be attached to the request session. + token = {'username': username, 'password': password, 'request': request} + + # our authenticate function returns either + # . a ManifoldResult - when something has gone wrong, like e.g. backend is unreachable + # . a django User in case of success + # . or None if the backend could be reached but the authentication failed + auth_result = authenticate(token=token) + # use one or two columns for the layout - not logged in users will see the login prompt + # high-level errors, like connection refused or the like + if isinstance (auth_result, ManifoldResult): + manifoldresult = auth_result + # let's use ManifoldResult.__repr__ + env['state']="%s"%manifoldresult + + return render_to_response(self.template,env, context_instance=RequestContext(request)) + # user was authenticated at the backend + elif auth_result is not None: + user=auth_result + if user.is_active: + print "LOGGING IN" + login(request, user) + + if request.user.is_authenticated(): + env['person'] = self.request.user + env['username'] = self.request.user + + ## check user is pi or not + platform_query = Query().get('local:platform').select('platform_id','platform','gateway_type','disabled') + account_query = Query().get('local:account').select('user_id','platform_id','auth_type','config') + + # Edleberto + #cc_auth_cred = {} + + platform_details = execute_query(self.request, platform_query) + account_details = execute_query(self.request, account_query) + for platform_detail in platform_details: + for account_detail in account_details: + if platform_detail['platform_id'] == account_detail['platform_id']: + if 'config' in account_detail and account_detail['config'] is not '': + account_config = json.loads(account_detail['config']) + if 'myslice' in platform_detail['platform']: + acc_auth_cred = account_config.get('delegated_authority_credentials','N/A') + # assigning values + if acc_auth_cred=={} or acc_auth_cred=='N/A': + pi = "is_not_pi" + else: + pi = "is_pi" + env['pi'] = pi + else: + env['person'] = None + return render_to_response(self.template,env, context_instance=RequestContext(request)) + else: + env['state'] = "Your account is not active, please contact the site admin." + env['layout_1_or_2']="layout-unfold2.html" + + return render_to_response(self.template,env, context_instance=RequestContext(request)) + # otherwise else: # log user activity - activity.user.login(self.request, "error") + #activity.user.login(self.request, "error") env['state'] = "Your username and/or password were incorrect." return render_to_response(self.template, env, context_instance=RequestContext(request)) diff --git a/portal/lsapiclient.py b/portal/lsapiclient.py new file mode 100644 index 00000000..23584abf --- /dev/null +++ b/portal/lsapiclient.py @@ -0,0 +1,107 @@ +import sys +import urllib +import urllib2 +import json +import socket +from urlparse import urlparse +from database import Database + +class LaboraSchedulerClient: + """ + A simple rest shell to a Labora Scheduler instance + This class can receive Labora API calls to the underlying testbed + """ + + direct_calls = [ 'get_testbed_info', 'get_users', 'add_user', 'delete_user', 'update_user', + 'get_user_id_by_username', 'add_user_public_key', 'delete_user_public_key' ] + + def __init__ ( self, organization ): + self.url, self.key = self.getOrganizationConfigs( organization ) + + def __getattr__(self, name): + + def func(*args, **kwds): + actual_name = None + + if name in LaboraSchedulerClient.direct_calls: + actual_name = name + + if not actual_name: + raise Exception, "Method %s not found in Labora Scheduler"%(name) + return + + if not self.url or not self.key: + raise Exception, "Missing Labora Scheduler island url and/or key." + return + + address = self.url + "?method=" + actual_name + "&key=" + self.key + + # get the direct_call parameters + method_parameters = [] + + if actual_name == "get_users": + method_parameters.extend(['filter']) + elif actual_name == "update_user": + method_parameters.extend(['user_id', 'new_user_data']) + elif actual_name == "delete_user" or actual_name == "delete_user_public_key": + method_parameters.extend(['user_id']) + elif actual_name == "get_user_id_by_username": + method_parameters.extend(['username']) + elif actual_name == "add_user": + method_parameters.extend(['username', 'email', 'password', 'name', 'gidnumber', + 'homedirectory', 'created_by']) + elif actual_name == "add_user_public_key": + method_parameters.extend(['user_id', 'public_key']) + + for parameter in args: + if isinstance(parameter, (frozenset, list, set, tuple, dict)): + for key_name in parameter.keys(): + + if key_name in method_parameters: + param_value = parameter[key_name] + + if param_value == None: + continue + + if isinstance(param_value, (frozenset, list, set, tuple, dict)): + param_value = json.dumps(param_value) + + param_value = urllib.quote(param_value.encode('utf-8')) + + address += "&" + key_name + "=" + param_value + + api_call = urllib2.urlopen(address) + api_call = json.load(api_call) + + if not api_call['call_status']: + result = api_call['method_result'] + else: + result = False + + + return result + + return func + + + def getOrganizationConfigs( self, organization ): + ls_url = None + ls_key = None + + databaseConfig = { + 'dbHost' : '10.128.11.200', + 'dbUser' : 'postgres', + 'dbPassword' : '5e6b70f2e9dc', + 'dbName' : 'LaboraSchedulerNOC' + } + + databaseConnection = Database( databaseConfig ) + + query = "SELECT * FROM islands WHERE domain ='" + organization + "'" + orgConfig = databaseConnection.fetchRows( query ) + + if orgConfig: + ls_url = orgConfig[0]["ls_url"] + ls_key = orgConfig[0]["ls_key"] + + return ls_url, ls_key diff --git a/portal/managementtabrequests.py b/portal/managementtabrequests.py index 31cbb0d7..66f9991f 100644 --- a/portal/managementtabrequests.py +++ b/portal/managementtabrequests.py @@ -18,6 +18,7 @@ from portal.actions import get_requests from myslice.theme import ThemeView import json +import ast class ManagementRequestsView (LoginRequiredView, ThemeView): template_name = "management-tab-requests.html" @@ -28,15 +29,21 @@ class ManagementRequestsView (LoginRequiredView, ThemeView): ctx_delegation_authorities = {} ctx_sub_authorities = {} dest = {} - + user_username = '' + user_authority = '' # The user need to be logged in if (self.request.user): - user_query = Query().get('local:user').filter_by('email', '==', self.request.user.email).select('user_id') + user_query = Query().get('local:user').filter_by('email', '==', self.request.user.username).select('user_id') user, = execute_query(self.request, user_query) user_id = user['user_id'] - + user_query = Query().get('local:user').filter_by('email', '==', self.request.user.username).select('config') + user, = execute_query(self.request, user_query) + user_config = user['config'] + user_config = ast.literal_eval(user_config) + user_authority = user_config['authority'] + user_username = self.request.user.username # Query manifold to learn about available SFA platforms for more information # In general we will at least have the portal # For now we are considering all registries @@ -120,8 +127,9 @@ class ManagementRequestsView (LoginRequiredView, ThemeView): # iterate on the requests and check if the authority matches a prefix # startswith an authority on which the user is PI requests = get_requests() - for r in requests: - auth_hrn = r['authority_hrn'] + auth_hrn = '' + for r in requests: + auth_hrn = r['authority_hrn'] for my_auth in pi_my_authorities: if auth_hrn.startswith(my_auth): dest = ctx_my_authorities @@ -156,10 +164,24 @@ class ManagementRequestsView (LoginRequiredView, ThemeView): # env['pi'] = "is_pi" # env['theme'] = self.theme # env['section'] = "Requests" - +# auth_hrn = user_authority + '.' + user_username.split("@")[1] + ctx_list = [ctx_my_authorities, ctx_sub_authorities, ctx_delegation_authorities] + for ctx in ctx_list: + if ctx: + for authorities in ctx: + for requests in ctx[authorities]: + try: + requests['object_auth'] = requests['user_hrn'].split('.')[0] + '.' + requests['user_hrn'].split('@')[1] + except: + print "This object has no user_hrn" + + pi_authority = user_authority + '.' + user_username.split("@")[1] context = super(ManagementRequestsView, self).get_context_data(**kwargs) - - + #print "testing" + #print ctx_my_authorities + #print auth_hrn + #print user_username + #print pi_authority context['my_authorities'] = ctx_my_authorities context['sub_authorities'] = ctx_sub_authorities context['delegation_authorities'] = ctx_delegation_authorities @@ -174,6 +196,7 @@ class ManagementRequestsView (LoginRequiredView, ThemeView): context['pi'] = "is_pi" context['theme'] = self.theme context['section'] = "Requests" + context['pi_authority'] = pi_authority # XXX We need to prepare the page for queries #context.update(page.prelude_env()) diff --git a/portal/models.py b/portal/models.py index cc484b5b..acc336b4 100644 --- a/portal/models.py +++ b/portal/models.py @@ -66,7 +66,8 @@ class PendingUser(models.Model): login = models.TextField() pi = models.TextField() email_hash = models.TextField() - status = models.TextField() + status = models.TextField() + reasons = models.TextField() created = models.DateTimeField(auto_now_add = True) # models.ForeignKey(Institution) diff --git a/portal/registrationview.py b/portal/registrationview.py index e55e30fd..c52689f6 100644 --- a/portal/registrationview.py +++ b/portal/registrationview.py @@ -18,6 +18,8 @@ from manifoldapi.manifoldapi import execute_admin_query from manifold.core.query import Query from portal.models import PendingUser +from django.contrib.auth.models import User #Pedro + from portal.actions import create_pending_user from myslice.theme import ThemeView @@ -41,7 +43,7 @@ class RegistrationView (FreeAccessView, ThemeView): """ errors = [] authority_hrn = None - authorities_query = Query.get('authority').select('name', 'authority_hrn') + authorities_query = Query.get('authority').select('name','authority_hrn') authorities = execute_admin_query(wsgi_request, authorities_query) if authorities is not None: authorities = sorted(authorities) @@ -60,9 +62,9 @@ class RegistrationView (FreeAccessView, ThemeView): # get the domain url current_site = Site.objects.get_current() current_site = current_site.domain - + print "############ BREAKPOINT 3 #################" - + for authority in authorities: if authority['name'] == wsgi_request.POST.get('org_name', ''): authority_hrn = authority['authority_hrn'] @@ -83,29 +85,50 @@ class RegistrationView (FreeAccessView, ThemeView): 'organization' : wsgi_request.POST.get('org_name', ''), 'authority_hrn' : authority_hrn, 'email' : post_email, + 'username' : wsgi_request.POST.get('username','').lower(), 'password' : wsgi_request.POST.get('password', ''), + 'reasons' : wsgi_request.POST.get('reasons', ''), 'current_site' : current_site, 'email_hash' : email_hash, 'pi' : '', - 'validation_link': 'http://' + current_site + '/portal/email_activation/'+ email_hash + 'validation_link': 'https://' + current_site + '/portal/email_activation/'+ email_hash } print "############ BREAKPOINT 5 #################" # Construct user_hrn from email (XXX Should use common code) - split_email = user_request['email'].split("@")[0] - split_email = split_email.replace(".", "_") - # Replace + by _ => more convenient for testing and validate with a real email - split_email = split_email.replace("+", "_") - user_request['user_hrn'] = user_request['authority_hrn'] \ - + '.' + split_email + # split_email = user_request['email'].split("@")[0] + # split_email = split_email.replace(".", "_") + # user_request['user_hrn'] = user_request['authority_hrn'] \ + # + '.' + split_email + username = user_request['username'] + + if user_request['authority_hrn'] == "fibre" : + user_request['username'] = user_request['username'] + "@" + "rnp" # catch-all island + split_authority = user_request['authority_hrn'] + else : + split_authority = user_request['authority_hrn'].split(".")[1] + user_request['username'] = user_request['username'] + '@' + split_authority + split_authority = user_request['authority_hrn'].split(".")[0] + + user_request['user_hrn'] = split_authority + '.' + user_request['username'] + # Validate input UserModel = get_user_model() if (re.search(r'^[\w+\s.@+-]+$', user_request['first_name']) == None): errors.append('First name may contain only letters, numbers, spaces and @/./+/-/_ characters.') if (re.search(r'^[\w+\s.@+-]+$', user_request['last_name']) == None): errors.append('Last name may contain only letters, numbers, spaces and @/./+/-/_ characters.') + if (re.search(r'^[\w,]+$' , username) == None): + errors.append('Username may contain only letters,numbers and -/_ characters.') + # checking in django_db !! + if PendingUser.objects.filter(email__iexact = user_request['email']): + errors.append('Email is pending for validation. Please provide a new email address.') + # if UserModel._default_manager.filter(email__iexact = user_request['email']): + # errors.append('This email is not usable. Please contact the administrator or try with another email.') + if User.objects.filter(username__iexact = user_request['username']): + errors.append('This username is already in use, try another one') # Does the user exist in Manifold? user_query = Query().get('local:user').select('user_id','email') user_details = execute_admin_query(wsgi_request, user_query) @@ -119,14 +142,14 @@ class RegistrationView (FreeAccessView, ThemeView): user_query = Query().get('user').select('user_hrn','user_email').filter_by('user_hrn','==',user_request['user_hrn']) user_details_sfa = execute_admin_query(wsgi_request, user_query) - for user in user_details_sfa: - if user['user_email'] == user_request['email']: - errors.append('Email already registered in OneLab registry. Contact OneLab support or use another email.') - if user['user_hrn'] == user_request['user_hrn']: - # add random number if user_hrn already exists in the registry - user_request['user_hrn'] = user_request['authority_hrn'] \ - + '.' + split_email + str(randint(1,1000000)) - + # for user in user_details_sfa: + # if user['user_email'] == user_request['email']: + # errors.append('Email already registered in SFA registry. Please use another email.') + # if user['user_hrn'] == user_request['user_hrn']: + # # add random number if user_hrn already exists in the registry + # user_request['user_hrn'] = user_request['authority_hrn'] \ + # + '.' + split_email + str(randint(1,1000000)) + # checking in django unfold db portal application pending users # sqlite3 /var/unfold/unfold.sqlite3 # select email from portal_pendinguser; @@ -137,7 +160,7 @@ class RegistrationView (FreeAccessView, ThemeView): # sqlite3 /var/unfold/unfold.sqlite3 # select email from auth_user; if UserModel._default_manager.filter(email__iexact = user_request['email']): - errors.append('Contact OneLab support or try with another email.') + errors.append('Please try with another email.') # XXX TODO: Factorize with portal/accountview.py # XXX TODO: Factorize with portal/registrationview.py @@ -174,8 +197,9 @@ class RegistrationView (FreeAccessView, ThemeView): create_pending_user(wsgi_request, user_request, user_detail) self.template_name = 'user_register_complete.html' # log user activity - activity.user.registered(self.request) - return render(wsgi_request, self.template, {'theme': self.theme}) + #activity.user.registered(self.request) + + return render(wsgi_request, self.template, {'theme': self.theme, 'REQINST':wsgi_request.POST.get('org_name', '').split(".")[1].upper()}) else: print "############ BREAKPOINT A #################" @@ -194,7 +218,8 @@ class RegistrationView (FreeAccessView, ThemeView): 'topmenu_items': topmenu_items_live('Register', page), 'errors': errors, 'authorities': authorities, - 'theme': self.theme + 'theme': self.theme, + 'section':'Registration' } template_env.update(user_request) template_env.update(reg_form) diff --git a/portal/slicerequestview.py b/portal/slicerequestview.py index e6683bd4..97a11dd0 100644 --- a/portal/slicerequestview.py +++ b/portal/slicerequestview.py @@ -181,7 +181,7 @@ class SliceRequestView (LoginRequiredAutoLogoutView, ThemeView): 'url': url, 'pi': pi, 'authority_name': authority_name, - 'authority_hrn': user_authority, + 'authority_hrn': user_authority, 'cc_myself': True, 'authorities': authorities, 'theme': self.theme, diff --git a/portal/static/css/fibre.css b/portal/static/css/fibre.css new file mode 100644 index 00000000..05526c88 --- /dev/null +++ b/portal/static/css/fibre.css @@ -0,0 +1,698 @@ +@import url("../fonts/opensans_bold_macroman/stylesheet.css"); + +html { height: 100% } + +body { + background-color:white; + color:black; + margin:0; + padding:0; + height: 100%; +} +a, a:active, a:focus { + outline: 0; + text-decoration:none; +} + +h1 { + border-bottom:1px solid #DDDDDD; + padding:0 0 0 0; + margin:15px 0 15px 0; + font-size:14pt; +} +h1 img { + vertical-align:middle; + margin-bottom:4px; + margin-right:10px; +} +h2 { + font-size:14pt; + color:#333333; +} +h3 { + font-size:13pt; + color:#201E62; +} +input[type=text], input[type=password], input[type=email], input[type=tel], input[type=number], select, option { + min-width:260px; + padding:6px; + border:1pt solid #22606D; + vertical-align:bottom; + border-radius:0; +} + +textarea { + padding:6px; + border:1pt solid #22606D !important; + border-radius:0 !important; +} + +span.label { + font-size:11pt; + color:gray; + font-weight:normal; + padding:0; +} +div.el { + padding-bottom:15px; +} +div.breadcrumbs { + margin:15px 0; + color:gray; + font-size:10pt; +} +/* buttons */ +button.btn, input.btn { + padding:6px 10px; + border-radius:5px; + font-size:10pt; + font-weight:normal; +} +button.btn span.glyphicon { + margin-right:6px; +} +button.btn-default { + border-bottom:3px solid #cccccc; +} +button.btn-default:hover { + background-color:white; + border:1px solid #ADADAD; + border-bottom:3px solid #ADADAD; +} +button.btn-default:active { + background-color:white; + border:1px solid #ADADAD; + border-bottom:1px solid #ADADAD; + margin-top:2px; + box-shadow:none; +} +button.btn-primary { + border-bottom:3px solid #3071A9; +} +button.btn-primary:hover { + box-shadow:none; + background-color:#428bca; + border:1px solid #357ebd; + border-bottom:3px solid #3071A9; +} +button.btn-primary:active { + box-shadow:none; + border-bottom:1px solid #3071A9; + margin-top:2px; +} +button.btn-danger { + border-bottom:3px solid #A13F3A; +} +button.btn-danger:hover { + box-shadow:none; + background-color:#d9534f; + border:1px solid #d43f3a; + border-bottom:3px solid #A13F3A; +} +button.btn-danger:active { + border:1px solid #d43f3a; + box-shadow:none; + margin-top:2px; +} +button.btn-onelab, input.btn-onelab { + border:0; + border-bottom:3px solid #760073; + background-color:#302562; + color:white; +} +button.btn-onelab:hover, input.btn-onelab:hover { + border:0; + border-bottom:3px solid #760073; + background-color:#302562; + color:white; +} +button.btn-onelab:active, input.btn-onelab:active { + box-shadow:none; + border-bottom:1px solid #760073; + margin-top:2px; +} + +.container-resource button { + padding:2px 4px; + border-radius:3px; + font-size:9pt; + font-weight:normal; +} +.container-resource select,.container-resource option, .container-resource input { + padding:2px 4px; + font-size:9pt; +} +.badge { + font-size:9pt; + margin-left:4px; +} +/***** Notifications *****/ +.warning { + border: 1px solid red; + margin: 20px 60px; + padding: 10px 20px; + color: red; + background-color: #f2dbdb; + text-align: center; +} + +/* HOME DASHBOARD */ +div#home-dashboard { + color:black; + margin:0 auto 25px auto; +} +div#home-dashboard table { + margin:25px; + width:100%; +} +div#home-dashboard table td { + text-align:center; + padding:15px 0; + width:33%; +} +div#home-dashboard table tr:first-child td { + font-size:12pt; + font-weight:bold; + color:#270A5A; +} +div#home-dashboard table tr:last-child td { + vertical-align:top; + padding:25px 0; +} +div#home-dashboard table tr:last-child td.logged-in { + border-right:1px solid #DDDDDD; + padding:25px; +} +div#home-dashboard table tr:last-child td.support { + border-left:1px solid #DDDDDD; + padding:25px; +} +div#home-dashboard table tr:last-child td:first-child { +} +div#home-dashboard table tr:last-child td:last-child { + border-right:0; +} +div#home-dashboard table tr:last-child td.logged-in div { + text-align:left; + padding:25px 0; +} +div#home-dashboard table tr:last-child td.support div { + text-align:left; + padding:25px 0; +} +div#home-dashboard div.login-widget { + padding:20px; +} +div#home-dashboard table td.support { +} +div#home-dashboard table td.support a { +} +div#home-dashboard table td.support a:hover { + text-decoration:none; +} + +div#home-dashboard div#manager { + display:none; +} + +div#home-dashboard div#home-slice-list { + margin:25px 0; + padding:0 25px; + text-align:left; +} +div#home-dashboard div#home-slice-list ul { + list-style: none; + padding:0; + margin:0; +} +div#home-dashboard div#home-slice-list li { + +} + +/**/ + +/**/ +/* WELL */ +div.well { +} +/**/ +/* TABLE */ +table.table { + margin:0; +} +table.table thead { + padding:0; +} +table.table tbody { + padding:0; +} +table.table tr { + padding:0; +} +table.table td { + padding:0; +} +/* INSTITUTION */ +div#institution { + color:black; +} +.form-hint { + font-size:11pt; + font-style:italic; + color:gray; +} + + +.form-hint { + font-size:11pt; + font-style:italic; + color:gray; +} + + + +/* TICKET REQUEST */ +div#ticket-request { + color:black; +} +.form-hint { + font-size:11pt; + font-style:italic; + color:gray; +} +div#ticket-request p { + margin:20px 0; +} + +ul.nav-tabs { + margin:0 0 15px 0; +} +ul.nav-tabs ul {} +ul.nav-tabs li {} + +ul.nav-section li a { + color:black; + border-bottom:0; +} +ul.nav-section li:first-child { + padding:0; +} +ul.nav-section li:first-child a { +} +ul.nav-section li:first-child.active a { +} + +ul.nav-resources { + margin:15px 0; +} +ul.nav-resources a { + padding: 4px 10px 5px 10px; +} + +/* SLICE VIEW */ +div.container-resource, div.container-slice { + padding-right:15px; + padding-left:15px; +} +.table th { + border-top:0 !important; +} +div#slice-view { + margin:0; +} +div.list-group-item { + border:0; + -moz-border-radius: 0; + border-radius: 0; + background-color:white; + font-weight:bold; + padding-left:0; +} +a.list-group-item { + -moz-border-radius: 0; + border-radius: 0; + border:0; + background-color:white; + padding:3px 2px 3px 10px; + border-left:2pt white solid; +} +a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.active:focus { + -moz-border-radius: 0; + border-radius: 0; + font-weight: bold; + color:black; + background-color:#F5F5F5; + border-left:2pt blue solid; +} + +a.list-group-item:hover { + -moz-border-radius: 0; + border-radius: 0; + border-left:2pt blue solid; +} +a.list-group-item p.list-group-item-text { + -moz-border-radius: 0; + border-radius: 0; + font-size:9pt; + font-style:italic; + font-weight: normal; + color: black !important; +} + +span.sl-resources { + font-size:9pt; + color:gray; +} +a.sl-resources, a.sl-resources:hover { + font-size:9pt; + border:0; + padding:2px 4px; + -moz-border-radius: 4px; + border-radius: 4px; + width:125px; + text-align: center; +} +a.sl-resources.active, a.sl-resources.active:hover, a.sl-resources.active:focus { + border:0; + -moz-border-radius: 4px; + border-radius: 4px; +} + +div#slice-info { + margin-top:25px; +} +div#slice-info table { + width:100%; + margin:0 auto; +} +div#slice-info table td:first-child { + text-align:right; + font-weight:bold; + padding-right:15px; +} +div#slice-info td { + padding:5px; +} + +/* SLICE VIEW sections */ +.slice-sections, .slice-pending { + margin:0; + padding:0; +} +.slice-sections ul, .slice-pending ul { + margin:0; + padding:0; +} +.slice-pending ul { + width:400px; + margin:0 auto 15px auto; +} +.slice-sections li { + text-align:left; + margin:0; + padding:0; +} +.slice-pending li { + padding-right:15px; +} +.slice-sections li a, .slice-pending li a { + font-size:14px; + color:black; + padding:0; +} +.slice-sections li.active a, .slice-pending li.active a { + color:#201E62; + background-color:#EFEFEF; + text-decoration:underline; + padding:0; +} +.slice-sections ul.nav-pills li a:hover, .slice-pending ul.nav-pills li a:hover { + text-decoration:underline; + background-color:#EFEFEF; + color:black; +} +.slice-sections ul.nav-pills li.active, .slice-pending ul.nav-pills li.active { + +} +.slice-sections li:first-child, .slice-sections li:first-child a { + color:#201E62; + font-weight:bold; +} +.slice-experiment { + text-align:right; + padding:0; +} +.slice-experiment button { + margin:3px 0 0 0; + background-color:#CC4125; + color:white; +} + +.slice-pending { +} +.slice-pending button { + font-size:9pt; + margin:-2px 0 0 0; + padding:3px 5px; +} +.slice-pending button.apply { +} +.slice-pending button.clear { +} +tr.active, tr.active td { + background-color:#FFFFCC !important; +} +div.dataTables_filter label{ + float:left; + width:400px; +} + +/* HEADER */ + + +.header { + -moz-box-shadow: 0 0 1px rgba(82,82,82,0.6); + -webkit-box-shadow: 0 0 1px rgba(82,82,82,0.6); + box-shadow: 0 0 1px rgba(82,82,82,0.6); + height:61px; + background-color:white; + margin-bottom: 35px; +} + +div.navigation { + +} +div.navigation ul { + margin:26px 0 0 0; + padding:0; + display: inline-block; + list-style-type: none; + white-space: nowrap; +} + +div.navigation li { + color:#0C0047; + font-family:open_sansbold, sans-serif; + font-size:9pt; + font-weight:normal; + line-height:0.8em; + letter-spacing:0.4pt; + list-style:none; + float:left; + padding:0 15px; + margin:0; + text-transform:uppercase; +} +div.navigation li:hover { +} +div.navigation li a { + color:#0C0047; +} +div.navigation li a:hover, div.navigation li a.current { + color:#760073; + text-decoration:none; +} + +div.navigation li:last-child { + margin-right:0; +} + + +div.navigation .dropdown-menu { + color:black; + -moz-box-shadow: 1px 1px 0px 0 rgba(58, 48, 100,0.8); + -webkit-box-shadow: 1px 1px 0px 0 rgba(58, 48, 100,0.8); + box-shadow: 1px 1px 0px 0 rgba(58, 48, 100,0.8); + border-radius:2px; + padding:0 5px 5px 5px; + margin-top:5px; + margin-left:20px; +} +div.navigation .dropdown-menu ul { + margin:0; + padding:15px 5px 5px 5px; + display:list-item; +} +div.navigation .dropdown-menu li { + margin:0 10px 0 0; + padding:0 0 8px 0; + display:list-item; + float:none; + text-transform: none; +} + +div.navigation .dropdown-menu a { + font-family:Helvetica,sans-serif; + font-size:10pt; + color:black; +} +div.navigation .dropdown-menu li.title { + margin-bottom:10px; +} +div.navigation .dropdown-menu li.title a { + font-family:open_sansbold, sans-serif; +} + + +div.navigation .dropdown-menu li:first-child { + border-bottom:1px solid white; + padding-bottom:5px; + +} + +div.secondary { + text-align:right; +} + +div.secondary ul { + margin:6px 0 0 0; + padding:0; +} + +div.secondary li { + font-size:9pt; + display:inline; + list-style:none; + margin:0px; + padding:0; + margin-right:15px; + color:#747474; + letter-spacing:0.4px; +} +div.secondary li:last-child { + margin-right:0; +} +div.secondary li a { + color:#747474; +} +div.secondary li a:hover { + text-decoration:none; +} +div.secondary .button { + width:300px; + margin-top:15px; +} +div.secondary .account { + margin-top:10px; + padding:0; + font-size:9pt; + color:gray; + text-align:right; +} +div.secondary .account span { + font-size:8pt; +} +div.secondary .account a { + color:black; +} +div.home { + font-size:11pt; + line-height:1.2em; + letter-spacing:0.3pt; + min-height:500px; + background-image: url('../img/optical_fibre.jpg'); + background-repeat:no-repeat; + background-size:cover; + background-position:center top; + background-color:#013ADF; + padding:100px 0; +} +div.home h2 { + color:white; + line-height:1.2em; + font-size:18pt; +} +div.home h3 { + color:white; + line-height:1.4em; +} +div.dashboard { + text-align:center; +} +div.dashboard div { + margin:25px 0; +} +div.dashboard ul { + text-align:left; + margin-left:24px; + list-style:none; +} +div.registration-form { + padding-top:150px; + text-align:center; +} + +.login-form input { + width:320px; +} +.login-form input[type=submit] { + width:108px; +} + +.login-submit { + vertical-align:middle; + padding:0; +} +.lost-password { + font-size:10pt; + color:black; + text-align:right; + padding:0px; +} +.lost-password a { + color:white; + text-shadow:0.5px 0.5px black; +} +.login-signup { + font-size:12pt; + color:white; + text-shadow:0.5px 0.5px black; + margin-top:45px; + padding:5px 0 0 4px; + +} +.login-signup a { + color:white; + text-shadow:0.5px 0.5px black; + padding-bottom:2px; + border-bottom:2pt solid white; +} +.login-signup a:hover { + text-decoration:none; +} +.login-signup button { + padding:8px; + border:0; + border-bottom:2px solid #540086; + background-color:#302562; + color:white; + width:100px; + border-radius:5px; + font-size:12pt; +} +div.slogan { + text-align:center; + color:white; + padding-top:60px; + text-shadow: 1px 1px #013540; +} diff --git a/portal/static/css/onelab_edelberto.css b/portal/static/css/onelab_edelberto.css new file mode 100644 index 00000000..797d79c4 --- /dev/null +++ b/portal/static/css/onelab_edelberto.css @@ -0,0 +1,457 @@ +/* @override unfold/static/css/plugin.css */ + +/*-------------------------------- MARKO'S STYLES -----*/ + +/* GENERAL */ + +a, a:visited { + color: rgb(13, 187, 255) !important; + text-decoration: none !important; +} + +a:hover { + color: blue !important; + text-decoration: none !important; +} + +.container { + /*padding: 0 !important;*/ + padding-top: 60px !important; + color: #fff; +/* background: url(http://new.fit-equipex.fr/images/background.jpg) no-repeat; */ + background-color: rgb(237, 241, 243); + margin: 0; + width: 100%; + max-width: 100%; + min-height: 100%; + height: 100%; +} + +.container h1, .container h2 { + color: #fff !important; +} + +.navbar-brand { + padding: 5px !important; +} + +.nav { + padding-top: 30px !important; + padding-left: 20px !important; +} + +.navbar-default { + background-color: #fff !important; +} + +p.login-status { + color: blue !important; +} + +div.plugin-outline-complete, +div.plugin-outline-body { + border: 0px solid; + border-radius: 0; + border-color: #ccc; + -webkit-transition: padding 200ms ease-out; + -moz-transition: padding 200ms ease-out; + -o-transition: padding 200ms ease-out; + transition: padding 0.2s ease-out; + padding: 20px; + margin: 0; +} +/* +div.plugin-outline-complete:hover, +div.plugin-outline-body:hover { + padding: 80px 80px 120px 80px; +} +*/ +a.plugin-tooltip { + font-size: 130%; + font-style: normal; + font-weight: bold; + padding: 5px; + color: #333; + font-family: Ubuntu, Arial, sans-serif; + text-transform: uppercase; +} + +a.plugin-tooltip:hover { + color: #fff; + text-decoration: none; +} + + + +/* LIST VIEW */ +div.well-lg { + background-color: rgba(168, 32, 202, 0.5) !important; +} +div.onelab-title { + background-color: #3A5FCD !important; + /*background-color: rgba(168, 32, 202, 0.5) !important;*/ +} +div.well { + /*background-color: rgba(0, 0, 0, 0.5) !important;*/ + background-color: #fff !important; + color: rgb(37, 37, 37) !important; +} +h2.well.well-lg { + border-radius:0; + border: 0; + font-family: Ubuntu, arial, sans-serif; + /* text-transform: ; */ + font-weight: normal; + font-size: 40px; + /* color: #30196d; */ + color: white; + margin-bottom: 0px; + margin-top: 0; + padding: 40px; + opacity: 1; + text-align: center; + background-color: #30196d; +} + +#complete-resources { +/* background-color: #92f79e !important; */ + background-color: #B8B2FF !important; +} + +#complete-filters { +/* background-color: #4af25d; */ + background-color: #add7ff; +} + +#complete-users { +/* background-color: #ff7394 !important; */ + background-color: #add7ff !important; +} +/* +#complete-measurements { + background-color: !important; +} +*/ +#complete-pending { +/* background-color: #add7ff !important; */ + background-color: #B8B2FF !important; + +} + +#complete-customize-resources { + background-color: #efdfdf; +} + +#complete-msgs-pre { + background-color: #ccc; +} + +#complete-resources, +#complete-filters, +#complete-users, +#complete-measurements, +#complete-pending, +#complete-customize-resources, +#complete-msgs-pre { + opacity: 1; + text-align: center; + color: #333; +} + +#complete-resources:hover, +#complete-filters:hover, +#complete-users:hover, +#complete-measurements:hover, +#complete-pending:hover, +#complete-customize-resources:hover, +#complete-msgs-pre:hover { + opacity: 1; +} + +.nav.nav-tabs { + font-family: Ubuntu, Arial, sans-serif; + border: 0 !important; + border-bottom: 3px solid #fff !important; + margin-bottom: 40px; +} + +.nav.nav-tabs li.active a { + color: #572bc9; + border-left: 0px solid #572bc9; + border-top: 0px solid #572bc9; + border-right: 0px solid #572bc9; +} + +.nav.nav-tabs li a { + color: #333; + border: 0 !important; + margin-right: 5px; +} + +.nav.nav-tabs li a:hover { + color: #333; + background: #572bc9; + color: #fff; + border: 0 !important; +} + + + +/* if window enlarged wider than background picture */ +body { + background: rgb(237, 241, 243) !important; + /*background: black !important;*/ +} + +/* TOPMENU.CSS */ + +/* Thierry : turning this off +body { + *//* background: #30196d !important; *//* + background: black !important; + padding-top: 60px; + padding-bottom: 0px; +} +Thierry */ + +/* Thierry : turning this off +div.topmenu { + padding-top: 0px; + font-family: Ubuntu, Arial, sans-serif; + font-weight: bold; + background: #fff; + -webkit-box-shadow: 0px 10px 10px rgba(50, 50, 50, 0.44); + -moz-box-shadow: 0px 10px 10px rgba(50, 50, 50, 0.44); + box-shadow: 0px 10px 10px rgba(50, 50, 50, 0.44); +} +Thierry */ + +/* Thierry : turning this off +.navbar-nav li a, +.navbar-nav li.other a { + padding-top: 25px; + padding-bottom: 20px; +} +Thierry */ + +.navbar-nav li a:hover { + color: #572bc9 !important; +} + +.navbar-nav li.active a { + background: #eee !important; +} + +/* Thierry : turning this off +ul.logged-in { + padding-top: 25px; +} +Thierry */ +button.logged-in { + font-size: 1em; + font-weight: bold; + margin-left: 5px; + margin-top: -5px; + background: #572bc9; + border: 2px solid #572bc9; + color: #eee; + padding: 5px 15px; + border-radius:5px; +} + +button.logged-in:hover { + /* background: #4af25d; */ + background: #ff7394; + border: 2px solid #ff7394; + color: #333; +} +li.username { + margin-bottom: 10px; + font-size: 0.8em; + text-transform: none; + font-weight: normal; + color: #999; +} + + +/* BOOTSTRAP */ + + +ul.pagination li a { + /* background: ; */ + color: #572bc9; + font-family: Ubuntu, Arial, sans-serif; +} + +ul.pagination li.active a { + background: #572bc9; + border: 1px solid #572bc9; +} + +.btn.btn-default { + background: #572bc9; + color: #ccc; + font-family: Ubuntu, Arial, sans-serif; + font-weight: bold; + border: 0px; +} + +.btn.btn-default:hover { + /* background: #4af25d; */ + background: #ff7394; + color: #333; + font-family: Ubuntu, Arial, sans-serif; + font-weight: bold; + border: 0px; +} + +input { + border-radius: 3px; + border: none; + border: 1px solid #ccc; +} + + +div.dataTables_length label, +div.dataTables_filter label, +div.dataTables_info { + font-family: Ubuntu, Arial, sans-serif !important; +} + + + + +/* QUERYTABLE */ + +div.QueryTable table.dataTable th { + font: bold 12px/22px Ubuntu, Arial, sans-serif; + color: #333 !important; + border-right: 0px solid #333 !important; + border-bottom: 0px solid #C1DAD7 !important; + border-top: 0px solid #C1DAD7 !important; + letter-spacing: 1px; + text-transform: uppercase; + text-align: left; + padding: 8px 12px 4px 20px; + vertical-align:middle; + background: url('../img/tablesort-header.png') no-repeat !important; +} + +div.QueryTable table.dataTable td, div.QueryTable table.dataTable textarea, div.QueryTable table.dataTable input [type="text"] { + font: normal 12px Ubuntu, Arial, Helvetica, sans-serif; + border-right: 0px solid #fff !important; + border-bottom: 1px solid #fff !important; +} + +div.QueryTable table.dataTable thead { + background: url('../img/tablesort-header.png') repeat-x !important; + background-color: #caebea; +} + +div.QueryTable table.dataTable tfoot { + background: url('../img/tablesort-header.png') repeat-x !important; + /* background-color: # !important; */ +} + + +/* QUERY EDITOR */ + +table.query-editor { + margin: 40px auto !important; + clear: both; + /* width: 80%;*/ + width: 100% !important; + font-family: Ubuntu; +} + +.query-editor-spacer, +.plugin.QueryUpdater, +/* Thierry : turning this off +.plugin.Tabs +Thierry */ +{ + margin-top: 60px !important; +} + +table.query-editor td { + padding: 5px 5px !important; + font: normal 12px Ubuntu, Arial, sans-serif !important; +} + + + +/* DASHBOARD */ + +#ms-dashboard-profile, +#ms-dashboard-testbeds, +#ms-dashboard-slices { + -webkit-transition: all 50ms ease-out; + -moz-transition: all 50ms ease-out; + -o-transition: all 50ms ease-out; + transition: all 0.05s ease-out; + padding-top: 140px; + padding-bottom: 60px; + margin-top: 60px; + color: #fff; + font-family: Ubuntu, Arial, sans-serif; + text-align: center; + +} + +#ms-dashboard-profile:hover, +#ms-dashboard-testbeds:hover, +#ms-dashboard-slices:hover { + margin-top: 65px; +} + +#ms-dashboard-profile { + background: url("../img/icon_users_color.png") top center no-repeat; +} + +#ms-dashboard-testbeds { + background: url("../img/icon_testbed_color.png") top center no-repeat; +} + +#ms-dashboard-slices { + background: url("../img/icon_slices_color.png") top center no-repeat; +} + +.ms-dashboard-content ul { + list-style-type: none !important; + padding-left: 0; + text-align: center !important; +} + +.ms-dashboard-content { + padding: 0 !important; +} + +.ms-dashboard-content a { + color: #ff7394 !important; +/* color: #ff0099 !important; */ +} + +.ms-dashboard-content a:hover { + color: white !important; +} +.ms-dashboard-caption h2 { + font-family: Ubuntu, Arial, sans-serif; + border-bottom: 0 !important; + text-transform: uppercase; +} + +#ms-dashboard-profile>div.ms-dashboard-caption { + background: no-repeat url(#) !important; + padding-left: 0 !important; +} + +#ms-dashboard-testbeds>div.ms-dashboard-caption { + background: no-repeat url(#) !important; + padding-left: 0 !important; +} + +#ms-dashboard-slices>div.ms-dashboard-caption { + background: no-repeat url(#) !important; + padding-left: 0 !important; +} + diff --git a/portal/static/img/fibre-logo.gif b/portal/static/img/fibre-logo.gif new file mode 100644 index 00000000..e215771d Binary files /dev/null and b/portal/static/img/fibre-logo.gif differ diff --git a/portal/static/img/fibre/br.png b/portal/static/img/fibre/br.png new file mode 100644 index 00000000..4225053b Binary files /dev/null and b/portal/static/img/fibre/br.png differ diff --git a/portal/static/img/fibre/cafe.jpg b/portal/static/img/fibre/cafe.jpg new file mode 100644 index 00000000..c73e6f1a Binary files /dev/null and b/portal/static/img/fibre/cafe.jpg differ diff --git a/portal/static/img/fibre/eu.png b/portal/static/img/fibre/eu.png new file mode 100644 index 00000000..03f1d0f0 Binary files /dev/null and b/portal/static/img/fibre/eu.png differ diff --git a/portal/static/img/fibre/icon_authority_color.png b/portal/static/img/fibre/icon_authority_color.png new file mode 100644 index 00000000..342a1dd3 Binary files /dev/null and b/portal/static/img/fibre/icon_authority_color.png differ diff --git a/portal/static/img/fibre/icon_slices.png b/portal/static/img/fibre/icon_slices.png new file mode 100644 index 00000000..9527461c Binary files /dev/null and b/portal/static/img/fibre/icon_slices.png differ diff --git a/portal/static/img/fibre/icon_support.png b/portal/static/img/fibre/icon_support.png new file mode 100644 index 00000000..54bce1b7 Binary files /dev/null and b/portal/static/img/fibre/icon_support.png differ diff --git a/portal/static/img/fibre/icon_testbed_color.png b/portal/static/img/fibre/icon_testbed_color.png new file mode 100644 index 00000000..f3c55e46 Binary files /dev/null and b/portal/static/img/fibre/icon_testbed_color.png differ diff --git a/portal/static/img/fibre/icon_user_color.png b/portal/static/img/fibre/icon_user_color.png new file mode 100644 index 00000000..e88d5594 Binary files /dev/null and b/portal/static/img/fibre/icon_user_color.png differ diff --git a/portal/static/img/fibre/icones.zip b/portal/static/img/fibre/icones.zip new file mode 100644 index 00000000..bab607b5 Binary files /dev/null and b/portal/static/img/fibre/icones.zip differ diff --git a/portal/static/img/icon_authority_color.png~HEAD b/portal/static/img/icon_authority_color.png~HEAD new file mode 100644 index 00000000..342a1dd3 Binary files /dev/null and b/portal/static/img/icon_authority_color.png~HEAD differ diff --git a/portal/static/img/icon_slices.png~HEAD b/portal/static/img/icon_slices.png~HEAD new file mode 100644 index 00000000..9527461c Binary files /dev/null and b/portal/static/img/icon_slices.png~HEAD differ diff --git a/portal/static/img/icon_support.png~HEAD b/portal/static/img/icon_support.png~HEAD new file mode 100644 index 00000000..54bce1b7 Binary files /dev/null and b/portal/static/img/icon_support.png~HEAD differ diff --git a/portal/static/img/icon_testbed_color.png~HEAD b/portal/static/img/icon_testbed_color.png~HEAD new file mode 100644 index 00000000..f3c55e46 Binary files /dev/null and b/portal/static/img/icon_testbed_color.png~HEAD differ diff --git a/portal/static/img/icon_user_color.png~HEAD b/portal/static/img/icon_user_color.png~HEAD new file mode 100644 index 00000000..e88d5594 Binary files /dev/null and b/portal/static/img/icon_user_color.png~HEAD differ diff --git a/portal/static/img/optical_fibre.jpg b/portal/static/img/optical_fibre.jpg new file mode 100644 index 00000000..a62eb25f Binary files /dev/null and b/portal/static/img/optical_fibre.jpg differ diff --git a/portal/static/img/original-backup/authority-icon.png b/portal/static/img/original-backup/authority-icon.png new file mode 100644 index 00000000..bde603ad Binary files /dev/null and b/portal/static/img/original-backup/authority-icon.png differ diff --git a/portal/static/img/original-backup/experiments.png b/portal/static/img/original-backup/experiments.png new file mode 100644 index 00000000..cd97086b Binary files /dev/null and b/portal/static/img/original-backup/experiments.png differ diff --git a/portal/static/img/original-backup/f4f-logo.png b/portal/static/img/original-backup/f4f-logo.png new file mode 100644 index 00000000..8a72315d Binary files /dev/null and b/portal/static/img/original-backup/f4f-logo.png differ diff --git a/portal/static/img/original-backup/marker1.png b/portal/static/img/original-backup/marker1.png new file mode 100644 index 00000000..5f29aea8 Binary files /dev/null and b/portal/static/img/original-backup/marker1.png differ diff --git a/portal/static/img/original-backup/marker2.png b/portal/static/img/original-backup/marker2.png new file mode 100644 index 00000000..9dce718a Binary files /dev/null and b/portal/static/img/original-backup/marker2.png differ diff --git a/portal/static/img/original-backup/ques_icon.png b/portal/static/img/original-backup/ques_icon.png new file mode 100644 index 00000000..4113c121 Binary files /dev/null and b/portal/static/img/original-backup/ques_icon.png differ diff --git a/portal/static/img/original-backup/resource-icon.png b/portal/static/img/original-backup/resource-icon.png new file mode 100644 index 00000000..919bdbe5 Binary files /dev/null and b/portal/static/img/original-backup/resource-icon.png differ diff --git a/portal/static/img/original-backup/slice-icon.png b/portal/static/img/original-backup/slice-icon.png new file mode 100644 index 00000000..9e8747bb Binary files /dev/null and b/portal/static/img/original-backup/slice-icon.png differ diff --git a/portal/static/img/original-backup/testbeds.png b/portal/static/img/original-backup/testbeds.png new file mode 100644 index 00000000..ed9aa0e4 Binary files /dev/null and b/portal/static/img/original-backup/testbeds.png differ diff --git a/portal/static/img/original-backup/user-icon.png b/portal/static/img/original-backup/user-icon.png new file mode 100644 index 00000000..88f6bf82 Binary files /dev/null and b/portal/static/img/original-backup/user-icon.png differ diff --git a/portal/static/img/original-backup/user.png b/portal/static/img/original-backup/user.png new file mode 100644 index 00000000..fa72bdd2 Binary files /dev/null and b/portal/static/img/original-backup/user.png differ diff --git a/portal/static/js/jquery.qtip.min.js b/portal/static/js/jquery.qtip.min.js index 3ae7bbe3..dc74d29d 100644 --- a/portal/static/js/jquery.qtip.min.js +++ b/portal/static/js/jquery.qtip.min.js @@ -1,4 +1,4 @@ /* qTip2 v2.2.0 tips modal viewport svg imagemap ie6 | qtip2.com | Licensed MIT, GPL | Thu Nov 21 2013 20:34:59 */ (function(t,e,i){(function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):jQuery&&!jQuery.fn.qtip&&t(jQuery)})(function(s){"use strict";function o(t,e,i,o){this.id=i,this.target=t,this.tooltip=E,this.elements={target:t},this._id=X+"-"+i,this.timers={img:{}},this.options=e,this.plugins={},this.cache={event:{},target:s(),disabled:k,attr:o,onTooltip:k,lastClass:""},this.rendered=this.destroyed=this.disabled=this.waiting=this.hiddenDuringWait=this.positioning=this.triggering=k}function n(t){return t===E||"object"!==s.type(t)}function r(t){return!(s.isFunction(t)||t&&t.attr||t.length||"object"===s.type(t)&&(t.jquery||t.then))}function a(t){var e,i,o,a;return n(t)?k:(n(t.metadata)&&(t.metadata={type:t.metadata}),"content"in t&&(e=t.content,n(e)||e.jquery||e.done?e=t.content={text:i=r(e)?k:e}:i=e.text,"ajax"in e&&(o=e.ajax,a=o&&o.once!==k,delete e.ajax,e.text=function(t,e){var n=i||s(this).attr(e.options.content.attr)||"Loading...",r=s.ajax(s.extend({},o,{context:e})).then(o.success,E,o.error).then(function(t){return t&&a&&e.set("content.text",t),t},function(t,i,s){e.destroyed||0===t.status||e.set("content.text",i+": "+s)});return a?n:(e.set("content.text",n),r)}),"title"in e&&(n(e.title)||(e.button=e.title.button,e.title=e.title.text),r(e.title||k)&&(e.title=k))),"position"in t&&n(t.position)&&(t.position={my:t.position,at:t.position}),"show"in t&&n(t.show)&&(t.show=t.show.jquery?{target:t.show}:t.show===W?{ready:W}:{event:t.show}),"hide"in t&&n(t.hide)&&(t.hide=t.hide.jquery?{target:t.hide}:{event:t.hide}),"style"in t&&n(t.style)&&(t.style={classes:t.style}),s.each(R,function(){this.sanitize&&this.sanitize(t)}),t)}function h(t,e){for(var i,s=0,o=t,n=e.split(".");o=o[n[s++]];)n.length>s&&(i=o);return[i||t,n.pop()]}function l(t,e){var i,s,o;for(i in this.checks)for(s in this.checks[i])(o=RegExp(s,"i").exec(t))&&(e.push(o),("builtin"===i||this.plugins[i])&&this.checks[i][s].apply(this.plugins[i]||this,e))}function c(t){return G.concat("").join(t?"-"+t+" ":" ")}function d(i){return i&&{type:i.type,pageX:i.pageX,pageY:i.pageY,target:i.target,relatedTarget:i.relatedTarget,scrollX:i.scrollX||t.pageXOffset||e.body.scrollLeft||e.documentElement.scrollLeft,scrollY:i.scrollY||t.pageYOffset||e.body.scrollTop||e.documentElement.scrollTop}||{}}function p(t,e){return e>0?setTimeout(s.proxy(t,this),e):(t.call(this),i)}function u(t){return this.tooltip.hasClass(ee)?k:(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this.timers.show=p.call(this,function(){this.toggle(W,t)},this.options.show.delay),i)}function f(t){if(this.tooltip.hasClass(ee))return k;var e=s(t.relatedTarget),i=e.closest(U)[0]===this.tooltip[0],o=e[0]===this.options.show.target[0];if(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this!==e[0]&&"mouse"===this.options.position.target&&i||this.options.hide.fixed&&/mouse(out|leave|move)/.test(t.type)&&(i||o))try{t.preventDefault(),t.stopImmediatePropagation()}catch(n){}else this.timers.hide=p.call(this,function(){this.toggle(k,t)},this.options.hide.delay,this)}function g(t){return this.tooltip.hasClass(ee)||!this.options.hide.inactive?k:(clearTimeout(this.timers.inactive),this.timers.inactive=p.call(this,function(){this.hide(t)},this.options.hide.inactive),i)}function m(t){this.rendered&&this.tooltip[0].offsetWidth>0&&this.reposition(t)}function v(t,i,o){s(e.body).delegate(t,(i.split?i:i.join(he+" "))+he,function(){var t=T.api[s.attr(this,H)];t&&!t.disabled&&o.apply(t,arguments)})}function y(t,i,n){var r,h,l,c,d,p=s(e.body),u=t[0]===e?p:t,f=t.metadata?t.metadata(n.metadata):E,g="html5"===n.metadata.type&&f?f[n.metadata.name]:E,m=t.data(n.metadata.name||"qtipopts");try{m="string"==typeof m?s.parseJSON(m):m}catch(v){}if(c=s.extend(W,{},T.defaults,n,"object"==typeof m?a(m):E,a(g||f)),h=c.position,c.id=i,"boolean"==typeof c.content.text){if(l=t.attr(c.content.attr),c.content.attr===k||!l)return k;c.content.text=l}if(h.container.length||(h.container=p),h.target===k&&(h.target=u),c.show.target===k&&(c.show.target=u),c.show.solo===W&&(c.show.solo=h.container.closest("body")),c.hide.target===k&&(c.hide.target=u),c.position.viewport===W&&(c.position.viewport=h.container),h.container=h.container.eq(0),h.at=new z(h.at,W),h.my=new z(h.my),t.data(X))if(c.overwrite)t.qtip("destroy",!0);else if(c.overwrite===k)return k;return t.attr(Y,i),c.suppress&&(d=t.attr("title"))&&t.removeAttr("title").attr(se,d).attr("title",""),r=new o(t,c,i,!!l),t.data(X,r),t.one("remove.qtip-"+i+" removeqtip.qtip-"+i,function(){var t;(t=s(this).data(X))&&t.destroy(!0)}),r}function b(t){return t.charAt(0).toUpperCase()+t.slice(1)}function w(t,e){var s,o,n=e.charAt(0).toUpperCase()+e.slice(1),r=(e+" "+be.join(n+" ")+n).split(" "),a=0;if(ye[e])return t.css(ye[e]);for(;s=r[a++];)if((o=t.css(s))!==i)return ye[e]=s,o}function _(t,e){return Math.ceil(parseFloat(w(t,e)))}function x(t,e){this._ns="tip",this.options=e,this.offset=e.offset,this.size=[e.width,e.height],this.init(this.qtip=t)}function q(t,e){this.options=e,this._ns="-modal",this.init(this.qtip=t)}function C(t){this._ns="ie6",this.init(this.qtip=t)}var T,j,z,M,I,W=!0,k=!1,E=null,S="x",L="y",A="width",B="height",D="top",F="left",O="bottom",P="right",N="center",$="flipinvert",V="shift",R={},X="qtip",Y="data-hasqtip",H="data-qtip-id",G=["ui-widget","ui-tooltip"],U="."+X,Q="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),J=X+"-fixed",K=X+"-default",Z=X+"-focus",te=X+"-hover",ee=X+"-disabled",ie="_replacedByqTip",se="oldtitle",oe={ie:function(){for(var t=3,i=e.createElement("div");(i.innerHTML="")&&i.getElementsByTagName("i")[0];);return t>4?t:0/0}(),iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||k};j=o.prototype,j._when=function(t){return s.when.apply(s,t)},j.render=function(t){if(this.rendered||this.destroyed)return this;var e,i=this,o=this.options,n=this.cache,r=this.elements,a=o.content.text,h=o.content.title,l=o.content.button,c=o.position,d=("."+this._id+" ",[]);return s.attr(this.target[0],"aria-describedby",this._id),this.tooltip=r.tooltip=e=s("
",{id:this._id,"class":[X,K,o.style.classes,X+"-pos-"+o.position.my.abbrev()].join(" "),width:o.style.width||"",height:o.style.height||"",tracking:"mouse"===c.target&&c.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":k,"aria-describedby":this._id+"-content","aria-hidden":W}).toggleClass(ee,this.disabled).attr(H,this.id).data(X,this).appendTo(c.container).append(r.content=s("
",{"class":X+"-content",id:this._id+"-content","aria-atomic":W})),this.rendered=-1,this.positioning=W,h&&(this._createTitle(),s.isFunction(h)||d.push(this._updateTitle(h,k))),l&&this._createButton(),s.isFunction(a)||d.push(this._updateContent(a,k)),this.rendered=W,this._setWidget(),s.each(R,function(t){var e;"render"===this.initialize&&(e=this(i))&&(i.plugins[t]=e)}),this._unassignEvents(),this._assignEvents(),this._when(d).then(function(){i._trigger("render"),i.positioning=k,i.hiddenDuringWait||!o.show.ready&&!t||i.toggle(W,n.event,k),i.hiddenDuringWait=k}),T.api[this.id]=this,this},j.destroy=function(t){function e(){if(!this.destroyed){this.destroyed=W;var t=this.target,e=t.attr(se);this.rendered&&this.tooltip.stop(1,0).find("*").remove().end().remove(),s.each(this.plugins,function(){this.destroy&&this.destroy()}),clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this._unassignEvents(),t.removeData(X).removeAttr(H).removeAttr(Y).removeAttr("aria-describedby"),this.options.suppress&&e&&t.attr("title",e).removeAttr(se),this._unbind(t),this.options=this.elements=this.cache=this.timers=this.plugins=this.mouse=E,delete T.api[this.id]}}return this.destroyed?this.target:(t===W&&"hide"!==this.triggering||!this.rendered?e.call(this):(this.tooltip.one("tooltiphidden",s.proxy(e,this)),!this.triggering&&this.hide()),this.target)},M=j.checks={builtin:{"^id$":function(t,e,i,o){var n=i===W?T.nextid:i,r=X+"-"+n;n!==k&&n.length>0&&!s("#"+r).length?(this._id=r,this.rendered&&(this.tooltip[0].id=this._id,this.elements.content[0].id=this._id+"-content",this.elements.title[0].id=this._id+"-title")):t[e]=o},"^prerender":function(t,e,i){i&&!this.rendered&&this.render(this.options.show.ready)},"^content.text$":function(t,e,i){this._updateContent(i)},"^content.attr$":function(t,e,i,s){this.options.content.text===this.target.attr(s)&&this._updateContent(this.target.attr(i))},"^content.title$":function(t,e,s){return s?(s&&!this.elements.title&&this._createTitle(),this._updateTitle(s),i):this._removeTitle()},"^content.button$":function(t,e,i){this._updateButton(i)},"^content.title.(text|button)$":function(t,e,i){this.set("content."+e,i)},"^position.(my|at)$":function(t,e,i){"string"==typeof i&&(t[e]=new z(i,"at"===e))},"^position.container$":function(t,e,i){this.rendered&&this.tooltip.appendTo(i)},"^show.ready$":function(t,e,i){i&&(!this.rendered&&this.render(W)||this.toggle(W))},"^style.classes$":function(t,e,i,s){this.rendered&&this.tooltip.removeClass(s).addClass(i)},"^style.(width|height)":function(t,e,i){this.rendered&&this.tooltip.css(e,i)},"^style.widget|content.title":function(){this.rendered&&this._setWidget()},"^style.def":function(t,e,i){this.rendered&&this.tooltip.toggleClass(K,!!i)},"^events.(render|show|move|hide|focus|blur)$":function(t,e,i){this.rendered&&this.tooltip[(s.isFunction(i)?"":"un")+"bind"]("tooltip"+e,i)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){if(this.rendered){var t=this.options.position;this.tooltip.attr("tracking","mouse"===t.target&&t.adjust.mouse),this._unassignEvents(),this._assignEvents()}}}},j.get=function(t){if(this.destroyed)return this;var e=h(this.options,t.toLowerCase()),i=e[0][e[1]];return i.precedance?i.string():i};var ne=/^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,re=/^prerender|show\.ready/i;j.set=function(t,e){if(this.destroyed)return this;var o,n=this.rendered,r=k,c=this.options;return this.checks,"string"==typeof t?(o=t,t={},t[o]=e):t=s.extend({},t),s.each(t,function(e,o){if(n&&re.test(e))return delete t[e],i;var a,l=h(c,e.toLowerCase());a=l[0][l[1]],l[0][l[1]]=o&&o.nodeType?s(o):o,r=ne.test(e)||r,t[e]=[l[0],l[1],o,a]}),a(c),this.positioning=W,s.each(t,s.proxy(l,this)),this.positioning=k,this.rendered&&this.tooltip[0].offsetWidth>0&&r&&this.reposition("mouse"===c.position.target?E:this.cache.event),this},j._update=function(t,e){var i=this,o=this.cache;return this.rendered&&t?(s.isFunction(t)&&(t=t.call(this.elements.target,o.event,this)||""),s.isFunction(t.then)?(o.waiting=W,t.then(function(t){return o.waiting=k,i._update(t,e)},E,function(t){return i._update(t,e)})):t===k||!t&&""!==t?k:(t.jquery&&t.length>0?e.empty().append(t.css({display:"block",visibility:"visible"})):e.html(t),this._waitForContent(e).then(function(t){t.images&&t.images.length&&i.rendered&&i.tooltip[0].offsetWidth>0&&i.reposition(o.event,!t.length)}))):k},j._waitForContent=function(t){var e=this.cache;return e.waiting=W,(s.fn.imagesLoaded?t.imagesLoaded():s.Deferred().resolve([])).done(function(){e.waiting=k}).promise()},j._updateContent=function(t,e){this._update(t,this.elements.content,e)},j._updateTitle=function(t,e){this._update(t,this.elements.title,e)===k&&this._removeTitle(k)},j._createTitle=function(){var t=this.elements,e=this._id+"-title";t.titlebar&&this._removeTitle(),t.titlebar=s("
",{"class":X+"-titlebar "+(this.options.style.widget?c("header"):"")}).append(t.title=s("
",{id:e,"class":X+"-title","aria-atomic":W})).insertBefore(t.content).delegate(".qtip-close","mousedown keydown mouseup keyup mouseout",function(t){s(this).toggleClass("ui-state-active ui-state-focus","down"===t.type.substr(-4))}).delegate(".qtip-close","mouseover mouseout",function(t){s(this).toggleClass("ui-state-hover","mouseover"===t.type)}),this.options.content.button&&this._createButton()},j._removeTitle=function(t){var e=this.elements;e.title&&(e.titlebar.remove(),e.titlebar=e.title=e.button=E,t!==k&&this.reposition())},j.reposition=function(i,o){if(!this.rendered||this.positioning||this.destroyed)return this;this.positioning=W;var n,r,a=this.cache,h=this.tooltip,l=this.options.position,c=l.target,d=l.my,p=l.at,u=l.viewport,f=l.container,g=l.adjust,m=g.method.split(" "),v=h.outerWidth(k),y=h.outerHeight(k),b=0,w=0,_=h.css("position"),x={left:0,top:0},q=h[0].offsetWidth>0,C=i&&"scroll"===i.type,T=s(t),j=f[0].ownerDocument,z=this.mouse;if(s.isArray(c)&&2===c.length)p={x:F,y:D},x={left:c[0],top:c[1]};else if("mouse"===c)p={x:F,y:D},!z||!z.pageX||!g.mouse&&i&&i.pageX?i&&i.pageX||((!g.mouse||this.options.show.distance)&&a.origin&&a.origin.pageX?i=a.origin:(!i||i&&("resize"===i.type||"scroll"===i.type))&&(i=a.event)):i=z,"static"!==_&&(x=f.offset()),j.body.offsetWidth!==(t.innerWidth||j.documentElement.clientWidth)&&(r=s(e.body).offset()),x={left:i.pageX-x.left+(r&&r.left||0),top:i.pageY-x.top+(r&&r.top||0)},g.mouse&&C&&z&&(x.left-=(z.scrollX||0)-T.scrollLeft(),x.top-=(z.scrollY||0)-T.scrollTop());else{if("event"===c?i&&i.target&&"scroll"!==i.type&&"resize"!==i.type?a.target=s(i.target):i.target||(a.target=this.elements.target):"event"!==c&&(a.target=s(c.jquery?c:this.elements.target)),c=a.target,c=s(c).eq(0),0===c.length)return this;c[0]===e||c[0]===t?(b=oe.iOS?t.innerWidth:c.width(),w=oe.iOS?t.innerHeight:c.height(),c[0]===t&&(x={top:(u||c).scrollTop(),left:(u||c).scrollLeft()})):R.imagemap&&c.is("area")?n=R.imagemap(this,c,p,R.viewport?m:k):R.svg&&c&&c[0].ownerSVGElement?n=R.svg(this,c,p,R.viewport?m:k):(b=c.outerWidth(k),w=c.outerHeight(k),x=c.offset()),n&&(b=n.width,w=n.height,r=n.offset,x=n.position),x=this.reposition.offset(c,x,f),(oe.iOS>3.1&&4.1>oe.iOS||oe.iOS>=4.3&&4.33>oe.iOS||!oe.iOS&&"fixed"===_)&&(x.left-=T.scrollLeft(),x.top-=T.scrollTop()),(!n||n&&n.adjustable!==k)&&(x.left+=p.x===P?b:p.x===N?b/2:0,x.top+=p.y===O?w:p.y===N?w/2:0)}return x.left+=g.x+(d.x===P?-v:d.x===N?-v/2:0),x.top+=g.y+(d.y===O?-y:d.y===N?-y/2:0),R.viewport?(x.adjusted=R.viewport(this,x,l,b,w,v,y),r&&x.adjusted.left&&(x.left+=r.left),r&&x.adjusted.top&&(x.top+=r.top)):x.adjusted={left:0,top:0},this._trigger("move",[x,u.elem||u],i)?(delete x.adjusted,o===k||!q||isNaN(x.left)||isNaN(x.top)||"mouse"===c||!s.isFunction(l.effect)?h.css(x):s.isFunction(l.effect)&&(l.effect.call(h,this,s.extend({},x)),h.queue(function(t){s(this).css({opacity:"",height:""}),oe.ie&&this.style.removeAttribute("filter"),t()})),this.positioning=k,this):this},j.reposition.offset=function(t,i,o){function n(t,e){i.left+=e*t.scrollLeft(),i.top+=e*t.scrollTop()}if(!o[0])return i;var r,a,h,l,c=s(t[0].ownerDocument),d=!!oe.ie&&"CSS1Compat"!==e.compatMode,p=o[0];do"static"!==(a=s.css(p,"position"))&&("fixed"===a?(h=p.getBoundingClientRect(),n(c,-1)):(h=s(p).position(),h.left+=parseFloat(s.css(p,"borderLeftWidth"))||0,h.top+=parseFloat(s.css(p,"borderTopWidth"))||0),i.left-=h.left+(parseFloat(s.css(p,"marginLeft"))||0),i.top-=h.top+(parseFloat(s.css(p,"marginTop"))||0),r||"hidden"===(l=s.css(p,"overflow"))||"visible"===l||(r=s(p)));while(p=p.offsetParent);return r&&(r[0]!==c[0]||d)&&n(r,1),i};var ae=(z=j.reposition.Corner=function(t,e){t=(""+t).replace(/([A-Z])/," $1").replace(/middle/gi,N).toLowerCase(),this.x=(t.match(/left|right/i)||t.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(t.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase(),this.forceY=!!e;var i=t.charAt(0);this.precedance="t"===i||"b"===i?L:S}).prototype;ae.invert=function(t,e){this[t]=this[t]===F?P:this[t]===P?F:e||this[t]},ae.string=function(){var t=this.x,e=this.y;return t===e?t:this.precedance===L||this.forceY&&"center"!==e?e+" "+t:t+" "+e},ae.abbrev=function(){var t=this.string().split(" ");return t[0].charAt(0)+(t[1]&&t[1].charAt(0)||"")},ae.clone=function(){return new z(this.string(),this.forceY)},j.toggle=function(t,i){var o=this.cache,n=this.options,r=this.tooltip;if(i){if(/over|enter/.test(i.type)&&/out|leave/.test(o.event.type)&&n.show.target.add(i.target).length===n.show.target.length&&r.has(i.relatedTarget).length)return this;o.event=d(i)}if(this.waiting&&!t&&(this.hiddenDuringWait=W),!this.rendered)return t?this.render(1):this;if(this.destroyed||this.disabled)return this;var a,h,l,c=t?"show":"hide",p=this.options[c],u=(this.options[t?"hide":"show"],this.options.position),f=this.options.content,g=this.tooltip.css("width"),m=this.tooltip.is(":visible"),v=t||1===p.target.length,y=!i||2>p.target.length||o.target[0]===i.target;return(typeof t).search("boolean|number")&&(t=!m),a=!r.is(":animated")&&m===t&&y,h=a?E:!!this._trigger(c,[90]),this.destroyed?this:(h!==k&&t&&this.focus(i),!h||a?this:(s.attr(r[0],"aria-hidden",!t),t?(o.origin=d(this.mouse),s.isFunction(f.text)&&this._updateContent(f.text,k),s.isFunction(f.title)&&this._updateTitle(f.title,k),!I&&"mouse"===u.target&&u.adjust.mouse&&(s(e).bind("mousemove."+X,this._storeMouse),I=W),g||r.css("width",r.outerWidth(k)),this.reposition(i,arguments[2]),g||r.css("width",""),p.solo&&("string"==typeof p.solo?s(p.solo):s(U,p.solo)).not(r).not(p.target).qtip("hide",s.Event("tooltipsolo"))):(clearTimeout(this.timers.show),delete o.origin,I&&!s(U+'[tracking="true"]:visible',p.solo).not(r).length&&(s(e).unbind("mousemove."+X),I=k),this.blur(i)),l=s.proxy(function(){t?(oe.ie&&r[0].style.removeAttribute("filter"),r.css("overflow",""),"string"==typeof p.autofocus&&s(this.options.show.autofocus,r).focus(),this.options.show.target.trigger("qtip-"+this.id+"-inactive")):r.css({display:"",visibility:"",opacity:"",left:"",top:""}),this._trigger(t?"visible":"hidden")},this),p.effect===k||v===k?(r[c](),l()):s.isFunction(p.effect)?(r.stop(1,1),p.effect.call(r,this),r.queue("fx",function(t){l(),t()})):r.fadeTo(90,t?1:0,l),t&&p.target.trigger("qtip-"+this.id+"-inactive"),this))},j.show=function(t){return this.toggle(W,t)},j.hide=function(t){return this.toggle(k,t)},j.focus=function(t){if(!this.rendered||this.destroyed)return this;var e=s(U),i=this.tooltip,o=parseInt(i[0].style.zIndex,10),n=T.zindex+e.length;return i.hasClass(Z)||this._trigger("focus",[n],t)&&(o!==n&&(e.each(function(){this.style.zIndex>o&&(this.style.zIndex=this.style.zIndex-1)}),e.filter("."+Z).qtip("blur",t)),i.addClass(Z)[0].style.zIndex=n),this},j.blur=function(t){return!this.rendered||this.destroyed?this:(this.tooltip.removeClass(Z),this._trigger("blur",[this.tooltip.css("zIndex")],t),this)},j.disable=function(t){return this.destroyed?this:("toggle"===t?t=!(this.rendered?this.tooltip.hasClass(ee):this.disabled):"boolean"!=typeof t&&(t=W),this.rendered&&this.tooltip.toggleClass(ee,t).attr("aria-disabled",t),this.disabled=!!t,this)},j.enable=function(){return this.disable(k)},j._createButton=function(){var t=this,e=this.elements,i=e.tooltip,o=this.options.content.button,n="string"==typeof o,r=n?o:"Close tooltip";e.button&&e.button.remove(),e.button=o.jquery?o:s("",{"class":"qtip-close "+(this.options.style.widget?"":X+"-icon"),title:r,"aria-label":r}).prepend(s("",{"class":"ui-icon ui-icon-close",html:"×"})),e.button.appendTo(e.titlebar||i).attr("role","button").click(function(e){return i.hasClass(ee)||t.hide(e),k})},j._updateButton=function(t){if(!this.rendered)return k;var e=this.elements.button;t?this._createButton():e.remove()},j._setWidget=function(){var t=this.options.style.widget,e=this.elements,i=e.tooltip,s=i.hasClass(ee);i.removeClass(ee),ee=t?"ui-state-disabled":"qtip-disabled",i.toggleClass(ee,s),i.toggleClass("ui-helper-reset "+c(),t).toggleClass(K,this.options.style.def&&!t),e.content&&e.content.toggleClass(c("content"),t),e.titlebar&&e.titlebar.toggleClass(c("header"),t),e.button&&e.button.toggleClass(X+"-icon",!t)},j._storeMouse=function(t){(this.mouse=d(t)).type="mousemove"},j._bind=function(t,e,i,o,n){var r="."+this._id+(o?"-"+o:"");e.length&&s(t).bind((e.split?e:e.join(r+" "))+r,s.proxy(i,n||this))},j._unbind=function(t,e){s(t).unbind("."+this._id+(e?"-"+e:""))};var he="."+X;s(function(){v(U,["mouseenter","mouseleave"],function(t){var e="mouseenter"===t.type,i=s(t.currentTarget),o=s(t.relatedTarget||t.target),n=this.options;e?(this.focus(t),i.hasClass(J)&&!i.hasClass(ee)&&clearTimeout(this.timers.hide)):"mouse"===n.position.target&&n.hide.event&&n.show.target&&!o.closest(n.show.target[0]).length&&this.hide(t),i.toggleClass(te,e)}),v("["+H+"]",Q,g)}),j._trigger=function(t,e,i){var o=s.Event("tooltip"+t);return o.originalEvent=i&&s.extend({},i)||this.cache.event||E,this.triggering=t,this.tooltip.trigger(o,[this].concat(e||[])),this.triggering=k,!o.isDefaultPrevented()},j._bindEvents=function(t,e,o,n,r,a){if(n.add(o).length===n.length){var h=[];e=s.map(e,function(e){var o=s.inArray(e,t);return o>-1?(h.push(t.splice(o,1)[0]),i):e}),h.length&&this._bind(o,h,function(t){var e=this.rendered?this.tooltip[0].offsetWidth>0:!1;(e?a:r).call(this,t)})}this._bind(o,t,r),this._bind(n,e,a)},j._assignInitialEvents=function(t){function e(t){return this.disabled||this.destroyed?k:(this.cache.event=d(t),this.cache.target=t?s(t.target):[i],clearTimeout(this.timers.show),this.timers.show=p.call(this,function(){this.render("object"==typeof t||o.show.ready)},o.show.delay),i)}var o=this.options,n=o.show.target,r=o.hide.target,a=o.show.event?s.trim(""+o.show.event).split(" "):[],h=o.hide.event?s.trim(""+o.hide.event).split(" "):[];/mouse(over|enter)/i.test(o.show.event)&&!/mouse(out|leave)/i.test(o.hide.event)&&h.push("mouseleave"),this._bind(n,"mousemove",function(t){this._storeMouse(t),this.cache.onTarget=W}),this._bindEvents(a,h,n,r,e,function(){clearTimeout(this.timers.show)}),(o.show.ready||o.prerender)&&e.call(this,t)},j._assignEvents=function(){var i=this,o=this.options,n=o.position,r=this.tooltip,a=o.show.target,h=o.hide.target,l=n.container,c=n.viewport,d=s(e),p=(s(e.body),s(t)),v=o.show.event?s.trim(""+o.show.event).split(" "):[],y=o.hide.event?s.trim(""+o.hide.event).split(" "):[];s.each(o.events,function(t,e){i._bind(r,"toggle"===t?["tooltipshow","tooltiphide"]:["tooltip"+t],e,null,r)}),/mouse(out|leave)/i.test(o.hide.event)&&"window"===o.hide.leave&&this._bind(d,["mouseout","blur"],function(t){/select|option/.test(t.target.nodeName)||t.relatedTarget||this.hide(t)}),o.hide.fixed?h=h.add(r.addClass(J)):/mouse(over|enter)/i.test(o.show.event)&&this._bind(h,"mouseleave",function(){clearTimeout(this.timers.show)}),(""+o.hide.event).indexOf("unfocus")>-1&&this._bind(l.closest("html"),["mousedown","touchstart"],function(t){var e=s(t.target),i=this.rendered&&!this.tooltip.hasClass(ee)&&this.tooltip[0].offsetWidth>0,o=e.parents(U).filter(this.tooltip[0]).length>0;e[0]===this.target[0]||e[0]===this.tooltip[0]||o||this.target.has(e[0]).length||!i||this.hide(t)}),"number"==typeof o.hide.inactive&&(this._bind(a,"qtip-"+this.id+"-inactive",g),this._bind(h.add(r),T.inactiveEvents,g,"-inactive")),this._bindEvents(v,y,a,h,u,f),this._bind(a.add(r),"mousemove",function(t){if("number"==typeof o.hide.distance){var e=this.cache.origin||{},i=this.options.hide.distance,s=Math.abs;(s(t.pageX-e.pageX)>=i||s(t.pageY-e.pageY)>=i)&&this.hide(t)}this._storeMouse(t)}),"mouse"===n.target&&n.adjust.mouse&&(o.hide.event&&this._bind(a,["mouseenter","mouseleave"],function(t){this.cache.onTarget="mouseenter"===t.type}),this._bind(d,"mousemove",function(t){this.rendered&&this.cache.onTarget&&!this.tooltip.hasClass(ee)&&this.tooltip[0].offsetWidth>0&&this.reposition(t)})),(n.adjust.resize||c.length)&&this._bind(s.event.special.resize?c:p,"resize",m),n.adjust.scroll&&this._bind(p.add(n.container),"scroll",m)},j._unassignEvents=function(){var i=[this.options.show.target[0],this.options.hide.target[0],this.rendered&&this.tooltip[0],this.options.position.container[0],this.options.position.viewport[0],this.options.position.container.closest("html")[0],t,e];this._unbind(s([]).pushStack(s.grep(i,function(t){return"object"==typeof t})))},T=s.fn.qtip=function(t,e,o){var n=(""+t).toLowerCase(),r=E,h=s.makeArray(arguments).slice(1),l=h[h.length-1],c=this[0]?s.data(this[0],X):E;return!arguments.length&&c||"api"===n?c:"string"==typeof t?(this.each(function(){var t=s.data(this,X);if(!t)return W;if(l&&l.timeStamp&&(t.cache.event=l),!e||"option"!==n&&"options"!==n)t[n]&&t[n].apply(t,h);else{if(o===i&&!s.isPlainObject(e))return r=t.get(e),k;t.set(e,o)}}),r!==E?r:this):"object"!=typeof t&&arguments.length?i:(c=a(s.extend(W,{},t)),this.each(function(t){var e,o;return o=s.isArray(c.id)?c.id[t]:c.id,o=!o||o===k||1>o.length||T.api[o]?T.nextid++:o,e=y(s(this),o,c),e===k?W:(T.api[o]=e,s.each(R,function(){"initialize"===this.initialize&&this(e)}),e._assignInitialEvents(l),i)}))},s.qtip=o,T.api={},s.each({attr:function(t,e){if(this.length){var i=this[0],o="title",n=s.data(i,"qtip");if(t===o&&n&&"object"==typeof n&&n.options.suppress)return 2>arguments.length?s.attr(i,se):(n&&n.options.content.attr===o&&n.cache.attr&&n.set("content.text",e),this.attr(se,e))}return s.fn["attr"+ie].apply(this,arguments)},clone:function(t){var e=(s([]),s.fn["clone"+ie].apply(this,arguments));return t||e.filter("["+se+"]").attr("title",function(){return s.attr(this,se)}).removeAttr(se),e}},function(t,e){if(!e||s.fn[t+ie])return W;var i=s.fn[t+ie]=s.fn[t];s.fn[t]=function(){return e.apply(this,arguments)||i.apply(this,arguments)}}),s.ui||(s["cleanData"+ie]=s.cleanData,s.cleanData=function(t){for(var e,i=0;(e=s(t[i])).length;i++)if(e.attr(Y))try{e.triggerHandler("removeqtip")}catch(o){}s["cleanData"+ie].apply(this,arguments)}),T.version="2.2.0",T.nextid=0,T.inactiveEvents=Q,T.zindex=15e3,T.defaults={prerender:k,id:k,overwrite:W,suppress:W,content:{text:W,attr:"title",title:k,button:k},position:{my:"top left",at:"bottom right",target:k,container:k,viewport:k,adjust:{x:0,y:0,mouse:W,scroll:W,resize:W,method:"flipinvert flipinvert"},effect:function(t,e){s(this).animate(e,{duration:200,queue:k})}},show:{target:k,event:"mouseenter",effect:W,delay:90,solo:k,ready:k,autofocus:k},hide:{target:k,event:"mouseleave",effect:W,delay:0,fixed:k,inactive:k,leave:"window",distance:k},style:{classes:"",widget:k,width:k,height:k,def:W},events:{render:E,move:E,show:E,hide:E,toggle:E,visible:E,hidden:E,focus:E,blur:E}};var le,ce="margin",de="border",pe="color",ue="background-color",fe="transparent",ge=" !important",me=!!e.createElement("canvas").getContext,ve=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,ye={},be=["Webkit","O","Moz","ms"];if(me)var we=t.devicePixelRatio||1,_e=function(){var t=e.createElement("canvas").getContext("2d");return t.backingStorePixelRatio||t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||1}(),xe=we/_e;else var qe=function(t,e,i){return"'};s.extend(x.prototype,{init:function(t){var e,i;i=this.element=t.elements.tip=s("
",{"class":X+"-tip"}).prependTo(t.tooltip),me?(e=s("").appendTo(this.element)[0].getContext("2d"),e.lineJoin="miter",e.miterLimit=1e5,e.save()):(e=qe("shape",'coordorigin="0,0"',"position:absolute;"),this.element.html(e+e),t._bind(s("*",i).add(i),["click","mousedown"],function(t){t.stopPropagation()},this._ns)),t._bind(t.tooltip,"tooltipmove",this.reposition,this._ns,this),this.create()},_swapDimensions:function(){this.size[0]=this.options.height,this.size[1]=this.options.width},_resetDimensions:function(){this.size[0]=this.options.width,this.size[1]=this.options.height},_useTitle:function(t){var e=this.qtip.elements.titlebar;return e&&(t.y===D||t.y===N&&this.element.position().top+this.size[1]/2+this.options.offsetoe.ie?0:_(this._useTitle(t)&&e.titlebar||e.content,i)||_(e.tooltip,i)||0},_invalidColour:function(t,e,i){var s=t.css(e);return!s||i&&s===t.css(i)||ve.test(s)?k:s},_parseColours:function(t){var e=this.qtip.elements,i=this.element.css("cssText",""),o=de+b(t[t.precedance])+b(pe),n=this._useTitle(t)&&e.titlebar||e.content,r=this._invalidColour,a=[];return a[0]=r(i,ue)||r(n,ue)||r(e.content,ue)||r(e.tooltip,ue)||i.css(ue),a[1]=r(i,o,pe)||r(n,o,pe)||r(e.content,o,pe)||r(e.tooltip,o,pe)||e.tooltip.css(o),s("*",i).add(i).css("cssText",ue+":"+fe+ge+";"+de+":0"+ge+";"),a},_calculateSize:function(t){var e,i,s,o=t.precedance===L,n=this.options.width,r=this.options.height,a="c"===t.abbrev(),h=(o?n:r)*(a?.5:1),l=Math.pow,c=Math.round,d=Math.sqrt(l(h,2)+l(r,2)),p=[this.border/h*d,this.border/r*d];return p[2]=Math.sqrt(l(p[0],2)-l(this.border,2)),p[3]=Math.sqrt(l(p[1],2)-l(this.border,2)),e=d+p[2]+p[3]+(a?0:p[0]),i=e/d,s=[c(i*n),c(i*r)],o?s:s.reverse()},_calculateTip:function(t,e,i){i=i||1,e=e||this.size;var s=e[0]*i,o=e[1]*i,n=Math.ceil(s/2),r=Math.ceil(o/2),a={br:[0,0,s,o,s,0],bl:[0,0,s,0,0,o],tr:[0,o,s,0,s,o],tl:[0,0,0,o,s,o],tc:[0,o,n,0,s,o],bc:[0,0,s,0,n,o],rc:[0,0,s,r,0,o],lc:[s,0,s,o,0,r]};return a.lt=a.br,a.rt=a.bl,a.lb=a.tr,a.rb=a.tl,a[t.abbrev()]},_drawCoords:function(t,e){t.beginPath(),t.moveTo(e[0],e[1]),t.lineTo(e[2],e[3]),t.lineTo(e[4],e[5]),t.closePath()},create:function(){var t=this.corner=(me||oe.ie)&&this._parseCorner(this.options.corner);return(this.enabled=!!this.corner&&"c"!==this.corner.abbrev())&&(this.qtip.cache.corner=t.clone(),this.update()),this.element.toggle(this.enabled),this.corner},update:function(e,i){if(!this.enabled)return this;var o,n,r,a,h,l,c,d,p=this.qtip.elements,u=this.element,f=u.children(),g=this.options,m=this.size,v=g.mimic,y=Math.round;e||(e=this.qtip.cache.corner||this.corner),v===k?v=e:(v=new z(v),v.precedance=e.precedance,"inherit"===v.x?v.x=e.x:"inherit"===v.y?v.y=e.y:v.x===v.y&&(v[e.precedance]=e[e.precedance])),n=v.precedance,e.precedance===S?this._swapDimensions():this._resetDimensions(),o=this.color=this._parseColours(e),o[1]!==fe?(d=this.border=this._parseWidth(e,e[e.precedance]),g.border&&1>d&&!ve.test(o[1])&&(o[0]=o[1]),this.border=d=g.border!==W?g.border:d):this.border=d=0,c=this.size=this._calculateSize(e),u.css({width:c[0],height:c[1],lineHeight:c[1]+"px"}),l=e.precedance===L?[y(v.x===F?d:v.x===P?c[0]-m[0]-d:(c[0]-m[0])/2),y(v.y===D?c[1]-m[1]:0)]:[y(v.x===F?c[0]-m[0]:0),y(v.y===D?d:v.y===O?c[1]-m[1]-d:(c[1]-m[1])/2)],me?(r=f[0].getContext("2d"),r.restore(),r.save(),r.clearRect(0,0,6e3,6e3),a=this._calculateTip(v,m,xe),h=this._calculateTip(v,this.size,xe),f.attr(A,c[0]*xe).attr(B,c[1]*xe),f.css(A,c[0]).css(B,c[1]),this._drawCoords(r,h),r.fillStyle=o[1],r.fill(),r.translate(l[0]*xe,l[1]*xe),this._drawCoords(r,a),r.fillStyle=o[0],r.fill()):(a=this._calculateTip(v),a="m"+a[0]+","+a[1]+" l"+a[2]+","+a[3]+" "+a[4]+","+a[5]+" xe",l[2]=d&&/^(r|b)/i.test(e.string())?8===oe.ie?2:1:0,f.css({coordsize:c[0]+d+" "+(c[1]+d),antialias:""+(v.string().indexOf(N)>-1),left:l[0]-l[2]*Number(n===S),top:l[1]-l[2]*Number(n===L),width:c[0]+d,height:c[1]+d}).each(function(t){var e=s(this);e[e.prop?"prop":"attr"]({coordsize:c[0]+d+" "+(c[1]+d),path:a,fillcolor:o[0],filled:!!t,stroked:!t}).toggle(!(!d&&!t)),!t&&e.html(qe("stroke",'weight="'+2*d+'px" color="'+o[1]+'" miterlimit="1000" joinstyle="miter"'))})),t.opera&&setTimeout(function(){p.tip.css({display:"inline-block",visibility:"visible"})},1),i!==k&&this.calculate(e,c)},calculate:function(t,e){if(!this.enabled)return k;var i,o,n=this,r=this.qtip.elements,a=this.element,h=this.options.offset,l=(r.tooltip.hasClass("ui-widget"),{});return t=t||this.corner,i=t.precedance,e=e||this._calculateSize(t),o=[t.x,t.y],i===S&&o.reverse(),s.each(o,function(s,o){var a,c,d;o===N?(a=i===L?F:D,l[a]="50%",l[ce+"-"+a]=-Math.round(e[i===L?0:1]/2)+h):(a=n._parseWidth(t,o,r.tooltip),c=n._parseWidth(t,o,r.content),d=n._parseRadius(t),l[o]=Math.max(-n.border,s?c:h+(d>a?d:-a))) }),l[t[i]]-=e[i===S?0:1],a.css({margin:"",top:"",bottom:"",left:"",right:""}).css(l),l},reposition:function(t,e,s){function o(t,e,i,s,o){t===V&&l.precedance===e&&c[s]&&l[i]!==N?l.precedance=l.precedance===S?L:S:t!==V&&c[s]&&(l[e]=l[e]===N?c[s]>0?s:o:l[e]===s?o:s)}function n(t,e,o){l[t]===N?g[ce+"-"+e]=f[t]=r[ce+"-"+e]-c[e]:(a=r[o]!==i?[c[e],-r[e]]:[-c[e],r[e]],(f[t]=Math.max(a[0],a[1]))>a[0]&&(s[e]-=c[e],f[e]=k),g[r[o]!==i?o:e]=f[t])}if(this.enabled){var r,a,h=e.cache,l=this.corner.clone(),c=s.adjusted,d=e.options.position.adjust.method.split(" "),p=d[0],u=d[1]||d[0],f={left:k,top:k,x:0,y:0},g={};this.corner.fixed!==W&&(o(p,S,L,F,P),o(u,L,S,D,O),l.string()===h.corner.string()||h.cornerTop===c.top&&h.cornerLeft===c.left||this.update(l,k)),r=this.calculate(l),r.right!==i&&(r.left=-r.right),r.bottom!==i&&(r.top=-r.bottom),r.user=this.offset,(f.left=p===V&&!!c.left)&&n(S,F,P),(f.top=u===V&&!!c.top)&&n(L,D,O),this.element.css(g).toggle(!(f.x&&f.y||l.x===N&&f.y||l.y===N&&f.x)),s.left-=r.left.charAt?r.user:p!==V||f.top||!f.left&&!f.top?r.left+this.border:0,s.top-=r.top.charAt?r.user:u!==V||f.left||!f.left&&!f.top?r.top+this.border:0,h.cornerLeft=c.left,h.cornerTop=c.top,h.corner=l.clone()}},destroy:function(){this.qtip._unbind(this.qtip.tooltip,this._ns),this.qtip.elements.tip&&this.qtip.elements.tip.find("*").remove().end().remove()}}),le=R.tip=function(t){return new x(t,t.options.style.tip)},le.initialize="render",le.sanitize=function(t){if(t.style&&"tip"in t.style){var e=t.style.tip;"object"!=typeof e&&(e=t.style.tip={corner:e}),/string|boolean/i.test(typeof e.corner)||(e.corner=W)}},M.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){this.create(),this.qtip.reposition()},"^style.tip.(height|width)$":function(t){this.size=[t.width,t.height],this.update(),this.qtip.reposition()},"^content.title|style.(classes|widget)$":function(){this.update()}},s.extend(W,T.defaults,{style:{tip:{corner:W,mimic:k,width:6,height:6,border:W,offset:0}}});var Ce,Te,je="qtip-modal",ze="."+je;Te=function(){function t(t){if(s.expr[":"].focusable)return s.expr[":"].focusable;var e,i,o,n=!isNaN(s.attr(t,"tabindex")),r=t.nodeName&&t.nodeName.toLowerCase();return"area"===r?(e=t.parentNode,i=e.name,t.href&&i&&"map"===e.nodeName.toLowerCase()?(o=s("img[usemap=#"+i+"]")[0],!!o&&o.is(":visible")):!1):/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||n:n}function i(t){1>c.length&&t.length?t.not("body").blur():c.first().focus()}function o(t){if(h.is(":visible")){var e,o=s(t.target),a=n.tooltip,l=o.closest(U);e=1>l.length?k:parseInt(l[0].style.zIndex,10)>parseInt(a[0].style.zIndex,10),e||o.closest(U)[0]===a[0]||i(o),r=t.target===c[c.length-1]}}var n,r,a,h,l=this,c={};s.extend(l,{init:function(){return h=l.elem=s("
",{id:"qtip-overlay",html:"
",mousedown:function(){return k}}).hide(),s(e.body).bind("focusin"+ze,o),s(e).bind("keydown"+ze,function(t){n&&n.options.show.modal.escape&&27===t.keyCode&&n.hide(t)}),h.bind("click"+ze,function(t){n&&n.options.show.modal.blur&&n.hide(t)}),l},update:function(e){n=e,c=e.options.show.modal.stealfocus!==k?e.tooltip.find("*").filter(function(){return t(this)}):[]},toggle:function(t,o,r){var c=(s(e.body),t.tooltip),d=t.options.show.modal,p=d.effect,u=o?"show":"hide",f=h.is(":visible"),g=s(ze).filter(":visible:not(:animated)").not(c);return l.update(t),o&&d.stealfocus!==k&&i(s(":focus")),h.toggleClass("blurs",d.blur),o&&h.appendTo(e.body),h.is(":animated")&&f===o&&a!==k||!o&&g.length?l:(h.stop(W,k),s.isFunction(p)?p.call(h,o):p===k?h[u]():h.fadeTo(parseInt(r,10)||90,o?1:0,function(){o||h.hide()}),o||h.queue(function(t){h.css({left:"",top:""}),s(ze).length||h.detach(),t()}),a=o,n.destroyed&&(n=E),l)}}),l.init()},Te=new Te,s.extend(q.prototype,{init:function(t){var e=t.tooltip;return this.options.on?(t.elements.overlay=Te.elem,e.addClass(je).css("z-index",T.modal_zindex+s(ze).length),t._bind(e,["tooltipshow","tooltiphide"],function(t,i,o){var n=t.originalEvent;if(t.target===e[0])if(n&&"tooltiphide"===t.type&&/mouse(leave|enter)/.test(n.type)&&s(n.relatedTarget).closest(Te.elem[0]).length)try{t.preventDefault()}catch(r){}else(!n||n&&"tooltipsolo"!==n.type)&&this.toggle(t,"tooltipshow"===t.type,o)},this._ns,this),t._bind(e,"tooltipfocus",function(t,i){if(!t.isDefaultPrevented()&&t.target===e[0]){var o=s(ze),n=T.modal_zindex+o.length,r=parseInt(e[0].style.zIndex,10);Te.elem[0].style.zIndex=n-1,o.each(function(){this.style.zIndex>r&&(this.style.zIndex-=1)}),o.filter("."+Z).qtip("blur",t.originalEvent),e.addClass(Z)[0].style.zIndex=n,Te.update(i);try{t.preventDefault()}catch(a){}}},this._ns,this),t._bind(e,"tooltiphide",function(t){t.target===e[0]&&s(ze).filter(":visible").not(e).last().qtip("focus",t)},this._ns,this),i):this},toggle:function(t,e,s){return t&&t.isDefaultPrevented()?this:(Te.toggle(this.qtip,!!e,s),i)},destroy:function(){this.qtip.tooltip.removeClass(je),this.qtip._unbind(this.qtip.tooltip,this._ns),Te.toggle(this.qtip,k),delete this.qtip.elements.overlay}}),Ce=R.modal=function(t){return new q(t,t.options.show.modal)},Ce.sanitize=function(t){t.show&&("object"!=typeof t.show.modal?t.show.modal={on:!!t.show.modal}:t.show.modal.on===i&&(t.show.modal.on=W))},T.modal_zindex=T.zindex-200,Ce.initialize="render",M.modal={"^show.modal.(on|blur)$":function(){this.destroy(),this.init(),this.qtip.elems.overlay.toggle(this.qtip.tooltip[0].offsetWidth>0)}},s.extend(W,T.defaults,{show:{modal:{on:k,effect:W,blur:W,stealfocus:W,escape:W}}}),R.viewport=function(i,s,o,n,r,a,h){function l(t,e,i,o,n,r,a,h,l){var c=s[n],p=_[t],b=x[t],w=i===V,q=p===n?l:p===r?-l:-l/2,C=b===n?h:b===r?-h:-h/2,T=v[n]+y[n]-(f?0:u[n]),j=T-c,z=c+l-(a===A?g:m)-T,M=q-(_.precedance===t||p===_[e]?C:0)-(b===N?h/2:0);return w?(M=(p===n?1:-1)*q,s[n]+=j>0?j:z>0?-z:0,s[n]=Math.max(-u[n]+y[n],c-M,Math.min(Math.max(-u[n]+y[n]+(a===A?g:m),c+M),s[n],"center"===p?c-q:1e9))):(o*=i===$?2:0,j>0&&(p!==n||z>0)?(s[n]-=M+o,d.invert(t,n)):z>0&&(p!==r||j>0)&&(s[n]-=(p===N?-M:M)+o,d.invert(t,r)),v>s[n]&&-s[n]>z&&(s[n]=c,d=_.clone())),s[n]-c}var c,d,p,u,f,g,m,v,y,b=o.target,w=i.elements.tooltip,_=o.my,x=o.at,q=o.adjust,C=q.method.split(" "),T=C[0],j=C[1]||C[0],z=o.viewport,M=o.container,I=i.cache,W={left:0,top:0};return z.jquery&&b[0]!==t&&b[0]!==e.body&&"none"!==q.method?(u=M.offset()||W,f="static"===M.css("position"),c="fixed"===w.css("position"),g=z[0]===t?z.width():z.outerWidth(k),m=z[0]===t?z.height():z.outerHeight(k),v={left:c?0:z.scrollLeft(),top:c?0:z.scrollTop()},y=z.offset()||W,("shift"!==T||"shift"!==j)&&(d=_.clone()),W={left:"none"!==T?l(S,L,T,q.x,F,P,A,n,a):0,top:"none"!==j?l(L,S,j,q.y,D,O,B,r,h):0},d&&I.lastClass!==(p=X+"-pos-"+d.abbrev())&&w.removeClass(i.cache.lastClass).addClass(i.cache.lastClass=p),W):W},R.polys={polygon:function(t,e){var i,s,o,n={width:0,height:0,position:{top:1e10,right:0,bottom:0,left:1e10},adjustable:k},r=0,a=[],h=1,l=1,c=0,d=0;for(r=t.length;r--;)i=[parseInt(t[--r],10),parseInt(t[r+1],10)],i[0]>n.position.right&&(n.position.right=i[0]),i[0]n.position.bottom&&(n.position.bottom=i[1]),i[1]0&&o>0&&h>0&&l>0;)for(s=Math.floor(s/2),o=Math.floor(o/2),e.x===F?h=s:e.x===P?h=n.width-s:h+=Math.floor(s/2),e.y===D?l=o:e.y===O?l=n.height-o:l+=Math.floor(o/2),r=a.length;r--&&!(2>a.length);)c=a[r][0]-n.position.left,d=a[r][1]-n.position.top,(e.x===F&&c>=h||e.x===P&&h>=c||e.x===N&&(h>c||c>n.width-h)||e.y===D&&d>=l||e.y===O&&l>=d||e.y===N&&(l>d||d>n.height-l))&&a.splice(r,1);n.position={left:a[0][0],top:a[0][1]}}return n},rect:function(t,e,i,s){return{width:Math.abs(i-t),height:Math.abs(s-e),position:{left:Math.min(t,i),top:Math.min(e,s)}}},_angles:{tc:1.5,tr:7/4,tl:5/4,bc:.5,br:.25,bl:.75,rc:2,lc:1,c:0},ellipse:function(t,e,i,s,o){var n=R.polys._angles[o.abbrev()],r=0===n?0:i*Math.cos(n*Math.PI),a=s*Math.sin(n*Math.PI);return{width:2*i-Math.abs(r),height:2*s-Math.abs(a),position:{left:t+r,top:e+a},adjustable:k}},circle:function(t,e,i,s){return R.polys.ellipse(t,e,i,i,s)}},R.svg=function(t,i,o){for(var n,r,a,h,l,c,d,p,u,f,g,m=s(e),v=i[0],y=s(v.ownerSVGElement),b=1,w=1,_=!0;!v.getBBox;)v=v.parentNode;if(!v.getBBox||!v.parentNode)return k;n=y.attr("width")||y.width()||parseInt(y.css("width"),10),r=y.attr("height")||y.height()||parseInt(y.css("height"),10);var x=(parseInt(i.css("stroke-width"),10)||0)/2;switch(x&&(b+=x/n,w+=x/r),v.nodeName){case"ellipse":case"circle":f=R.polys.ellipse(v.cx.baseVal.value,v.cy.baseVal.value,(v.rx||v.r).baseVal.value+x,(v.ry||v.r).baseVal.value+x,o);break;case"line":case"polygon":case"polyline":for(u=v.points||[{x:v.x1.baseVal.value,y:v.y1.baseVal.value},{x:v.x2.baseVal.value,y:v.y2.baseVal.value}],f=[],p=-1,c=u.numberOfItems||u.length;c>++p;)d=u.getItem?u.getItem(p):u[p],f.push.apply(f,[d.x,d.y]);f=R.polys.polygon(f,o);break;default:f=v.getBoundingClientRect(),f={width:f.width,height:f.height,position:{left:f.left,top:f.top}},_=!1}return g=f.position,y=y[0],_&&(y.createSVGPoint&&(a=v.getScreenCTM(),u=y.createSVGPoint(),u.x=g.left,u.y=g.top,h=u.matrixTransform(a),g.left=h.x,g.top=h.y),y.viewBox&&(l=y.viewBox.baseVal)&&l.width&&l.height&&(b*=n/l.width,w*=r/l.height)),g.left+=m.scrollLeft(),g.top+=m.scrollTop(),f},R.imagemap=function(t,e,i){e.jquery||(e=s(e));var o,n,r,a,h,l=e.attr("shape").toLowerCase().replace("poly","polygon"),c=s('img[usemap="#'+e.parent("map").attr("name")+'"]'),d=s.trim(e.attr("coords")),p=d.replace(/,$/,"").split(",");if(!c.length)return k;if("polygon"===l)a=R.polys.polygon(p,i);else{if(!R.polys[l])return k;for(r=-1,h=p.length,n=[];h>++r;)n.push(parseInt(p[r],10));a=R.polys[l].apply(this,n.concat(i))}return o=c.offset(),o.left+=Math.ceil((c.outerWidth(k)-c.width())/2),o.top+=Math.ceil((c.outerHeight(k)-c.height())/2),a.position.left+=o.left,a.position.top+=o.top,a};var Me,Ie='';s.extend(C.prototype,{_scroll:function(){var e=this.qtip.elements.overlay;e&&(e[0].style.top=s(t).scrollTop()+"px")},init:function(i){var o=i.tooltip;1>s("select, object").length&&(this.bgiframe=i.elements.bgiframe=s(Ie).appendTo(o),i._bind(o,"tooltipmove",this.adjustBGIFrame,this._ns,this)),this.redrawContainer=s("
",{id:X+"-rcontainer"}).appendTo(e.body),i.elements.overlay&&i.elements.overlay.addClass("qtipmodal-ie6fix")&&(i._bind(t,["scroll","resize"],this._scroll,this._ns,this),i._bind(o,["tooltipshow"],this._scroll,this._ns,this)),this.redraw()},adjustBGIFrame:function(){var t,e,i=this.qtip.tooltip,s={height:i.outerHeight(k),width:i.outerWidth(k)},o=this.qtip.plugins.tip,n=this.qtip.elements.tip;e=parseInt(i.css("borderLeftWidth"),10)||0,e={left:-e,top:-e},o&&n&&(t="x"===o.corner.precedance?[A,F]:[B,D],e[t[1]]-=n[t[0]]()),this.bgiframe.css(e).css(s)},redraw:function(){if(1>this.qtip.rendered||this.drawing)return this;var t,e,i,s,o=this.qtip.tooltip,n=this.qtip.options.style,r=this.qtip.options.position.container;return this.qtip.drawing=1,n.height&&o.css(B,n.height),n.width?o.css(A,n.width):(o.css(A,"").appendTo(this.redrawContainer),e=o.width(),1>e%2&&(e+=1),i=o.css("maxWidth")||"",s=o.css("minWidth")||"",t=(i+s).indexOf("%")>-1?r.width()/100:0,i=(i.indexOf("%")>-1?t:1)*parseInt(i,10)||e,s=(s.indexOf("%")>-1?t:1)*parseInt(s,10)||0,e=i+s?Math.min(Math.max(e,s),i):e,o.css(A,Math.round(e)).appendTo(r)),this.drawing=0,this},destroy:function(){this.bgiframe&&this.bgiframe.remove(),this.qtip._unbind([t,this.qtip.tooltip],this._ns)}}),Me=R.ie6=function(t){return 6===oe.ie?new C(t):k},Me.initialize="render",M.ie6={"^content|style$":function(){this.redraw()}}})})(window,document); -//@ sourceMappingURL=http://cdnjs.cloudflare.com/ajax/libs/qtip2/2.2.0/jquery.qtip.min.map \ No newline at end of file +//@ sourceMappingURL=https://cdnjs.cloudflare.com/ajax/libs/qtip2/2.2.0/jquery.qtip.min.map diff --git a/portal/templates/_widget-topmenu.html b/portal/templates/_widget-topmenu.html index 6236344a..01a0c912 100644 --- a/portal/templates/_widget-topmenu.html +++ b/portal/templates/_widget-topmenu.html @@ -2,15 +2,13 @@