Skip to content

Commit

Permalink
Merge pull request #2515 from bgruening/realtime_communication
Browse files Browse the repository at this point in the history
Add a simple Galaxy Communication Server for realtime communication between Galaxy users.
  • Loading branch information
bgruening authored Jul 23, 2016
2 parents 4752220 + 6b8b0cd commit b0566ad
Show file tree
Hide file tree
Showing 31 changed files with 1,499 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ doc/build
.DS_Store
*.rej
*~
.idea/
98 changes: 98 additions & 0 deletions client/galaxy/scripts/layout/generic-nav-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/** Real-time Communication feature **/
define(['mvc/ui/ui-modal'], function( Modal ) {

var GenericNavView = Backbone.View.extend({

initialize: function ( ) {
this.modal = null;
},

/** makes bootstrap modal and iframe inside it */
makeModalIframe: function( e ) {
// make modal
var host = window.Galaxy.config.communication_server_host,
port = window.Galaxy.config.communication_server_port,
username = escape( window.Galaxy.user.attributes.username ),
persistent_communication_rooms = escape( window.Galaxy.config.persistent_communication_rooms ),
query_string = "?username=" + username + "&persistent_communication_rooms=" + persistent_communication_rooms,
src = host + ":" + port + query_string,
$el_chat_modal_header = null,
$el_chat_modal_body = null,
iframe_template = '<iframe class="f-iframe fade in communication-iframe" src="' + src + '"> </iframe>',
header_template = '<i class="fa fa-comment" aria-hidden="true" title="Communicate with other users"></i>' +
'<i class="fa fa-expand expand-compress-modal" aria-hidden="true" title="Maximize"></i>' +
'<i class="fa fa-times close-modal" aria-hidden="true" title="Close"></i>',
frame_height = 350,
frame_width = 600,
class_names = 'ui-modal chat-modal';

// deletes the chat modal if already present and create one
if( $( '.chat-modal' ).length > 0 ) {
$( '.chat-modal' ).remove();
}
// creates a modal
GenericNavView.modal = new Modal.View({
body : iframe_template,
height : frame_height,
width : frame_width,
closing_events : true,
title_separator : false,
cls : class_names
});

// shows modal
GenericNavView.modal.show();
$el_chat_modal_header = $( '.chat-modal .modal-header' );
$el_chat_modal_body = $( '.chat-modal .modal-body' );
// adjusts the css of bootstrap modal for chat
$el_chat_modal_header.addClass('modal-header-body');
$el_chat_modal_body.addClass('modal-header-body');
$el_chat_modal_header.find( 'h4' ).remove();
$el_chat_modal_header.removeAttr( 'min-height padding border' );
$el_chat_modal_header.append( header_template );
// click event of the close button for chat
$( '.close-modal' ).click(function( e ) {
$( '.chat-modal' ).css( 'display', 'none' );
});
// click event of expand and compress icon
$( '.expand-compress-modal' ).click(function( e ) {
if( $( '.expand-compress-modal' ).hasClass( 'fa-expand' ) ) {
$( '.chat-modal .modal-dialog' ).width( '1000px' );
$( '.chat-modal .modal-body' ).height( '575px' );
$( '.expand-compress-modal' ).removeClass( 'fa-expand' ).addClass( 'fa-compress' );
$( '.expand-compress-modal' ).attr('title', 'Minimize');
$( '.expand-compress-modal' ).css('margin-left', '96.2%');
}
else {
$( '.chat-modal .modal-dialog' ).width( frame_width + 'px' );
$( '.chat-modal .modal-body' ).height( frame_height + 'px' );
$( '.expand-compress-modal' ).removeClass( 'fa-compress' ).addClass( 'fa-expand' );
$( '.expand-compress-modal' ).attr('title', 'Maximize');
$( '.expand-compress-modal' ).css('margin-left', '93.2%');
}

});
return this;
},

/**renders the chat icon as a nav item*/
render: function() {
var self = this,
navItem = {};
navItem = {
id : 'show-chat-online',
icon : 'fa-comment-o',
tooltip : 'Chat online',
visible : false,
onclick : self.makeModalIframe
}
return navItem;
}
});

return {
GenericNavView : GenericNavView
};

});

