diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py
index 560062ed8cd..a8df982d53c 100644
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -38,10 +38,12 @@ class Configuration( object ):
self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" )
self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) )
self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
+ self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
+ self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) )
self.template_path = resolve_path( kwargs.get( "template_path", "templates" ), self.root )
self.template_cache = resolve_path( kwargs.get( "template_cache_path", "database/compiled_templates" ), self.root )
self.local_job_queue_workers = int( kwargs.get( "local_job_queue_workers", "5" ) )
- self.cluster_job_queue_workers = int( kwargs.get( "cluster_job_queue_workers", "5" ) )
+ self.cluster_job_queue_workers = int( kwargs.get( "cluster_job_queue_workers", "3" ) )
self.job_scheduler_policy = kwargs.get("job_scheduler_policy", "FIFO")
self.job_queue_cleanup_interval = int( kwargs.get("job_queue_cleanup_interval", "5") )
self.job_working_directory = resolve_path( kwargs.get( "job_working_directory", "database/job_working_directory" ), self.root )
diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py
index d992300719c..4696c0ca1ad 100644
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -391,7 +391,10 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab
children=relation(
HistoryDatasetAssociation,
primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ),
- backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) )
+ backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ),
+ visible_children=relation(
+ HistoryDatasetAssociation,
+ primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) )
) )
assign_mapper( context, Dataset, Dataset.table,
@@ -531,6 +534,9 @@ assign_mapper( context, LibraryFolderDatasetAssociation, LibraryFolderDatasetAss
LibraryFolderDatasetAssociation,
primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ),
backref=backref( "parent", primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ), remote_side=[LibraryFolderDatasetAssociation.table.c.id] ) ),
+ visible_children=relation(
+ LibraryFolderDatasetAssociation,
+ primaryjoin=( ( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ) & ( LibraryFolderDatasetAssociation.table.c.visible == True ) ) ),
tags=relation(
LibraryTagDatasetAssociation,
primaryjoin=( LibraryFolderDatasetAssociation.table.c.id == LibraryTagDatasetAssociation.table.c.dataset_id ),
diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py
index 5c7d068f861..f82997f532c 100644
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -246,6 +246,18 @@ def string_as_bool( string ):
else:
return False
+def listify( item ):
+ """
+ Make a single item a single item list, or return a list if passed a
+ list. Passing a None returns an empty list.
+ """
+ if item is None:
+ return []
+ elif isinstance( item, list ):
+ return item
+ else:
+ return [ item ]
+
def commaify(amount):
orig = amount
new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', amount)
diff --git a/lib/galaxy/web/__init__.py b/lib/galaxy/web/__init__.py
index 508e2b35037..15bb7888368 100644
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -2,5 +2,5 @@
The Galaxy web application.
"""
-from framework import expose, json, require_login, url_for, error, form, FormBuilder
+from framework import expose, json, require_login, require_admin, url_for, error, form, FormBuilder
diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py
index d96588f41f1..7ca8d20233f 100644
--- a/lib/galaxy/web/controllers/admin.py
+++ b/lib/galaxy/web/controllers/admin.py
@@ -13,68 +13,53 @@ import sqlalchemy as sa
import logging
log = logging.getLogger( __name__ )
-entities = { '@': 'FuNkYaT' }
-unentities = { 'FuNkYaT' : '@' }
-no_privilege_msg = "You must have Galaxy administrator privileges to use this feature."
-
class Admin( BaseController ):
- def user_is_admin( self, trans ):
- admin_users = trans.app.config.get( "admin_users", "" ).split( "," )
- if not admin_users:
- return False
- user = trans.get_user()
- if not user:
- return False
- if not user.email in admin_users:
- return False
- return True
@web.expose
+ @web.require_admin
def index( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
- return trans.fill_template( '/admin/index.mako', msg=msg )
+ messagetype = params.get( 'messagetype', 'done' )
+ return trans.fill_template( '/admin/index.mako', msg=msg, messagetype=messagetype )
@web.expose
+ @web.require_admin
def center( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
return trans.fill_template( '/admin/center.mako' )
@web.expose
+ @web.require_admin
def reload_tool( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
- return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg )
+ messagetype = params.get( 'messagetype', 'done' )
+ return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg, messagetype=messagetype )
@web.expose
+ @web.require_admin
def tool_reload( self, trans, tool_version=None, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
tool_id = params.tool_id
self.app.toolbox.reload( tool_id )
msg = 'Reloaded tool: ' + tool_id
- return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg )
+ return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg, messagetype='done' )
# Galaxy Role Stuff
@web.expose
+ @web.require_admin
def roles( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
return trans.fill_template( '/admin/dataset_security/roles.mako',
roles=trans.app.model.Role.query() \
.filter( trans.app.model.Role.table.c.type != trans.app.model.Role.types.PRIVATE ) \
.order_by( trans.app.model.Role.table.c.name ).all(),
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def create_role( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
users=trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all()
groups = trans.app.model.Group.query() \
.filter( galaxy.model.Group.table.c.deleted==False ) \
@@ -83,21 +68,20 @@ class Admin( BaseController ):
return trans.fill_template( '/admin/dataset_security/role_create.mako',
users=users,
groups=groups,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def new_role( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
name = params.name
description = params.description
if not name or not description:
msg = "Please enter a name and a description"
- trans.response.send_redirect( '/admin/create_role?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='create_role', msg=msg, messagetype='error' ) )
elif trans.app.model.Role.filter_by( name=name ).first():
msg = "A role with that name already exists"
- trans.response.send_redirect( '/admin/create_role?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='create_role', msg=msg, messagetype='error' ) )
else:
# Create the role
role = galaxy.model.Role( name=name,
@@ -105,27 +89,27 @@ class Admin( BaseController ):
type=trans.app.model.Role.types.ADMIN )
role.flush()
# Add the users
- users = listify( params.users )
+ users = util.listify( params.users )
for user_id in users:
user = galaxy.model.User.get( user_id )
# Create the UserRoleAssociation
ura = galaxy.model.UserRoleAssociation( user, role )
ura.flush()
# Add the groups
- groups = listify( params.groups )
+ groups = util.listify( params.groups )
for group_id in groups:
group = galaxy.model.Group.get( group_id )
# Create the GroupRoleAssociation
gra = galaxy.model.GroupRoleAssociation( group, role )
gra.flush()
msg = "The new role has been created with %s associated users and %s associated groups" % ( str( len( users ) ), str( len( groups ) ) )
- trans.response.send_redirect( '/admin/roles?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def role( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
role = trans.app.model.Role.get( int( params.role_id ) )
in_users = []
out_users = []
@@ -173,15 +157,14 @@ class Admin( BaseController ):
in_groups=in_groups,
out_groups=out_groups,
library_dataset_actions=library_dataset_actions,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def role_members_edit( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
role = galaxy.model.Role.get( int( params.role_id ) )
- in_users = [ trans.app.model.User.get( x ) for x in listify( params.in_users ) ]
+ in_users = [ trans.app.model.User.get( x ) for x in util.listify( params.in_users ) ]
for ura in role.users:
user = trans.app.model.User.get( ura.user_id )
if user not in in_users:
@@ -196,28 +179,26 @@ class Admin( BaseController ):
if role == dhp.role:
dhp.delete()
dhp.flush()
- in_groups = [ trans.app.model.Group.get( x ) for x in listify( params.in_groups ) ]
+ in_groups = [ trans.app.model.Group.get( x ) for x in util.listify( params.in_groups ) ]
trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
role.refresh()
msg = "The role has been updated with %s associated users and %s associated groups" % ( str( len( in_users ) ), str( len( in_groups ) ) )
- trans.response.send_redirect( '/admin/roles?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def mark_role_deleted( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
role = galaxy.model.Role.get( int( params.role_id ) )
role.deleted = True
role.flush()
msg = "The role has been marked as deleted."
- trans.response.send_redirect( '/admin/roles?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def deleted_roles( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
# Build a list of tuples which are roles followed by lists of groups and users
# [ ( role, [ group, group, group ], [ user, user ] ), ( role, [ group, group ], [ user ] ) ]
roles_groups_users = []
@@ -235,24 +216,21 @@ class Admin( BaseController ):
roles_groups_users.append( ( role, groups, users ) )
return trans.fill_template( '/admin/dataset_security/deleted_roles.mako',
roles_groups_users=roles_groups_users,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def undelete_role( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
role = galaxy.model.Role.get( int( params.role_id ) )
role.deleted = False
role.flush()
msg = "The role has been marked as not deleted."
- trans.response.send_redirect( '/admin/roles?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def purge_role( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
role = galaxy.model.Role.get( int( params.role_id ) )
# Delete UserRoleAssociations
for ura in role.users:
@@ -278,15 +256,15 @@ class Admin( BaseController ):
role.delete()
role.flush()
msg = "The role has been purged from the database."
- trans.response.send_redirect( '/admin/deleted_roles?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='deleted_roles', msg=msg, messagetype='done' ) )
# Galaxy Group Stuff
@web.expose
+ @web.require_admin
def groups( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
# Build a list of tuples which are groups followed by lists of members and roles
# [ ( group, [ member, member, member ], [ role, role ] ), ( group, [ member, member ], [ role ] ) ]
groups_members_roles = []
@@ -304,13 +282,14 @@ class Admin( BaseController ):
groups_members_roles.append( ( group, members, roles ) )
return trans.fill_template( '/admin/dataset_security/groups.mako',
groups_members_roles=groups_members_roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def create_group( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
users=trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all()
roles = trans.app.model.Role.query() \
.filter( and_( galaxy.model.Role.table.c.deleted == False,
@@ -320,26 +299,25 @@ class Admin( BaseController ):
return trans.fill_template( '/admin/dataset_security/group_create.mako',
users=users,
roles=roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def new_group( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
name = params.name
if not name:
msg = "Please enter a name"
- trans.response.send_redirect( '/admin/create_group?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='create_group', msg=msg, messagetype='error' ) )
elif trans.app.model.Group.filter_by( name=name ).first():
msg = "A group with that name already exists"
- trans.response.send_redirect( '/admin/create_group?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='create_group', msg=msg, messagetype='error' ) )
else:
# Create the group
group = galaxy.model.Group( name )
group.flush()
# Add the members
- members = listify( params.members )
+ members = util.listify( params.members )
for user_id in members:
user = galaxy.model.User.get( user_id )
# Create the UserGroupAssociation
@@ -357,13 +335,13 @@ class Admin( BaseController ):
gra = galaxy.model.GroupRoleAssociation( group, role )
gra.flush()
msg = "The new group has been created with %s members and %s associated roles" % ( str( len( members ) ), str( len( roles ) ) )
- trans.response.send_redirect( '/admin/groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def group_members_edit( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
group = galaxy.model.Group.get( int( params.group_id ) )
members = []
for uga in group.members:
@@ -372,14 +350,14 @@ class Admin( BaseController ):
group=group,
members=members,
users=galaxy.model.User.query().order_by( galaxy.model.User.table.c.email ).all(),
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def update_group_members( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
group_id = int( params.group_id )
- members = listify( params.members )
+ members = util.listify( params.members )
group = galaxy.model.Group.get( group_id )
# This is tricky since we have default association tables with
# records referring to members of this group. Because of this,
@@ -399,15 +377,15 @@ class Admin( BaseController ):
uga = galaxy.model.UserGroupAssociation( user, group )
uga.flush()
msg = "Group membership has been updated with a total of %s members" % len( members )
- trans.response.send_redirect( '/admin/groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) )
# TODO: We probably don't want the following 2 methods since managing roles should be
# restricted to the Role page due to private roles and rules governing them
@web.expose
+ @web.require_admin
def group_roles_edit( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
group = galaxy.model.Group.get( int( params.group_id ) )
group_roles = []
for gra in group.roles:
@@ -416,14 +394,14 @@ class Admin( BaseController ):
group=group,
group_roles=group_roles,
roles=galaxy.model.Role.query().order_by( galaxy.model.Role.table.c.name ).all(),
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def update_group_roles( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
group_id = int( params.group_id )
- roles = listify( params.roles )
+ roles = util.listify( params.roles )
group = galaxy.model.Group.get( group_id )
# This is tricky since we have default association tables with
# records referring to members of this group. Because of this,
@@ -443,24 +421,22 @@ class Admin( BaseController ):
gra = galaxy.model.GroupRoleAssociation( group, role )
gra.flush()
msg = "Group updated with a total of %s associated roles" % len( roles )
- trans.response.send_redirect( '/admin/groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def mark_group_deleted( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
group = galaxy.model.Group.get( int( params.group_id ) )
group.deleted = True
group.flush()
msg = "The group has been marked as deleted."
- trans.response.send_redirect( '/admin/groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def deleted_groups( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
# Build a list of tuples which are groups followed by lists of members and roles
# [ ( group, [ member, member, member ], [ role, role ] ), ( group, [ member, member ], [ role ] ) ]
groups_members_roles = []
@@ -478,24 +454,21 @@ class Admin( BaseController ):
groups_members_roles.append( ( group, members, roles ) )
return trans.fill_template( '/admin/dataset_security/deleted_groups.mako',
groups_members_roles=groups_members_roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def undelete_group( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
group = galaxy.model.Group.get( int( params.group_id ) )
group.deleted = False
group.flush()
msg = "The group has been marked as not deleted."
- trans.response.send_redirect( '/admin/groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def purge_group( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
group = galaxy.model.Group.get( int( params.group_id ) )
# Delete UserGroupAssociations
for uga in group.users:
@@ -509,15 +482,15 @@ class Admin( BaseController ):
group.delete()
group.flush()
msg = "The group has been purged from the database."
- trans.response.send_redirect( '/admin/deleted_groups?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='deleted_groups', msg=msg, messagetype='done' ) )
# Galaxy User Stuff
@web.expose
+ @web.require_admin
def users( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
# Build a list of tuples which are users followed by lists of groups and roles
# [ ( user, [ group, group, group ], [ role, role ] ), ( user, [ group, group ], [ role ] ) ]
users_groups_roles = []
@@ -532,14 +505,15 @@ class Admin( BaseController ):
users_groups_roles.append( ( user, groups, roles ) )
return trans.fill_template( '/admin/dataset_security/users.mako',
users_groups_roles=users_groups_roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def user( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
user_id = params.user_id
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
user = trans.app.model.User.get( user_id )
# Get the groups and roles to which the user belongs
groups = trans.app.model.Group.query() \
@@ -553,13 +527,14 @@ class Admin( BaseController ):
user=user,
groups=groups,
roles=roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def user_groups_edit( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
user = galaxy.model.User.get( int( params.user_id ) )
user_groups = []
for uga in user.groups:
@@ -572,14 +547,14 @@ class Admin( BaseController ):
user=user,
user_groups=user_groups,
groups=groups,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def update_user_groups( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
user_id = int( params.user_id )
- groups = listify( params.groups )
+ groups = util.listify( params.groups )
user = galaxy.model.User.get( user_id )
# First remove existing UserGroupAssociations that are not in the received groups param
for uga in user.groups:
@@ -594,27 +569,30 @@ class Admin( BaseController ):
uga = galaxy.model.UserGroupAssociation( user, group )
uga.flush()
msg = "The user now belongs to a total of %s groups" % len( groups )
- trans.response.send_redirect( '/admin/users?msg=%s' % msg )
+ trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='done' ) )
# Galaxy Library Stuff
@web.expose
+ @web.require_admin
def library_browser( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
+ created_lfda_ids = params.get( 'created_lfda_ids', '' )
return trans.fill_template( '/admin/library/browser.mako',
libraries=trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ) \
.order_by( trans.app.model.Library.name ).all(),
+ created_lfda_ids=created_lfda_ids,
deleted=False,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
libraries = library_browser
@web.expose
+ @web.require_admin
def library( self, trans, id=None, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
if params.get( 'new', False ):
action = 'new'
elif params.get( 'rename', False ):
@@ -622,9 +600,11 @@ class Admin( BaseController ):
elif params.get( 'delete', False ):
action = 'delete'
else:
- return trans.show_error_message( "You must specify a valid action ( new, rename, delete ) to perform on a library." )
+ msg = 'Invalid action attempted on library'
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
if not id and not action == 'new':
- return trans.show_error_message( "You must specify a library to %s." % action )
+ msg = "You must specify a library to %s." % action
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
if not action == 'new':
library = trans.app.model.Library.get( int( id ) )
if action == 'new':
@@ -635,19 +615,27 @@ class Admin( BaseController ):
root_folder.flush()
library.root_folder = root_folder
library.flush()
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
- return trans.fill_template( '/admin/library/new_library.mako', msg=msg )
+ msg = 'The new library named %s has been created' % library.name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
+ return trans.fill_template( '/admin/library/new_library.mako', msg=msg, messagetype=messagetype )
elif action == 'rename':
if params.rename == 'submitted':
- if params.get( 'root_folder', None ):
- root_folder = library.root_folder
- root_folder.name = util.restore_text( params.name )
- root_folder.flush()
- library.name = util.restore_text( params.name )
- library.description = util.restore_text( params.description )
- library.flush()
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
- return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg )
+ new_name = util.restore_text( params.name )
+ new_description = util.restore_text( params.description )
+ if not new_name:
+ msg = 'Enter a valid name'
+ return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg, messagetype='error' )
+ else:
+ if params.get( 'root_folder', False ):
+ root_folder = library.root_folder
+ root_folder.name = new_name
+ root_folder.flush()
+ library.name = new_name
+ library.description = new_description
+ library.flush()
+ msg = 'The library has been renamed to %s' % new_name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
+ return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg, messagetype=messagetype )
elif action == 'delete':
def delete_folder( library_folder ):
for folder in library_folder.active_folders:
@@ -665,26 +653,25 @@ class Admin( BaseController ):
library.deleted = True
library.flush()
msg = 'The library and all of its contents have been marked deleted'
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def deleted_libraries( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
libraries=trans.app.model.Library.filter( and_( trans.app.model.Library.table.c.deleted==True,
trans.app.model.Library.table.c.purged==False ) ) \
.order_by( trans.app.model.Library.table.c.name ).all()
return trans.fill_template( '/admin/library/browser.mako',
libraries=libraries,
deleted=True,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
@web.expose
+ @web.require_admin
def undelete_library( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
library = galaxy.model.Library.get( int( params.id ) )
def undelete_folder( library_folder ):
for folder in library_folder.folders:
@@ -698,13 +685,11 @@ class Admin( BaseController ):
library.deleted = False
library.flush()
msg = "The library and all of its contents have been marked not deleted"
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def purge_library( self, trans, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
- msg = params.msg
library = galaxy.model.Library.get( int( params.id ) )
def purge_folder( library_folder ):
for lf in library_folder.folders:
@@ -727,13 +712,13 @@ class Admin( BaseController ):
library.purged = True
library.flush()
msg = "The library and all of its contents have been purged, datasets will be removed from disk via the cleanup_datasets script"
- return trans.response.send_redirect( web.url_for( action='deleted_libraries', msg=msg ) )
+ return trans.response.send_redirect( web.url_for( action='deleted_libraries', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def folder( self, trans, id, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
if params.get( 'new', False ):
action = 'new'
elif params.get( 'rename', False ):
@@ -741,10 +726,12 @@ class Admin( BaseController ):
elif params.get( 'delete', False ):
action = 'delete'
else:
- return trans.show_error_message( "You must specify a valid action ( new, rename, delete ) to perform on a folder." )
+ msg = "Invalid action attempted on folder."
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
folder = trans.app.model.LibraryFolder.get( id )
if not folder:
- return trans.show_error_message( "Invalid folder specified, id: %s" % str( id ) )
+ msg = "Invalid folder specified, id: %s" % str( id )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
if action == 'new':
if params.new == 'submitted':
new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ),
@@ -755,17 +742,26 @@ class Admin( BaseController ):
new_folder.genome_build = util.dbnames.default_value
folder.add_folder( new_folder )
new_folder.flush()
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
- return trans.fill_template( '/admin/library/new_folder.mako', folder=folder, msg=msg )
+ msg = 'The new folder named %s has been added to this library' % new_folder.name
+ return trans.response.send_redirect( web.url_for( action='folder', id=new_folder.id, msg=msg, messagetype='done' ) )
+ return trans.fill_template( '/admin/library/new_folder.mako', folder=folder, msg=msg, messagetype=messagetype )
elif action == 'rename':
if params.rename == 'submitted':
- folder.name = util.restore_text( params.name )
- folder.description = util.restore_text( params.description )
- folder.flush()
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
- return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg )
+ new_name = util.restore_text( params.name )
+ new_description = util.restore_text( params.description )
+ if not new_name:
+ msg = 'Enter a valid name'
+ return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg, messagetype='error' )
+ else:
+ folder.name = new_name
+ folder.description = new_description
+ folder.flush()
+ msg = 'The folder has been renamed to %s' % new_name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
+ return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg, messagetype=messagetype )
elif action == 'delete':
def delete_folder( folder ):
+ folder.refresh()
for subfolder in folder.active_folders:
delete_folder( subfolder )
for lfda in folder.active_datasets:
@@ -774,12 +770,11 @@ class Admin( BaseController ):
folder.deleted = True
folder.flush()
delete_folder( folder )
- msg = 'The folder and all of its contents have been marked deleted'
- return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ msg = 'The folder %s and all of its contents have been marked deleted' % folder.name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
@web.expose
+ @web.require_admin
def dataset( self, trans, id=None, name="Unnamed", info='no info', extension=None, folder_id=None, dbkey=None, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
if isinstance( dbkey, list ):
last_used_build = dbkey[0]
else:
@@ -790,6 +785,7 @@ class Admin( BaseController ):
data_files = []
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
# add_file method
def add_file( file_obj, name, extension, dbkey, last_used_build, roles, info='no info', space_to_tab=False ):
@@ -862,7 +858,6 @@ class Admin( BaseController ):
# Dataset upload
if params.get( 'new_dataset_button', False ):
# Copied from upload tool action
- last_dataset_created = None
data_file = params.get( 'file_data', '' )
url_paste = params.get( 'url_paste', '' )
server_dir = params.get( 'server_dir', 'None' )
@@ -871,44 +866,45 @@ class Admin( BaseController ):
msg = 'Select a file, enter a URL or Text, or select a server directory.'
else:
msg = 'Select a file, enter a URL or enter Text.'
- trans.response.send_redirect( web.url_for( action='dataset', folder_id=folder_id, msg=msg ) )
+ trans.response.send_redirect( web.url_for( action='dataset', folder_id=folder_id, msg=msg, messagetype='done' ) )
space_to_tab = params.get( 'space_to_tab', False )
if space_to_tab and space_to_tab not in [ "None", None ]:
space_to_tab = True
roles = []
role_ids = params.get( 'roles', [] )
- for role_id in listify( role_ids ):
+ for role_id in util.listify( role_ids ):
roles.append( galaxy.model.Role.get( role_id ) )
temp_name = ""
data_list = []
- created_datasets = []
+ created_lfda_ids = ''
if 'filename' in dir( data_file ):
file_name = data_file.filename
file_name = file_name.split( '\\' )[-1]
file_name = file_name.split( '/' )[-1]
- last_dataset_created = add_file( data_file.file,
- file_name,
- extension,
- dbkey,
- last_used_build,
- roles,
- info="uploaded file",
- space_to_tab=space_to_tab )
+ created_lfda = add_file( data_file.file,
+ file_name,
+ extension,
+ dbkey,
+ last_used_build,
+ roles,
+ info="uploaded file",
+ space_to_tab=space_to_tab )
+ created_lfda_ids = str( created_lfda.id )
elif url_paste not in [ None, "" ]:
if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0:
url_paste = url_paste.replace( '\r', '' ).split( '\n' )
for line in url_paste:
line = line.rstrip( '\r\n' )
if line:
- last_dataset_created = add_file( urllib.urlopen( line ),
- line,
- extension,
- dbkey,
- last_used_build,
- roles,
- info="uploaded url",
- space_to_tab=space_to_tab )
- created_datasets.append( last_dataset_created )
+ created_lfda = add_file( urllib.urlopen( line ),
+ line,
+ extension,
+ dbkey,
+ last_used_build,
+ roles,
+ info="uploaded url",
+ space_to_tab=space_to_tab )
+ created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) )
else:
is_valid = False
for line in url_paste:
@@ -917,14 +913,15 @@ class Admin( BaseController ):
is_valid = True
break
if is_valid:
- last_dataset_created = add_file( StringIO.StringIO( url_paste ),
- 'Pasted Entry',
- extension,
- dbkey,
- last_used_build,
- roles,
- info="pasted entry",
- space_to_tab=space_to_tab )
+ created_lfda = add_file( StringIO.StringIO( url_paste ),
+ 'Pasted Entry',
+ extension,
+ dbkey,
+ last_used_build,
+ roles,
+ info="pasted entry",
+ space_to_tab=space_to_tab )
+ created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) )
elif server_dir not in [ None, "", "None" ]:
full_dir = os.path.join( trans.app.config.library_import_dir, server_dir )
try:
@@ -935,31 +932,23 @@ class Admin( BaseController ):
full_file = os.path.join( full_dir, file )
if not os.path.isfile( full_file ):
continue
- last_dataset_created = add_file( open( full_file, 'rb' ),
- file,
- extension,
- dbkey,
- last_used_build,
- roles,
- info="imported file",
- space_to_tab=space_to_tab )
- created_datasets.append( last_dataset_created )
- if len( created_datasets ) > 1:
- trans.response.send_redirect( web.url_for(
- action = 'library_browser',
- msg = "%i new datasets added to the library. Click here if you'd like to edit the permissions on these datasets." % (
- len( created_datasets ),
- web.url_for( action='dataset', id=",".join( [ str(d.id) for d in created_datasets ] ) )
- )
- ) )
- elif last_dataset_created is not None:
- trans.response.send_redirect( web.url_for(
- action = 'library_browser',
- msg = "New dataset added to the library. Click here if you'd like to edit the permissions or attributes on this dataset." %
- web.url_for( action='dataset', id=last_dataset_created.id )
- ) )
+ created_lfda = add_file( open( full_file, 'rb' ),
+ file,
+ extension,
+ dbkey,
+ last_used_build,
+ roles,
+ info="imported file",
+ space_to_tab=space_to_tab )
+ created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) )
+ if created_lfda_ids:
+ created_lfda_ids = created_lfda_ids.lstrip( ',' )
+ total_added = len( created_lfda_ids.split( ',' ) )
+ msg = "%i new datasets added to the library ( each is selected below ). Click the Go button at the bottom of this page to edit the permissions on these datasets." % total_added
+ trans.response.send_redirect( web.url_for( action='library_browser', created_lfda_ids=created_lfda_ids, msg=msg, messagetype='done' ) )
else:
- return trans.show_error_message( 'Upload failed' )
+ msg = "Upload failed"
+ trans.response.send_redirect( web.url_for( action='library_browser', created_lfda_ids=created_lfda_ids, msg=msg, messagetype='error' ) )
# No dataset(s) specified, display upload form
elif not id:
@@ -971,17 +960,18 @@ class Admin( BaseController ):
yield build_name, dbkey, ( dbkey==last_used_build )
dbkeys = get_dbkey_options( last_used_build )
# Send list of roles to the form so the dataset can be associated with 1 or more of them.
- roles = trans.app.model.Role.query().order_by( trans.app.model.Role.c.name ).all()
+ roles = trans.app.model.Role.filter( trans.app.model.Role.c.type != trans.app.model.Role.types.PRIVATE ).order_by( trans.app.model.Role.c.name ).all()
return trans.fill_template( '/admin/library/new_dataset.mako',
folder_id=folder_id,
file_formats=file_formats,
dbkeys=dbkeys,
last_used_build=last_used_build,
roles=roles,
- msg=msg )
+ msg=msg,
+ messagetype=messagetype )
else:
if id.count( ',' ):
- ids = id.split(',')
+ ids = id.split( ',' )
id = None
else:
ids = None
@@ -989,7 +979,8 @@ class Admin( BaseController ):
if id:
lda = trans.app.model.LibraryFolderDatasetAssociation.get( id )
if not lda:
- return trans.show_error_message( "Invalid dataset specified, id: %s" %str( id ) )
+ msg = "Invalid dataset specified, id: %s" %str( id )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
# Copied from edit attributes for 'regular' datasets with some additions
p = util.Params(kwd, safe=False)
@@ -997,7 +988,7 @@ class Admin( BaseController ):
# The user clicked the Save button on the 'Associate With Roles' form
permissions = {}
for k, v in trans.app.model.Dataset.permitted_actions.items():
- in_roles = [ trans.app.model.Role.get( x ) for x in listify( p.get( k + '_in', [] ) ) ]
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( p.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
trans.app.security_agent.set_dataset_permissions( lda.dataset, permissions )
lda.dataset.refresh()
@@ -1023,7 +1014,8 @@ class Admin( BaseController ):
lda.metadata.dbkey = dbkey
lda.datatype.after_edit( lda )
trans.app.model.flush()
- return trans.show_ok_message( "Attributes updated" )
+ msg = 'Attributes updated for dataset %s' % lda.name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
elif p.detect:
# The user clicked the Auto-detect button on the 'Edit Attributes' form
for name, spec in lda.datatype.metadata_spec.items():
@@ -1034,11 +1026,14 @@ class Admin( BaseController ):
lda.datatype.set_meta( lda )
lda.datatype.after_edit( lda )
trans.app.model.flush()
- return trans.show_ok_message( "Attributes updated" )
+ msg = 'Attributes updated for dataset %s' % lda.name
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
elif p.delete:
+ # The user selected the "Remove this dataset from the library" pop-up menu option
lda.deleted = True
lda.flush()
- trans.response.send_redirect( web.url_for( action='library_browser' ) )
+ msg = 'Dataset %s has been removed from this library' % lda.name
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
lda.datatype.before_edit( lda )
if "dbkey" in lda.datatype.metadata_spec and not lda.metadata.dbkey:
# Copy dbkey into metadata, for backwards compatability
@@ -1055,67 +1050,82 @@ class Admin( BaseController ):
dataset=lda,
datatypes=ldatatypes,
err=None,
- msg=msg )
- # multiple ids specfied, display multi permission form
+ msg=msg,
+ messagetype=messagetype )
+ # multiple ids specfied, display permission form for each one...
elif ids:
- ldas = []
+ lfdas = []
for id in [ int( id ) for id in ids ]:
- lda = trans.app.model.LibraryFolderDatasetAssociation.get( id )
- if lda is None:
- return trans.show_error_message( 'You specified an invalid dataset' )
- ldas.append( lda )
- if len( ldas ) < 2:
- return trans.show_error_message( 'You must specify at least two datasets to modify permissions on' )
+ lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id )
+ if lfda is None:
+ msg = 'You specified an invalid dataset id: %s' %str( id )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
+ lfdas.append( lfda )
+ if len( lfdas ) < 2:
+ msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
if 'update_roles' in kwd:
- p = util.Params( kwd )
+ #p = util.Params( kwd )
permissions = {}
for k, v in trans.app.model.Dataset.permitted_actions.items():
- in_roles = [ trans.app.model.Role.get( x ) for x in listify( p.get( k + '_in', [] ) ) ]
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
- for lda in ldas:
- trans.app.security_agent.set_dataset_permissions( lda.dataset, permissions )
- lda.dataset.refresh()
- # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together.
+ for lfda in lfdas:
+ trans.app.security_agent.set_dataset_permissions( lfda.dataset, permissions )
+ lfda.dataset.refresh()
+ msg = 'Permissions and roles have been updated on %d datasets' % len( lfdas )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
+ # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together.
tmp = []
- for lda in ldas:
- perms = trans.app.security_agent.get_dataset_permissions( lda.dataset )
+ for lfda in lfdas:
+ perms = trans.app.security_agent.get_dataset_permissions( lfda.dataset )
if perms not in tmp:
tmp.append( perms )
if len( tmp ) != 1:
- return trans.show_error_message( "The datasets you selected do not have identical permissions, so they can not be updated together" )
+ msg = 'The datasets you selected do not have identical permissions, so they can not be updated together'
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
else:
- return trans.fill_template( "/admin/library/dataset.mako",
- dataset=ldas )
+ return trans.fill_template( "/admin/library/dataset.mako", dataset=lfdas )
@web.expose
def add_dataset_to_folder_from_history( self, trans, ids="", folder_id=None, **kwd ):
- if not isinstance( ids, list ):
- if ids:
- ids = ids.split( "," )
- else:
- ids = []
+ if not self.user_is_admin( trans ):
+ return trans.show_error_message( no_privilege_msg )
try:
folder = trans.app.model.LibraryFolder.get( folder_id )
except:
- folder = None
- if folder is None:
- return trans.show_error_message( "You must provide a valid target folder." )
- error_msg = ok_msg = ""
- dataset_names = []
- if ids:
- for data_id in ids:
- data = trans.app.model.HistoryDatasetAssociation.get( data_id )
- if data:
- data.to_library_dataset_folder_association( target_folder = folder )
- dataset_names.append( data.name )
+ msg = "Invalid folder id: %s" % str( folder_id )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
+ params = util.Params( kwd )
+ msg = params.get( 'msg', None )
+ messagetype = params.get( 'messagetype', 'done' )
+ # See if the current history is empty
+ history=trans.get_history()
+ if not history.active_datasets:
+ msg = 'Your current history is empty'
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
+ if params.get( 'add_dataset_from_history_button', False ):
+ if not isinstance( ids, list ):
+ if ids:
+ ids = ids.split( "," )
else:
- error_msg += "A requested dataset (%s) was invalid. " % ( data_id )
- if dataset_names:
- ok_msg = "Added datasets (%s) to the library folder." % ( ", ".join( dataset_names ) )
- return trans.fill_template( "/admin/library/add_dataset_from_history.mako",
- history=trans.get_history(),
- folder=folder,
- ok_msg=ok_msg,
- error_msg=error_msg )
+ ids = []
+ dataset_names = []
+ if ids:
+ for data_id in ids:
+ data = trans.app.model.HistoryDatasetAssociation.get( data_id )
+ if data:
+ data.to_library_dataset_folder_association( target_folder = folder )
+ dataset_names.append( data.name )
+ else:
+ msg = "The requested dataset id '%s' is invalid" % str( data_id )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
+ if dataset_names:
+ msg = "Added the following datasets to the library folder: %s" % ( ", ".join( dataset_names ) )
+ return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
+ else:
+ msg = 'Select at least one dataset from the list'
+ messagetype = 'error'
+ return trans.fill_template( "/admin/library/add_dataset_from_history.mako", history=history, folder=folder, msg=msg, messagetype=messagetype )
def check_gzip( self, temp_name ):
"""
@@ -1134,106 +1144,53 @@ class Admin( BaseController ):
# return( True, False )
return ( True, True )
@web.expose
+ @web.require_admin
def datasets( self, trans, **kwd ):
# This method is used by the select list labeled "Perform action on selected datasets" on the admin library browser.
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
params = util.Params( kwd )
msg = params.msg
+ messagetype = params.get( 'messagetype', 'done' )
if params.get( 'action_on_datasets_button', False ):
if not params.dataset_ids:
msg = "At least one dataset must be selected for %s" % params.action
- trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
- dataset_ids = listify( params.dataset_ids )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
+ dataset_ids = util.listify( params.dataset_ids )
if params.action == 'edit':
- trans.response.send_redirect( web.url_for( action='dataset', id=",".join( dataset_ids ) ) )
+ trans.response.send_redirect( web.url_for( action='dataset', id=",".join( dataset_ids ), msg=msg, messagetype=messagetype ) )
elif params.action == 'delete':
for id in dataset_ids:
lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id )
lfda.deleted = True
lfda.flush()
msg = "The selected datasets have been removed from this library"
- trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) )
else:
msg = "Action '%s' is not yet implemented" % str( params.action )
- trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) )
else:
- trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) )
+ trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype=messagetype ) )
@web.expose
+ @web.require_admin
def delete_dataset( self, trans, id=None, **kwd):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
if id:
# id is a LibraryFolderDatasetAssociation.id
- library_folder_dataset_assoc = trans.app.model.LibraryFolderDatasetAssociation.get( id )
- self._delete_dataset( library_folder_dataset_assoc )
- trans.log_event( "Dataset id %s deleted from library folder id %s" % ( str( id ), str( library_folder_dataset_assoc.folder.id ) ) )
- trans.response.send_redirect( web.url_for( action = 'folder', id = library_folder_dataset_assoc.folder.id, msg = 'The dataset was deleted from the folder' ) )
- return trans.show_error_message( "You did not specify a dataset to delete." )
-
- def _delete_dataset( self, library_folder_dataset_assoc ):
- #dataset = library_folder_dataset_assoc.dataset
- # TODO: assuming 1 to 1 mapping between Dataset -> LibraryFolders ( i.e., is can the same
- # dataset record be shared across LibraryFolders?
- # Confirm that things should be deleted as follows
-
- ### Deleting the base dataset will delete datasets that exist in user's histories
- ### ( LDA.dataset == HDA.dataset )
- ### Shouldn't this be a separate option?
- ### For Now, I am commenting out logic that acts on the dataset directly
- ### -- Dan:
-
-
- # Delete the LibraryFolderDatasetAssociation
- library_folder_dataset_assoc.deleted = True
- library_folder_dataset_assoc.flush()
-
- @web.expose
- def delete_folder( self, trans, id=None, **kwd):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
- if id:
- if 'confirm' not in kwd:
- return trans.show_warn_message( 'Click here to confirm folder deletion.' % web.url_for( action = 'delete_folder', id = id, confirm=True ) )
- # id is a LibraryFolder.id
- folder = trans.app.model.LibraryFolder.get( id )
- self._delete_folder( folder )
-
- trans.log_event( "Folder id %s deleted." % id )
-
- if folder.library_root:
- trans.response.send_redirect( web.url_for( action = 'library', id = folder.library_root[0].id, msg = 'You have deleted the root folder.' ) )
- trans.response.send_redirect( web.url_for( action = 'folder', id = folder.parent_id, msg = 'The folder was deleted.' ) )
- return trans.show_error_message( "You did not specify a folder to delete." )
-
- def _delete_folder( self, folder ):
- for lfda in folder.active_datasets:
- self._delete_dataset( lfda )
- for folder in folder.active_folders:
- self._delete_folder( folder )
- folder.deleted = True
- folder.flush()
-
- @web.expose
- def delete_library( self, trans, id=None, **kwd):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
- if id:
- if 'confirm' not in kwd:
- return trans.show_warn_message( 'Click here to confirm library deletion.' % web.url_for( action = 'delete_library', id = id, confirm=True ) )
- # id is a LibraryFolder.id
- library = trans.app.model.Library.get( id )
- self._delete_folder( library.root_folder )
- library.deleted = True
- library.flush()
- trans.log_event( "Library id %s deleted." % id )
- trans.response.send_redirect( web.url_for( action = 'libraries', msg = 'You have deleted the library %s.' % library.id ) )
- return trans.show_error_message( "You did not specify a library to delete." )
+ lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id )
+ lfda.deleted = True
+ lfda.flush()
+ msg = "Dataset %s was deleted from library folder %s" % ( lfda.name, lfda.folder.name )
+ trans.response.send_redirect( web.url_for( action='folder',
+ id=str( lfda.folder.id ),
+ msg=msg,
+ messagetype='done' ) )
+ msg = "You did not specify a dataset to delete."
+ return trans.response.send_redirect( web.url_for( action='folder',
+ id=str( lfda.folder.id ),
+ msg=msg,
+ messagetype='error' ) )
@web.expose
+ @web.require_admin
def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
- if not self.user_is_admin( trans ):
- return trans.show_error_message( no_privilege_msg )
if self.app.memdump is None:
return trans.show_error_message( "Memdump is not enabled (set use_memdump = True in universe_wsgi.ini)" )
heap = self.app.memdump.get()
@@ -1271,14 +1228,3 @@ class Admin( BaseController ):
breadcrumb += ".theone"
heap = heap.theone
return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
-
-def listify( item, return_none=False ):
- """
- Since single params are not a single item list
- """
- if item is None:
- return []
- elif isinstance( item, list ):
- return item
- else:
- return [ item ]
diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py
index eb6ca6f4b90..5d7949ed347 100644
--- a/lib/galaxy/web/controllers/root.py
+++ b/lib/galaxy/web/controllers/root.py
@@ -25,7 +25,10 @@ class RootController( BaseController ):
@web.expose
def tool_menu( self, trans ):
- return trans.fill_template('/root/tool_menu.mako', toolbox=self.get_toolbox() )
+ if trans.app.config.require_login and not trans.user:
+ return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy tools.' )
+ else:
+ return trans.fill_template('/root/tool_menu.mako', toolbox=self.get_toolbox() )
@web.expose
def tool_help( self, trans, id ):
@@ -51,6 +54,8 @@ class RootController( BaseController ):
NOTE: No longer accepts "id" or "template" options for security reasons.
"""
history = trans.get_history()
+ if trans.app.config.require_login and not trans.user:
+ return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy histories.' )
if as_xml:
trans.response.set_content_type('text/xml')
return trans.fill_template_mako( "root/history_as_xml.mako", history=history )
diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py
index 075474869b1..695bda82dd1 100644
--- a/lib/galaxy/web/controllers/user.py
+++ b/lib/galaxy/web/controllers/user.py
@@ -9,6 +9,17 @@ from random import choice
log = logging.getLogger( __name__ )
+require_login_template = """
+
+ This installation of Galaxy has been configured such that only users who are logged in may use it.%s +
+ +""" +require_login_nocreation_template = require_login_template % "" +require_login_creation_template = require_login_template % " If you don't already have an account, you may create one." + class User( BaseController ): @web.expose @@ -75,6 +86,10 @@ class User( BaseController ): m0 = trans.app.memory_usage.memory() email_error = password_error = None # Attempt login + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] if email or password: user = trans.app.model.User.filter_by( email=email ).first() if not user: @@ -87,33 +102,50 @@ class User( BaseController ): else: trans.handle_user_login( user ) trans.log_event( "User logged in" ) - return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=refresh_frames ) if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/login, memory used increased by %s" % m1 ) - return trans.show_form( - web.FormBuilder( web.url_for(), "Login", submit_text="Login" ) - .add_text( "email", "Email address", value=email, error=email_error ) + form = web.FormBuilder( web.url_for(), "Login", submit_text="Login" ) \ + .add_text( "email", "Email address", value=email, error=email_error ) \ .add_password( "password", "Password", value='', error=password_error, - help="Forgot password? Reset here" % web.url_for( action='reset_password' ) ) ) + help="Forgot password? Reset here" % web.url_for( action='reset_password' ) ) + if trans.app.config.require_login: + if trans.app.config.allow_user_creation: + return trans.show_form( form, header = require_login_creation_template % web.url_for( action = 'create' ) ) + else: + return trans.show_form( form, header = require_login_nocreation_template ) + else: + return trans.show_form( form ) + @web.expose def logout( self, trans ): if trans.app.memory_usage: # Keep track of memory usage m0 = trans.app.memory_usage.memory() + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] # Since logging an event requires a session, we'll log prior to ending the session trans.log_event( "User logged out" ) trans.handle_user_logout() if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/logout, memory used increased by %s" % m1 ) - return trans.show_ok_message( "You are no longer logged in", refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( "You are no longer logged in.", refresh_frames=refresh_frames ) @web.expose - def create( self, trans, email='', password='', confirm='',subscribe=False ): + def create( self, trans, email='', password='', confirm='', subscribe=False ): if trans.app.memory_usage: # Keep track of memory usage m0 = trans.app.memory_usage.memory() + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] + if not trans.app.config.allow_user_creation and not trans.user_is_admin(): + return trans.show_error_message( 'User registration is disabled. Please contact your local Galaxy administrator for an account.' ) email_error = password_error = confirm_error = None if email: if len( email ) == 0 or "@" not in email or "." not in email: @@ -130,29 +162,36 @@ class User( BaseController ): user = trans.app.model.User( email=email ) user.set_password_cleartext( password ) user.flush() - trans.app.security_agent.setup_new_user( user ) - trans.handle_user_login( user ) - trans.log_event( "User created a new account" ) - trans.log_event( "User logged in" ) + if trans.user_is_admin(): + trans.app.security_agent.create_private_user_role( user ) + trans.app.security_agent.user_set_default_permissions( user ) + trans.log_event( "Admin created a new account" ) + msg = 'Created account ' + user.email + else: + trans.app.security_agent.setup_new_user( user ) + trans.handle_user_login( user ) + trans.log_event( "User created a new account" ) + trans.log_event( "User logged in" ) + msg = 'Now logged in as ' + user.email #subscribe user to email list if subscribe: mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w') mail.write("To: %s\nFrom: %s\nSubject: Join Mailing List\n\nJoin Mailing list." % (trans.app.config.mailing_join_addr,email) ) if mail.close(): - return trans.show_warn_message( "Now logged in as " + user.email+". However, subscribing to the mailing list has failed.", refresh_frames=['masthead', 'history'] ) + return trans.show_warn_message( msg + ". However, subscribing to the mailing list has failed.", refresh_frames=refresh_frames ) if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/create, memory used increased by %s" % m1 ) - return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( msg, refresh_frames=refresh_frames ) return trans.show_form( web.FormBuilder( web.url_for(), "Create account", submit_text="Create" ) .add_text( "email", "Email address", value=email, error=email_error ) - .add_password( "password", "Password", value='', error=password_error ) - .add_password( "confirm", "Confirm password", value='', error=confirm_error ) + .add_password( "password", "Password", value='', error=password_error ) + .add_password( "confirm", "Confirm password", value='', error=confirm_error ) .add_input( "checkbox","Subscribe To Mailing List","subscribe", value='subscribe' ) ) @web.expose - def reset_password(self, trans, email=None, **kwd): + def reset_password( self, trans, email=None, **kwd ): error = '' reset_user = trans.app.model.User.filter_by( email=email ).first() user = trans.get_user() @@ -167,11 +206,11 @@ class User( BaseController ): mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w') mail.write("To: %s\nFrom: no-reply@%s\nSubject: Galaxy Password Reset\n\nYour password has been reset to \"%s\" (no quotes)." % (email, trans.request.remote_addr, new_pass) ) if mail.close(): - return trans.show_ok_message( "Failed to reset password! If this problem persist, submit a bug report.") + return trans.show_error_message( 'Failed to reset password. If this problem persists, please submit a bug report.' ) reset_user.set_password_cleartext( new_pass ) reset_user.flush() trans.log_event( "User reset password: %s" % email ) - return trans.show_ok_message( "Password has been reset and emailed to: %s." % email) + return trans.show_ok_message( "Password has been reset and emailed to: %s. Click here to return to the login form." % ( email, web.url_for( action='login' ) ) ) elif email != None: error = "The specified user does not exist" return trans.show_form( diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index c415c9c023c..ff51ec616c1 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -60,6 +60,19 @@ def require_login( verb="perform this action" ): return decorator return argcatcher +def require_admin( func ): + def decorator( self, trans, *args, **kwargs ): + admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) + if not admin_users: + return trans.show_error_message( "You must be an administrator to access this feature, and no administrators are set in the Galaxy configuration." ) + user = trans.get_user() + if not user: + return trans.show_error_message( "You must be an administrator to access this feature, and currently you are not logged in." ) + if not user.email in admin_users: + return trans.show_error_message( "You must be an administrator to access this feature." ) + return func( self, trans, *args, **kwargs ) + return decorator + NOT_SET = object() class MessageException( Exception ): @@ -117,6 +130,8 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.workflow_building_mode = False # Always have a valid galaxy session self.__ensure_valid_session() + if self.app.config.require_login: + self.__ensure_logged_in_user( environ ) @property def sa_session( self ): """ @@ -240,6 +255,18 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): if prev_galaxy_session: objects_to_flush.append( prev_galaxy_session ) sa_session.flush( objects_to_flush ) + def __ensure_logged_in_user( self, environ ): + allowed_paths = ( + url_for( controller='root', action='index' ), + url_for( controller='root', action='tool_menu' ), + url_for( controller='root', action='masthead' ), + url_for( controller='root', action='history' ), + url_for( controller='user', action='login' ), + url_for( controller='user', action='create' ), + url_for( controller='user', action='reset_password' ), + ) + if self.galaxy_session.user is None and environ['PATH_INFO'] not in allowed_paths: + self.response.send_redirect( url_for( controller='root', action='index' ) ) def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ): """ Create a new GalaxySession for this request, possibly with a connection @@ -369,7 +396,13 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.galaxy_session.user = user self.sa_session.flush( [ self.galaxy_session ] ) user = property( get_user, set_user ) - + + def user_is_admin( self ): + admin_users = self.app.config.get( "admin_users", "" ).split( "," ) + if self.user and admin_users and self.user.email in admin_users: + return True + return False + def get_toolbox(self): """Returns the application toolbox""" return self.app.toolbox @@ -415,12 +448,12 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): Convenience method for displaying an warn message. See `show_message`. """ return self.show_message( message, 'warning', refresh_frames ) - def show_form( self, form ): + def show_form( self, form, header=None ): """ Convenience method for displaying a simple page with a single HTML form. """ - return self.fill_template( "form.mako", form=form ) + return self.fill_template( "form.mako", form=form, header=header ) def fill_template(self, filename, **kwargs): """ Fill in a template, putting any keyword arguments on the context. diff --git a/lib/galaxy/web/framework/base.py b/lib/galaxy/web/framework/base.py index 6dff0719d2a..5bef6415223 100644 --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -84,15 +84,19 @@ class WebApplication( object ): friendly objects, finds the appropriate method to handle the request and calls it. """ - # Setup the transaction - trans = self.transaction_factory( environ ) # Map url using routes - path_info = trans.request.path_info + path_info = environ.get( 'PATH_INFO', '' ) map = self.mapper.match( path_info ) if map == None: raise httpexceptions.HTTPNotFound( "No route for " + path_info ) - # Save the complete mapper dict, we pop things off so they don't get passed down - raw_map = dict( map ) + # Setup routes + rc = routes.request_config() + rc.mapper = self.mapper + rc.mapper_dict = map + rc.environ = environ + # Setup the transaction + trans = self.transaction_factory( environ ) + rc.redirect = trans.response.send_redirect # Get the controller class controller_name = map.pop( 'controller', None ) controller = self.controllers.get( controller_name, None ) @@ -111,12 +115,6 @@ class WebApplication( object ): # Is the method callable if not callable( method ): raise httpexceptions.HTTPNotFound( "Action not callable for " + path_info ) - # Setup routes - rc = routes.request_config() - rc.mapper = self.mapper - rc.mapper_dict = raw_map - rc.environ = environ - rc.redirect = trans.response.send_redirect # Combine mapper args and query string / form args and call kwargs = trans.request.params.mixed() kwargs.update( map ) @@ -336,4 +334,4 @@ def flatten( seq ): for y in flatten( x, encoding ): yield y else: - yield x \ No newline at end of file + yield x diff --git a/static/scripts/galaxy.panels.js b/static/scripts/galaxy.panels.js index 7463991fbf9..efe2ccb0a2a 100644 --- a/static/scripts/galaxy.panels.js +++ b/static/scripts/galaxy.panels.js @@ -79,7 +79,12 @@ function make_left_panel( panel_el, center_el, border_el ) { } } ).find( "div" ).show();; - + var force_panel = function( op ) { + if ( ( hidden && op == 'show' ) || ( ! hidden && op == 'hide' ) ) { + toggle(); + } + } + return { force_panel: force_panel }; }; function make_right_panel( panel_el, center_el, border_el ) { @@ -173,7 +178,12 @@ function make_right_panel( panel_el, center_el, border_el ) { } } ).find( "div" ).show(); - return { handle_minwidth_hint: handle_minwidth_hint }; + var force_panel = function( op ) { + if ( ( hidden && op == 'show' ) || ( ! hidden && op == 'hide' ) ) { + toggle(); + } + } + return { handle_minwidth_hint: handle_minwidth_hint, force_panel: force_panel }; }; // Modal dialog boxes @@ -234,4 +244,4 @@ function make_popupmenu( button_element, options ) { } ); }; $( button_element ).click( click ); -}; \ No newline at end of file +}; diff --git a/static/scripts/packed/galaxy.panels.js b/static/scripts/packed/galaxy.panels.js index 241d997a112..fd71a3f43d6 100644 --- a/static/scripts/packed/galaxy.panels.js +++ b/static/scripts/packed/galaxy.panels.js @@ -1 +1 @@ -var hidden_width=7;var border_tweak=9;var jq=jQuery;function ensure_dd_helper(){if(jq("#DD-helper").length==0){$("").css({background:"white",opacity:0,zIndex:9000,position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendTo("body").hide()}}function make_left_panel(E,A,B){var D=false;var C=null;resize=function(F){var G=F;if(F<0){F=0}jq(E).css("width",F);jq(B).css("left",G);jq(A).css("left",F+7);if(document.recalc){document.recalc()}};toggle=function(){if(D){jq(B).removeClass("hover");jq(B).animate({left:C},"fast");jq(E).css("left",-C).show().animate({left:0},"fast",function(){resize(C);jq(B).removeClass("hidden")});D=false}else{C=jq(B).position().left;jq(A).css("left",hidden_width);if(document.recalc){document.recalc()}jq(B).removeClass("hover");jq(E).animate({left:-C},"fast");jq(B).animate({left:-1},"fast",function(){jq(this).addClass("hidden")});D=true}};jq(B).hover(function(){jq(this).addClass("hover")},function(){jq(this).removeClass("hover")}).draggable({start:function(F,G){jq("#DD-helper").show()},stop:function(F,G){jq("#DD-helper").hide();return false},drag:function(F,G){x=G.position.left;x=Math.min(400,Math.max(100,x));if(D){jq(E).css("left",0);jq(B).removeClass("hidden");D=false}resize(x);G.position.left=x;G.position.top=$(this).data("draggable").originalPosition.top},click:function(){toggle()}}).find("div").show()}function make_right_panel(A,E,G){var I=false;var F=false;var C=null;var D=function(J){jq(A).css("width",J);jq(E).css("right",J+9);jq(G).css("right",J).css("left","");if(document.recalc){document.recalc()}};var H=function(){if(I){jq(G).removeClass("hover");jq(G).animate({right:C},"fast");jq(A).css("right",-C).show().animate({right:0},"fast",function(){D(C);jq(G).removeClass("hidden")});I=false}else{C=jq(document).width()-jq(G).position().left-border_tweak;jq(E).css("right",hidden_width+1);if(document.recalc){document.recalc()}jq(G).removeClass("hover");jq(A).animate({right:-C},"fast");jq(G).animate({right:-1},"fast",function(){jq(this).addClass("hidden")});I=true}F=false};var B=function(J){var K=jq(E).width()-(I?C:0);if(K
")}$(".dialog-box").find(".body").html(B);if(!$(".dialog-box-container").is(":visible")){$("#overlay").show();$(".dialog-box-container").fadeIn()}}function make_popupmenu(D,B){var A=$("").appendTo("body");$.each(B,function(G,F){$("").html(G).click(F).appendTo(A)});var C=function(){$(A).unbind().hide();$("#popup-helper").unbind().hide()};var E=function(){var F=$(D).offset();$("#popup-helper").mousedown(C).show();$(A).click(C).css({top:-1000}).show().css({top:F.top+$(D).height()+9,left:F.left+$(D).width()-$(A).width()})};$(D).click(E)};
\ No newline at end of file
+var hidden_width=7;var border_tweak=9;var jq=jQuery;function ensure_dd_helper(){if(jq("#DD-helper").length==0){$("").css({background:"white",opacity:0,zIndex:9000,position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendTo("body").hide()}}function make_left_panel(F,B,C){var E=false;var D=null;resize=function(G){var H=G;if(G<0){G=0}jq(F).css("width",G);jq(C).css("left",H);jq(B).css("left",G+7);if(document.recalc){document.recalc()}};toggle=function(){if(E){jq(C).removeClass("hover");jq(C).animate({left:D},"fast");jq(F).css("left",-D).show().animate({left:0},"fast",function(){resize(D);jq(C).removeClass("hidden")});E=false}else{D=jq(C).position().left;jq(B).css("left",hidden_width);if(document.recalc){document.recalc()}jq(C).removeClass("hover");jq(F).animate({left:-D},"fast");jq(C).animate({left:-1},"fast",function(){jq(this).addClass("hidden")});E=true}};jq(C).hover(function(){jq(this).addClass("hover")},function(){jq(this).removeClass("hover")}).draggable({start:function(G,H){jq("#DD-helper").show()},stop:function(G,H){jq("#DD-helper").hide();return false},drag:function(G,H){x=H.position.left;x=Math.min(400,Math.max(100,x));if(E){jq(F).css("left",0);jq(C).removeClass("hidden");E=false}resize(x);H.position.left=x;H.position.top=$(this).data("draggable").originalPosition.top},click:function(){toggle()}}).find("div").show();var A=function(G){if((E&&G=="show")||(!E&&G=="hide")){toggle()}};return{force_panel:A}}function make_right_panel(A,E,H){var J=false;var G=false;var C=null;var D=function(K){jq(A).css("width",K);jq(E).css("right",K+9);jq(H).css("right",K).css("left","");if(document.recalc){document.recalc()}};var I=function(){if(J){jq(H).removeClass("hover");jq(H).animate({right:C},"fast");jq(A).css("right",-C).show().animate({right:0},"fast",function(){D(C);jq(H).removeClass("hidden")});J=false}else{C=jq(document).width()-jq(H).position().left-border_tweak;jq(E).css("right",hidden_width+1);if(document.recalc){document.recalc()}jq(H).removeClass("hover");jq(A).animate({right:-C},"fast");jq(H).animate({right:-1},"fast",function(){jq(this).addClass("hidden")});J=true}G=false};var B=function(K){var L=jq(E).width()-(J?C:0);if(L
")}$(".dialog-box").find(".body").html(B);if(!$(".dialog-box-container").is(":visible")){$("#overlay").show();$(".dialog-box-container").fadeIn()}}function make_popupmenu(D,B){var A=$("").appendTo("body");$.each(B,function(G,F){$("").html(G).click(F).appendTo(A)});var C=function(){$(A).unbind().hide();$("#popup-helper").unbind().hide()};var E=function(){var F=$(D).offset();$("#popup-helper").mousedown(C).show();$(A).click(C).css({top:-1000}).show().css({top:F.top+$(D).height()+9,left:F.left+$(D).width()-$(A).width()})};$(D).click(E)};
\ No newline at end of file
diff --git a/static/scripts/packed/galaxy.ui.scrollPanel.js b/static/scripts/packed/galaxy.ui.scrollPanel.js
index b16bbeeca48..fd0e8056d1d 100644
--- a/static/scripts/packed/galaxy.ui.scrollPanel.js
+++ b/static/scripts/packed/galaxy.ui.scrollPanel.js
@@ -1 +1 @@
-$.ui.plugin.add("draggable","scrollPanel",{drag:function(G,H){var J=$(this).data("draggable");clearTimeout(J.timeout);var B=H.options,D=J.element,A=B.panel,C=A.position(),I=A.width(),E=A.height();viewport=A.parent();viewport_w=viewport.width(),viewport_h=viewport.height(),element_w=D.width(),element_h=D.height(),moved=false,close=5,nudge=23,p_min_x=-(I-viewport_w),p_min_y=-(E-viewport_h),p_max_x=0,p_max_y=0,min_vis_x=-C.left,max_vis_x=min_vis_x+viewport_w,min_vis_y=-C.top,max_vis_y=min_vis_y+viewport_h,mouse_x=H.position.left+J.offset.click.left;mouse_y=H.position.top+J.offset.click.top;if((C.left