8 changes: 7 additions & 1 deletion client/galaxy/scripts/layout/menu.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Masthead Collection **/
define(['mvc/tours'], function( Tours ) {
define(['mvc/tours', 'layout/generic-nav-view'], function( Tours, GenericNav ) {
var Collection = Backbone.Collection.extend({
model: Backbone.Model.extend({
defaults: {
Expand All @@ -11,6 +11,12 @@ var Collection = Backbone.Collection.extend({
options = options || {};
this.reset();

//
// Chat server tab
//
var extendedNavItem = new GenericNav.GenericNavView();
this.add(extendedNavItem.render());

//
// Analyze data tab.
//
Expand Down
31 changes: 30 additions & 1 deletion client/galaxy/scripts/layout/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({
this.renderMessageBox();
this.renderInactivityBox();
this.renderPanels();
this._checkCommunicationServerOnline();
return this;
},

Expand Down Expand Up @@ -131,7 +132,35 @@ var PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({
}
},

toString : function() { return 'PageLayoutView'; }
toString : function() { return 'PageLayoutView'; },

/** Check if the communication server is online and show the icon otherwise hide the icon */
_checkCommunicationServerOnline: function(){
var host = window.Galaxy.config.communication_server_host,
port = window.Galaxy.config.communication_server_port,
$chat_icon_element = $( "#show-chat-online" );
/** Check if the user has deactivated the communication in it's personal settings */
if (window.Galaxy.user.attributes.preferences !== undefined && window.Galaxy.user.attributes.preferences.communication_server === '1') {
// See if the configured communication server is available
$.ajax({
url: host + ":" + port,
})
.success( function( data ) {
// enable communication only when a user is logged in
if( window.Galaxy.user.id !== null ) {
if( $chat_icon_element.css( "visibility") === "hidden" ) {
$chat_icon_element.css( "visibility", "visible" );
}
}
})
.error( function( data ) {
// hide the communication icon if the communication server is not available
$chat_icon_element.css( "visibility", "hidden" );
});
} else {
$chat_icon_element.css( "visibility", "hidden" );
}
},
});

// ============================================================================
Expand Down
26 changes: 26 additions & 0 deletions client/galaxy/style/less/base.less
Original file line number Diff line number Diff line change
Expand Up @@ -1719,3 +1719,29 @@ div.toolTitleNoSection
#for_bears {
display: none;
}

// communication channel bootstrap modal

.chat-modal {
overflow: hidden;
}

.modal-header-body {
padding: 2px;
}

.communication-iframe {
width: 100%;
height: 100%;
}

.close-modal {
float: right;
cursor: pointer;
}

.expand-compress-modal {
cursor: pointer;
font-size: 12px;
margin-left: 93.2%;
}
6 changes: 6 additions & 0 deletions config/galaxy.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,12 @@ use_interactive = True

#amqp_internal_connection = sqlalchemy+sqlite:///./database/control.sqlite?isolation_level=IMMEDIATE

# Galaxy real time communication server settings
#enable_communication_server = False
#communication_server_host = http://localhost
#communication_server_port = 7070
# persistent_communication_rooms is a comma-separated list of rooms that should be always available.
#persistent_communication_rooms =


# ---- Galaxy External Message Queue -------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions lib/galaxy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ def __init__( self, **kwargs ):
self.track_jobs_in_database = string_as_bool( kwargs.get( 'track_jobs_in_database', 'True') )
self.start_job_runners = listify(kwargs.get( 'start_job_runners', '' ))
self.expose_dataset_path = string_as_bool( kwargs.get( 'expose_dataset_path', 'False' ) )
self.enable_communication_server = string_as_bool( kwargs.get( 'enable_communication_server', 'False' ) )
self.communication_server_host = kwargs.get( 'communication_server_host', 'http://localhost' )
self.communication_server_port = int( kwargs.get( 'communication_server_port', '7070' ) )
self.persistent_communication_rooms = listify( kwargs.get( "persistent_communication_rooms", [] ), do_strip=True )
# External Service types used in sample tracking
self.external_service_type_path = resolve_path( kwargs.get( 'external_service_type_path', 'external_service_types' ), self.root )
# Tasked job runner.
Expand Down
3 changes: 3 additions & 0 deletions lib/galaxy/managers/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def _defaults_to( default ):
'biostar_url_redirect' : lambda *a, **c: self.url_for( controller='biostar', action='biostar_redirect',
qualified=True ),

'communication_server_host' : _defaults_to( None ),
'communication_server_port' : _defaults_to( None ),
'persistent_communication_rooms' : _defaults_to( None ),
'allow_user_creation' : _defaults_to( False ),
'use_remote_user' : _defaults_to( None ),
'remote_user_logout_href' : _defaults_to( '' ),
Expand Down
8 changes: 7 additions & 1 deletion lib/galaxy/managers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def get_or_create_valid_api_key( self, user ):
return self.create_api_key( self, user )

# ---- preferences
def preferences( self, user ):
log.warn(dict( (key, value) for key, value in user.preferences.items() ))
return dict( (key, value) for key, value in user.preferences.items() )

# ---- roles and permissions
def private_role( self, user ):
return self.app.security_agent.get_private_user_role( user )
Expand Down Expand Up @@ -263,7 +267,7 @@ def __init__( self, app ):
'purged',
# 'active',

# 'preferences',
'preferences',
# all tags
'tags_used',
# all annotations
Expand All @@ -280,6 +284,8 @@ def add_serializers( self ):
'update_time' : self.serialize_date,
'is_admin' : lambda i, k, **c: self.user_manager.is_admin( i ),

'preferences' : lambda i, k, **c: self.user_manager.preferences( i ),

'total_disk_usage' : lambda i, k, **c: float( i.total_disk_usage ),
'quota_percent' : lambda i, k, **c: self.user_manager.quota( i ),

Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ def asbool(obj):


def string_as_bool( string ):
if str( string ).lower() in ( 'true', 'yes', 'on' ):
if str( string ).lower() in ( 'true', 'yes', 'on', '1' ):
return True
else:
return False
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/web/base/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class JSAppLauncher( BaseUIController ):
DEFAULT_ENTRY_FN = "app"
#: keys used when serializing current user for bootstrapped data
USER_BOOTSTRAP_KEYS = ( 'id', 'email', 'username', 'is_admin', 'tags_used', 'requests',
'total_disk_usage', 'nice_total_disk_usage', 'quota_percent' )
'total_disk_usage', 'nice_total_disk_usage', 'quota_percent', 'preferences' )

def __init__( self, app ):
super( JSAppLauncher, self ).__init__( app )
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/web/framework/middleware/remoteuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def __call__( self, environ, start_response ):
'/user/dbkeys',
'/user/toolbox_filters',
'/user/set_default_permissions',
'/user/change_communication',
)

admin_accessible_paths = (
Expand Down
41 changes: 41 additions & 0 deletions lib/galaxy/webapps/galaxy/controllers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from galaxy import model
from galaxy import util
from galaxy import web
from galaxy.util import string_as_bool
from galaxy.exceptions import ObjectInvalid
from galaxy.security.validate_user_input import (transform_publicname,
validate_email,
Expand Down Expand Up @@ -1189,6 +1190,46 @@ def change_password( self, trans, token=None, **kwd):
display_top=kwd.get('redirect_home', False)
)

@web.expose
def change_communication( self, trans, cntrller, **kwd):
"""
Provides a form with which the user can activate/deactivate
the commnication server.
"""
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
activated = ''

if user_id and is_admin:
user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( user_id ) )
else:
user = trans.user

if user and params.get( 'change_communication_button', False ):
communication_enabled = kwd.get( 'enable_communication_server', False )
if communication_enabled:
activated = 'checked'
user.preferences['communication_server'] = True
message = 'Your communication settings has been updated and activated.'
else:
activated = ''
user.preferences['communication_server'] = False
message = 'Your communication settings has been updated and deactivated.'
trans.sa_session.add( user )
trans.sa_session.flush()
else:
if string_as_bool( user.preferences.get('communication_server', '0') ):
activated = 'checked'

return trans.fill_template( '/user/communication_settings.mako',
cntrller=cntrller,
status=status,
activated=activated,
message=message )

@web.expose
def reset_password( self, trans, email=None, **kwd ):
"""Reset the user's password. Send an email with token that allows a password change."""
Expand Down
Loading

0 comments on commit b0566ad

Please sign in to comment.