mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'dev' into admin-docs-from-hub
This commit is contained in:
@@ -68,15 +68,6 @@ window.app = function app( options, bootstrapped ){
|
||||
Galaxy.currHistoryPanel = historyPanel.historyView;
|
||||
Galaxy.currHistoryPanel.listenToGalaxy( Galaxy );
|
||||
|
||||
//HACK: move there
|
||||
Galaxy.app = {
|
||||
display : function( view, target ){
|
||||
// TODO: Remove this line after select2 update
|
||||
$( '.select2-hidden-accessible' ).remove();
|
||||
centerPanel.display( view );
|
||||
},
|
||||
};
|
||||
|
||||
// .................................................... routes
|
||||
/** */
|
||||
Galaxy.router = new ( Backbone.Router.extend({
|
||||
@@ -86,6 +77,17 @@ window.app = function app( options, bootstrapped ){
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
/** helper to push a new navigation state */
|
||||
push: function( url, data ) {
|
||||
data = data || {};
|
||||
data.__identifer = Math.random().toString( 36 ).substr( 2 );
|
||||
if ( !$.isEmptyObject( data ) ) {
|
||||
url += url.indexOf( '?' ) == -1 ? '?' : '&';
|
||||
url += $.param( data , true );
|
||||
}
|
||||
this.navigate( url, { 'trigger': true } );
|
||||
},
|
||||
|
||||
/** override to parse query string into obj and send to each route */
|
||||
execute: function( callback, args, name ){
|
||||
Galaxy.debug( 'router execute:', callback, args, name );
|
||||
@@ -101,7 +103,8 @@ window.app = function app( options, bootstrapped ){
|
||||
// TODO: remove annoying 'root' from root urls
|
||||
'(/)root*' : 'home',
|
||||
'(/)tours(/)(:tour_id)' : 'show_tours',
|
||||
'(/)users(/)' : 'show_users',
|
||||
'(/)user(/)' : 'show_user',
|
||||
'(/)user(/)(:form_id)' : 'show_user_form',
|
||||
},
|
||||
|
||||
show_tours : function( tour_id ){
|
||||
@@ -112,10 +115,14 @@ window.app = function app( options, bootstrapped ){
|
||||
}
|
||||
},
|
||||
|
||||
show_users : function(){
|
||||
show_user : function(){
|
||||
centerPanel.display( new UserPreferences.View() );
|
||||
},
|
||||
|
||||
show_user_form : function( form_id ) {
|
||||
centerPanel.display( new UserPreferences.Forms( { form_id: form_id, user_id: Galaxy.params.id } ) );
|
||||
},
|
||||
|
||||
/** */
|
||||
home : function( params ){
|
||||
// TODO: to router, remove Globals
|
||||
|
||||
@@ -17,6 +17,59 @@ function display_spinner(){
|
||||
$('#main').append('<img id="spinner" src="' + galaxy_root + 'static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a URL for a boolean true/false and call a callback when done.
|
||||
*/
|
||||
function load_when_ready(url, success_callback){
|
||||
var request_count = 0;
|
||||
var timeout_time = 1000;
|
||||
var timeout_time_max = 15000;
|
||||
var timeout_time_step = 1000;
|
||||
var timeout = function(){
|
||||
$.ajax({
|
||||
url: url,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
type: "GET",
|
||||
timeout: 500,
|
||||
dataType: "json",
|
||||
success: function(data){
|
||||
if(data == true){
|
||||
console.log("Galaxy reports IE container ready, returning");
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
success_callback();
|
||||
}else if(data == false){
|
||||
if(request_count == 0){
|
||||
display_spinner();
|
||||
toastr.info(
|
||||
"Galaxy is launching a container in which to run this interactive environment. Please wait...",
|
||||
{'closeButton': true, 'tapToDismiss': false}
|
||||
);
|
||||
}
|
||||
request_count++;
|
||||
if(timeout_time < timeout_time_max){
|
||||
timeout_time += timeout_time_step;
|
||||
}
|
||||
console.log("Readiness request " + request_count + " sleeping " + timeout_time / 1000 + "s");
|
||||
window.setTimeout(timeout, timeout_time)
|
||||
}else{
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
toastr.error(
|
||||
"Galaxy failed to launch a container in which to run this interactive environment, contact your administrator.",
|
||||
"Error",
|
||||
{'closeButton': true, 'tapToDismiss': false}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
window.setTimeout(timeout, timeout_time);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Test availability of a URL, and call a callback when done.
|
||||
@@ -43,7 +96,7 @@ function test_ie_availability(url, success_callback){
|
||||
},
|
||||
error: function(jqxhr, status, error){
|
||||
request_count++;
|
||||
console.log("Request " + request_count);
|
||||
console.log("Availability request " + request_count);
|
||||
if(request_count > 30){
|
||||
clearInterval(interval);
|
||||
clear_main_area();
|
||||
|
||||
@@ -247,10 +247,14 @@ var Collection = Backbone.Collection.extend({
|
||||
title : 'Logged in as ' + Galaxy.user.get( 'email' )
|
||||
},{
|
||||
title : 'Preferences',
|
||||
url : 'users',
|
||||
url : 'user',
|
||||
target : 'galaxy_main',
|
||||
onclick : function() {
|
||||
window.location = Galaxy.root + 'users';
|
||||
if ( Galaxy.router ) {
|
||||
Galaxy.router.push( 'user' );
|
||||
} else {
|
||||
window.location = Galaxy.root + 'user';
|
||||
}
|
||||
}
|
||||
},{
|
||||
title : 'Custom Builds',
|
||||
|
||||
@@ -218,14 +218,7 @@ var DatasetListItemEdit = _super.extend(
|
||||
faIcon : 'fa-refresh',
|
||||
onclick : function( ev ) {
|
||||
ev.preventDefault();
|
||||
// create webpack split point in order to load the tool form async
|
||||
// TODO: split not working (tool loads fine)
|
||||
require([ 'mvc/tool/tool-form' ], function( ToolForm ){
|
||||
var form = new ToolForm.View({ 'job_id' : creating_job });
|
||||
form.deferred.execute( function(){
|
||||
Galaxy.app.display( form );
|
||||
});
|
||||
});
|
||||
Galaxy.router.push( '/', { job_id : creating_job } );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ return {
|
||||
for (i in options.global_actions) {
|
||||
var action = options.global_actions[i];
|
||||
var label_cls = '';
|
||||
if (action.inbound) {
|
||||
if (action.target == 'inbound') {
|
||||
label_cls = 'use-inbound'
|
||||
} else {
|
||||
label_cls = 'use-outbound'
|
||||
@@ -179,7 +179,7 @@ return {
|
||||
// load attributes
|
||||
var link = column_settings.link;
|
||||
var value = column_settings.value;
|
||||
var inbound = column_settings.inbound;
|
||||
var target = column_settings.target;
|
||||
|
||||
// unescape value
|
||||
if (jQuery.type( value ) === 'string') {
|
||||
@@ -206,14 +206,7 @@ return {
|
||||
if (options.operations.length != 0) {
|
||||
tmpl += '<div id="' + id + '" class="' + cls + '" style="float: left;">';
|
||||
}
|
||||
|
||||
var label_class = '';
|
||||
if (inbound) {
|
||||
label_class = 'use-inbound';
|
||||
} else {
|
||||
label_class = 'use-outbound';
|
||||
}
|
||||
tmpl += '<a class="menubutton-label ' + label_class + '" href="' + link + '" onclick="return false;">' + value + '</a>';
|
||||
tmpl += '<a class="menubutton-label use-target" target="' + target + '" href="' + link + '" onclick="return false;">' + value + '</a>';
|
||||
if (options.operations.length != 0) {
|
||||
tmpl += '</div>';
|
||||
}
|
||||
|
||||
@@ -209,26 +209,17 @@ return Backbone.View.extend({
|
||||
//
|
||||
// add inbound/outbound events
|
||||
//
|
||||
this.$el.find('.use-inbound').each( function() {
|
||||
this.$el.find('.use-target').each( function() {
|
||||
$(this).click( function(e) {
|
||||
self.execute({
|
||||
href : $(this).attr('href'),
|
||||
inbound : true
|
||||
target : $(this).attr('target')
|
||||
});
|
||||
return false;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
this.$el.find('.use-outbound').each( function() {
|
||||
$(this).click( function(e) {
|
||||
self.execute({
|
||||
href : $(this).attr('href')
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// empty grid?
|
||||
var items_length = options.items.length;
|
||||
if (items_length == 0) {
|
||||
@@ -266,8 +257,7 @@ return Backbone.View.extend({
|
||||
html : operation['label'],
|
||||
href : operation_settings['url_args'],
|
||||
target : operation_settings['target'],
|
||||
confirmation_text : operation['confirm'],
|
||||
inbound : operation['inbound']
|
||||
confirmation_text : operation['confirm']
|
||||
};
|
||||
|
||||
// add popup function
|
||||
@@ -506,17 +496,16 @@ return Backbone.View.extend({
|
||||
var href = null;
|
||||
var operation = null;
|
||||
var confirmation_text = null;
|
||||
var inbound = null;
|
||||
var target = null;
|
||||
|
||||
// check for options
|
||||
if (options)
|
||||
{
|
||||
if (options) {
|
||||
// get options
|
||||
href = options.href;
|
||||
operation = options.operation;
|
||||
id = options.id;
|
||||
confirmation_text = options.confirmation_text;
|
||||
inbound = options.inbound;
|
||||
target = options.target;
|
||||
|
||||
// check if input contains the operation tag
|
||||
if (href !== undefined && href.indexOf('operation=') != -1) {
|
||||
@@ -559,7 +548,7 @@ return Backbone.View.extend({
|
||||
if (this.grid.can_async_op(operation)) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(inbound, href);
|
||||
this.go_to(target, href);
|
||||
}
|
||||
|
||||
// done
|
||||
@@ -568,7 +557,7 @@ return Backbone.View.extend({
|
||||
|
||||
// refresh grid
|
||||
if (href) {
|
||||
this.go_to(inbound, href);
|
||||
this.go_to(target, href);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -576,7 +565,7 @@ return Backbone.View.extend({
|
||||
if (this.grid.get('async')) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(inbound, href);
|
||||
this.go_to(target, href);
|
||||
}
|
||||
|
||||
// done
|
||||
@@ -584,7 +573,7 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// go to url
|
||||
go_to: function (inbound, href) {
|
||||
go_to: function (target, href) {
|
||||
// get aysnc status
|
||||
var async = this.grid.get('async');
|
||||
this.grid.set('async', false);
|
||||
@@ -604,17 +593,21 @@ return Backbone.View.extend({
|
||||
item_ids: undefined,
|
||||
async: async
|
||||
});
|
||||
|
||||
if (inbound) {
|
||||
// this currently assumes that there is only a single grid shown at a time
|
||||
var $div = $('.grid-header').closest('.inbound');
|
||||
if ($div.length !== 0) {
|
||||
$div.load(href);
|
||||
return;
|
||||
}
|
||||
switch (target) {
|
||||
case 'inbound':
|
||||
// this currently assumes that there is only a single grid shown at a time
|
||||
var $div = $('.grid-header').closest('.inbound');
|
||||
if ($div.length !== 0) {
|
||||
$div.load(href);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'top':
|
||||
window.top.location = href;
|
||||
break;
|
||||
default:
|
||||
window.location = href;
|
||||
}
|
||||
|
||||
window.location = href;
|
||||
},
|
||||
|
||||
// Update grid.
|
||||
|
||||
@@ -532,10 +532,7 @@ var ToolLinkView = BaseView.extend({
|
||||
var self = this;
|
||||
$link.find('a').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );
|
||||
form.deferred.execute(function() {
|
||||
Galaxy.app.display( form );
|
||||
});
|
||||
Galaxy.router.push( '/', { tool_id : self.model.id, version : self.model.get('version') } );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
/** User Preferences view */
|
||||
define( [ 'mvc/form/form-view', 'mvc/ui/ui-misc' ], function( Form, Ui ) {
|
||||
|
||||
var View = Backbone.View.extend({
|
||||
|
||||
initialize: function() {
|
||||
this.defs = {
|
||||
/** Contains descriptive dictionaries describing user forms */
|
||||
var Model = Backbone.Model.extend({
|
||||
initialize: function( options ) {
|
||||
options = options || {};
|
||||
options.user_id = options.user_id || Galaxy.user.id;
|
||||
this.set({
|
||||
'user_id' : options.user_id,
|
||||
'information': {
|
||||
title : 'Manage information',
|
||||
description : 'Edit your email, addresses and custom parameters or change your username.',
|
||||
url : 'api/users/' + Galaxy.user.id + '/information/inputs',
|
||||
url : 'api/users/' + options.user_id + '/information/inputs',
|
||||
icon : 'fa-user'
|
||||
},
|
||||
'password': {
|
||||
title : 'Change password',
|
||||
description : 'Allows you to change your login credentials.',
|
||||
icon : 'fa-unlock-alt',
|
||||
url : 'api/users/' + Galaxy.user.id + '/password/inputs',
|
||||
url : 'api/users/' + options.user_id + '/password/inputs',
|
||||
submit_title : 'Save password',
|
||||
},
|
||||
'communication': {
|
||||
title : 'Change communication settings',
|
||||
description : 'Enable or disable the communication feature to chat with other users.',
|
||||
url : 'api/users/' + Galaxy.user.id + '/communication/inputs',
|
||||
url : 'api/users/' + options.user_id + '/communication/inputs',
|
||||
icon : 'fa-comments-o'
|
||||
},
|
||||
'permissions': {
|
||||
title : 'Set dataset permissions for new histories',
|
||||
description : 'Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored.',
|
||||
url : 'api/users/' + Galaxy.user.id + '/permissions/inputs',
|
||||
url : 'api/users/' + options.user_id + '/permissions/inputs',
|
||||
icon : 'fa-users',
|
||||
submit_title : 'Save permissions'
|
||||
},
|
||||
'api_key': {
|
||||
title : 'Manage API key',
|
||||
description : 'Access your current API key or create a new one.',
|
||||
url : 'api/users/' + Galaxy.user.id + '/api_key/inputs',
|
||||
url : 'api/users/' + options.user_id + '/api_key/inputs',
|
||||
icon : 'fa-key',
|
||||
submit_title : 'Create a new key',
|
||||
submit_icon : 'fa-check'
|
||||
@@ -42,7 +45,7 @@ define( [ 'mvc/form/form-view', 'mvc/ui/ui-misc' ], function( Form, Ui ) {
|
||||
'toolbox_filters': {
|
||||
title : 'Manage Toolbox filters',
|
||||
description : 'Customize your Toolbox by displaying or omitting sets of Tools.',
|
||||
url : 'api/users/' + Galaxy.user.id + '/toolbox_filters/inputs',
|
||||
url : 'api/users/' + options.user_id + '/toolbox_filters/inputs',
|
||||
icon : 'fa-filter',
|
||||
submit_title : 'Save filters'
|
||||
},
|
||||
@@ -69,7 +72,15 @@ define( [ 'mvc/form/form-view', 'mvc/ui/ui-misc' ], function( Form, Ui ) {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/** View of the main user preference panel with links to individual user forms */
|
||||
var View = Backbone.View.extend({
|
||||
|
||||
initialize: function() {
|
||||
this.model = new Model();
|
||||
this.message = new Ui.Message();
|
||||
this.setElement( '<div/>' );
|
||||
this.render();
|
||||
@@ -85,93 +96,39 @@ define( [ 'mvc/form/form-view', 'mvc/ui/ui-misc' ], function( Form, Ui ) {
|
||||
.append( $( '<p/>' ).append( 'You are logged in as <strong>' + _.escape( data.email ) + '</strong>.' ) )
|
||||
.append( self.$table = $( '<table/>' ).addClass( 'ui-panel-table' ) );
|
||||
if( !config.use_remote_user ) {
|
||||
self._link( self.defs.information );
|
||||
self._link( self.defs.password );
|
||||
self._addLink( 'information' );
|
||||
self._addLink( 'password' );
|
||||
}
|
||||
if( config.enable_communication_server ) {
|
||||
self._link( self.defs.communication );
|
||||
self._addLink( 'communication' );
|
||||
}
|
||||
self._link( self.defs.permissions );
|
||||
self._link( self.defs.api_key );
|
||||
self._addLink( 'permissions' );
|
||||
self._addLink( 'api_key' );
|
||||
if( config.has_user_tool_filters ) {
|
||||
self._link( self.defs.toolbox_filters );
|
||||
self._addLink( 'toolbox_filters' );
|
||||
}
|
||||
if( config.enable_openid && !config.use_remote_user ) {
|
||||
self._link( self.defs.openids );
|
||||
self._addLink( 'openids' );
|
||||
}
|
||||
self._link( self.defs.logout );
|
||||
self._addLink( 'logout' );
|
||||
self.$preferences.append( self._templateFooter( data ) );
|
||||
self.$el.empty().append( self.$preferences );
|
||||
});
|
||||
},
|
||||
|
||||
_link: function( page ) {
|
||||
var self = this;
|
||||
var $page_item = $( this._templateRow( page ) );
|
||||
this.$table.append( $page_item );
|
||||
$page_item.find( 'a' ).on( 'click', function() {
|
||||
if ( page.url ) {
|
||||
$.ajax({
|
||||
url : Galaxy.root + page.url,
|
||||
type : 'GET'
|
||||
}).done( function( response ) {
|
||||
var options = $.extend( {}, page, response );
|
||||
var form = new Form({
|
||||
title : options.title,
|
||||
icon : options.icon,
|
||||
inputs : options.inputs,
|
||||
operations: {
|
||||
'submit': new Ui.ButtonIcon({
|
||||
tooltip : options.submit_tooltip,
|
||||
title : options.submit_title || 'Save settings',
|
||||
icon : options.submit_icon || 'fa-save',
|
||||
onclick : function() { self._submit( form, options ) }
|
||||
}),
|
||||
'back': new Ui.ButtonIcon({
|
||||
icon : 'fa-caret-left',
|
||||
tooltip : 'Return to user preferences',
|
||||
title : 'Preferences',
|
||||
onclick : function() { form.remove(); self.$preferences.show(); }
|
||||
})
|
||||
}
|
||||
});
|
||||
self.$preferences.hide();
|
||||
self.$el.append( form.$el );
|
||||
}).fail( function( response ) {
|
||||
self.message.update( { message: 'Failed to load resource ' + page.url + '.', status: 'danger' } );
|
||||
})
|
||||
} else {
|
||||
page.onclick();
|
||||
}
|
||||
});
|
||||
_addLink: function( action ) {
|
||||
var options = this.model.get( action );
|
||||
var $row = $( this._templateLink( options ) );
|
||||
var $a = $row.find( 'a' );
|
||||
if ( options.onclick ) {
|
||||
$a.on( 'click', function() { options.onclick() } );
|
||||
} else {
|
||||
$a.attr( 'href', Galaxy.root + 'user/' + action );
|
||||
}
|
||||
this.$table.append( $row );
|
||||
},
|
||||
|
||||
_submit: function( form, options ) {
|
||||
var self = this;
|
||||
$.ajax( {
|
||||
url : options.url,
|
||||
data : JSON.stringify(form.data.create()),
|
||||
type : 'PUT',
|
||||
contentType : 'application/json'
|
||||
}).done( function( response ) {
|
||||
var updated_values = false;
|
||||
form.data.matchModel( response, function ( input, input_id ) {
|
||||
form.field_list[ input_id ].value( input.value );
|
||||
updated_values = true;
|
||||
});
|
||||
if ( updated_values ) {
|
||||
form.message.update( { message: response.message, status: 'success' } );
|
||||
} else {
|
||||
form.remove();
|
||||
self.$preferences.show();
|
||||
self.message.update( { message: response.message, status: 'success' } );
|
||||
}
|
||||
}).fail( function( response ) {
|
||||
form.message.update( { message: response.responseJSON.err_msg, status: 'danger' } );
|
||||
});
|
||||
},
|
||||
|
||||
_templateRow: function( options ) {
|
||||
_templateLink: function( options ) {
|
||||
return '<tr>' +
|
||||
'<td>' +
|
||||
'<div class="ui-panel-icon fa ' + options.icon + '">' +
|
||||
@@ -192,7 +149,69 @@ define( [ 'mvc/form/form-view', 'mvc/ui/ui-misc' ], function( Form, Ui ) {
|
||||
}
|
||||
});
|
||||
|
||||
/** View of individual user forms */
|
||||
var Forms = Backbone.View.extend({
|
||||
|
||||
initialize: function( options ) {
|
||||
this.model = new Model( options );
|
||||
this.page = this.model.get( options.form_id );
|
||||
this.setElement( '<div/>' );
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
$.ajax({
|
||||
url : Galaxy.root + this.page.url,
|
||||
type : 'GET'
|
||||
}).done( function( response ) {
|
||||
var options = $.extend( {}, self.page, response );
|
||||
var form = new Form({
|
||||
title : options.title,
|
||||
icon : options.icon,
|
||||
inputs : options.inputs,
|
||||
operations: {
|
||||
'submit': new Ui.ButtonIcon({
|
||||
tooltip : options.submit_tooltip,
|
||||
title : options.submit_title || 'Save settings',
|
||||
icon : options.submit_icon || 'fa-save',
|
||||
onclick : function() { self._submit( form, options ) }
|
||||
})
|
||||
}
|
||||
});
|
||||
self.$el.empty().append( form.$el );
|
||||
}).fail( function( response ) {
|
||||
self.$el.empty().append( new Ui.Message({
|
||||
message : 'Failed to load resource ' + self.page.url + '.',
|
||||
status : 'danger',
|
||||
persistent : true
|
||||
}).$el );
|
||||
});
|
||||
},
|
||||
|
||||
_submit: function( form, options ) {
|
||||
var self = this;
|
||||
$.ajax( {
|
||||
url : Galaxy.root + options.url,
|
||||
data : JSON.stringify( form.data.create() ),
|
||||
type : 'PUT',
|
||||
contentType : 'application/json'
|
||||
}).done( function( response ) {
|
||||
var updated_values = false;
|
||||
form.data.matchModel( response, function ( input, input_id ) {
|
||||
form.field_list[ input_id ].value( input.value );
|
||||
updated_values = true;
|
||||
});
|
||||
form.message.update( { message: response.message, status: 'success' } );
|
||||
}).fail( function( response ) {
|
||||
window.console.log( response );
|
||||
form.message.update( { message: response.responseJSON.err_msg, status: 'danger' } );
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
View: View
|
||||
View : View,
|
||||
Forms : Forms
|
||||
};
|
||||
});
|
||||
|
||||
@@ -236,6 +236,8 @@
|
||||
<datatype extension="ct" type="galaxy.datatypes.tabular:ConnectivityTable" display_in_upload="true"/>
|
||||
<datatype extension="searchgui_archive" type="galaxy.datatypes.binary:SearchGuiArchive" display_in_upload="true"/>
|
||||
<datatype extension="peptideshaker_archive" type="galaxy.datatypes.binary:CompressedArchive" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="percin" type="galaxy.datatypes.tabular:Tabular" subclass="true" />
|
||||
<datatype extension="percout" type="galaxy.datatypes.xml:GenericXml" subclass="true" />
|
||||
<!-- End Proteomics Datatypes -->
|
||||
<datatype extension="netcdf" type="galaxy.datatypes.binary:NetCDF" mimetype="application/octet-stream" display_in_upload="true" description="Format used by netCDF software library for writing and reading chromatography-MS data files." />
|
||||
<datatype extension="eps" type="galaxy.datatypes.images:Eps" mimetype="image/eps"/>
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<dependency_resolvers>
|
||||
<!-- the default configuration, first look for dependencies installed from the toolshed -->
|
||||
<!-- the default configuration, first look for dependencies installed from the toolshed -->
|
||||
<tool_shed_packages />
|
||||
<!-- then look for env.sh files in directories according to the "galaxy packages" schema.
|
||||
These resolvers can take a base_path attribute to specify where to look for
|
||||
package definitions, but by default look in the directory specified by tool_dependency_dir
|
||||
in Galaxy's config/galaxy.ini -->
|
||||
<!-- then look for env.sh files in directories according to the "galaxy packages" schema.
|
||||
These resolvers can take a base_path attribute to specify where to look for
|
||||
package definitions, but by default look in the directory specified by tool_dependency_dir
|
||||
in Galaxy's config/galaxy.ini -->
|
||||
<galaxy_packages />
|
||||
<galaxy_packages versionless="true" />
|
||||
<!-- check whether the correct version has been installed via conda -->
|
||||
<conda />
|
||||
<!-- look for a "default" symlink pointing to a directory containing an
|
||||
env.sh file for the package in the "galaxy packages" schema -->
|
||||
<galaxy_packages versionless="true" />
|
||||
<!-- look for any version of the dependency installed via conda -->
|
||||
<conda versionless="true" />
|
||||
|
||||
<!-- Example configuration of modules dependency resolver, uses Environment Modules -->
|
||||
<!--
|
||||
<!-- Example configuration of modules dependency resolver, uses Environment Modules -->
|
||||
<!--
|
||||
<modules modulecmd="/opt/Modules/3.2.9/bin/modulecmd" />
|
||||
<modules modulecmd="/opt/Modules/3.2.9/bin/modulecmd" versionless="true" default_indicator="default" />
|
||||
Attributes are:
|
||||
* modulecmd - path to modulecmd
|
||||
* versionless - default: false - whether to resolve tools using a version number or not
|
||||
* find_by - directory or avail - use the DirectoryModuleChecker or AvailModuleChecker
|
||||
* prefetch - default: true - in the AvailModuleChecker prefetch module info with 'module avail'
|
||||
* default_indicator - default: '(default)' - what indicate to the AvailModuleChecker that a module is the default version
|
||||
-->
|
||||
<!-- other resolvers
|
||||
Attributes are:
|
||||
* modulecmd - path to modulecmd
|
||||
* versionless - default: false - whether to resolve tools using a version number or not
|
||||
* find_by - directory or avail - use the DirectoryModuleChecker or AvailModuleChecker
|
||||
* prefetch - default: true - in the AvailModuleChecker prefetch module info with 'module avail'
|
||||
* default_indicator - default: '(default)' - what indicate to the AvailModuleChecker that a module is the default version
|
||||
-->
|
||||
<!-- other resolvers
|
||||
<tool_shed_tap />
|
||||
<homebrew />
|
||||
-->
|
||||
</dependency_resolvers>
|
||||
-->
|
||||
</dependency_resolvers>
|
||||
|
||||
@@ -365,6 +365,11 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
# the plugin's ini config file.
|
||||
#interactive_environment_swarm_mode = False
|
||||
|
||||
# Galaxy can run a "swarm manager" service that will monitor utilization of the
|
||||
# swarm and provision/deprovision worker nodes as necessary. The service has
|
||||
# its own configuration file.
|
||||
#swarm_manager_config_file = config/swarm_manager_conf.yml
|
||||
|
||||
# Interactive tour directory: where to store interactive tour definition files.
|
||||
# Galaxy ships with several basic interface tours enabled, though a different
|
||||
# directory with custom tours can be specified here. The path is relative to the
|
||||
@@ -1201,7 +1206,7 @@ use_interactive = True
|
||||
# walltime limits but did fail quickly (either while queueing or running). The
|
||||
# commented out default below results in no default job resubmission condition,
|
||||
# failing jobs are just failed outright.
|
||||
#default_job_resubmission_condition =
|
||||
#default_job_resubmission_condition =
|
||||
|
||||
# In multiprocess configurations, notification between processes about new jobs
|
||||
# must be done via the database. In single process configurations, this can be
|
||||
|
||||
@@ -22,12 +22,10 @@ function message_failed_connection(){
|
||||
*
|
||||
*/
|
||||
function load_notebook(notebook_access_url){
|
||||
$( document ).ready(function() {
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_access_url, function(){
|
||||
_handle_notebook_loading(notebook_access_url);
|
||||
});
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_access_url, function(){
|
||||
_handle_notebook_loading(notebook_access_url);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ root = h.url_for( '/' )
|
||||
var startup = function(){
|
||||
// Load notebook
|
||||
requirejs(['interactive_environments', 'plugin/bam_iobio'], function(){
|
||||
load_notebook(notebook_access_url);
|
||||
load_when_ready(ie_readiness_url, function(){
|
||||
load_notebook(notebook_access_url);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ ie_password = '${ ie_request.notebook_pw }';
|
||||
|
||||
var galaxy_root = '${ ie_request.attr.root }';
|
||||
var app_root = '${ ie_request.attr.app_root }';
|
||||
var ie_readiness_url = '${ ie_request.url_template("${PROXY_PREFIX}/interactive_environments/ready") }';
|
||||
</%def>
|
||||
|
||||
|
||||
|
||||
@@ -34,12 +34,10 @@ function message_no_auth(){
|
||||
*
|
||||
*/
|
||||
function load_notebook(password, notebook_login_url, notebook_access_url){
|
||||
$( document ).ready(function() {
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_login_url, function(){
|
||||
_handle_notebook_loading(password, notebook_login_url, notebook_access_url);
|
||||
});
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_login_url, function(){
|
||||
_handle_notebook_loading(password, notebook_login_url, notebook_access_url);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ requirejs(['interactive_environments', 'plugin/jupyter'], function(){
|
||||
// Load notebook
|
||||
|
||||
requirejs(['interactive_environments', 'plugin/jupyter'], function(){
|
||||
load_notebook(ie_password, notebook_login_url, notebook_access_url);
|
||||
load_when_ready(ie_readiness_url, function(){
|
||||
load_notebook(ie_password, notebook_login_url, notebook_access_url);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// Load an interactive environment (IE) from a remote URL
|
||||
// @param {String} notebook_access_url: the URL embeded in the page and loaded
|
||||
function load_notebook(notebook_access_url){
|
||||
// When the page has completely loaded...
|
||||
$( document ).ready(function() {
|
||||
// Test if we can access the GIE, and if so, execute the function
|
||||
// to load the GIE for the user.
|
||||
test_ie_availability(notebook_access_url, function(){
|
||||
append_notebook(notebook_access_url);
|
||||
});
|
||||
// Test if we can access the GIE, and if so, execute the function
|
||||
// to load the GIE for the user.
|
||||
test_ie_availability(notebook_access_url, function(){
|
||||
append_notebook(notebook_access_url);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
|
||||
|
||||
requirejs(['interactive_environments', 'plugin/neo'], function () {
|
||||
load_notebook(url);
|
||||
load_when_ready(ie_readiness_url, function(){
|
||||
load_notebook(url);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
function load_notebook(url){
|
||||
$( document ).ready(function() {
|
||||
test_ie_availability(url, function(){
|
||||
append_notebook(url)
|
||||
});
|
||||
test_ie_availability(url, function(){
|
||||
append_notebook(url)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ requirejs(['interactive_environments', 'plugin/phinch'], function(){
|
||||
|
||||
// Load notebook
|
||||
requirejs(['interactive_environments', 'plugin/phinch'], function(){
|
||||
load_notebook(url);
|
||||
load_when_ready(ie_readiness_url, function(){
|
||||
load_notebook(url);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -15,58 +15,56 @@ function message_failed_connection(){
|
||||
*
|
||||
*/
|
||||
function load_notebook(notebook_login_url, notebook_access_url, notebook_pubkey_url, username){
|
||||
$( document ).ready(function() {
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_pubkey_url, function(){
|
||||
var payload = username + "\n" + ie_password;
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: notebook_pubkey_url,
|
||||
xhrFields: {
|
||||
// Test notebook_login_url for accessibility, executing the login+load function whenever
|
||||
// we've successfully connected to the IE.
|
||||
test_ie_availability(notebook_pubkey_url, function(){
|
||||
var payload = username + "\n" + ie_password;
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: notebook_pubkey_url,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
success: function(response_text){
|
||||
var chunks = response_text.split(':', 2);
|
||||
var exp = chunks[0];
|
||||
var mod = chunks[1];
|
||||
console.log("Found " + exp +" and " + mod);
|
||||
var rsa = new RSAKey();
|
||||
rsa.setPublic(mod, exp);
|
||||
console.log("Encrypting '" + username + "', '" + ie_password + "'");
|
||||
var enc_hex = rsa.encrypt(payload);
|
||||
var encrypted = hex2b64(enc_hex);
|
||||
console.log("E: " + encrypted);
|
||||
|
||||
// Now we can login
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
// to the Login URL
|
||||
url: notebook_login_url,
|
||||
// With our password
|
||||
data: {
|
||||
'v': encrypted,
|
||||
'persist': 1,
|
||||
'clientPath': '/rstudio/auth-sign-in',
|
||||
'appUri': '',
|
||||
},
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
success: function(response_text){
|
||||
var chunks = response_text.split(':', 2);
|
||||
var exp = chunks[0];
|
||||
var mod = chunks[1];
|
||||
console.log("Found " + exp +" and " + mod);
|
||||
var rsa = new RSAKey();
|
||||
rsa.setPublic(mod, exp);
|
||||
console.log("Encrypting '" + username + "', '" + ie_password + "'");
|
||||
var enc_hex = rsa.encrypt(payload);
|
||||
var encrypted = hex2b64(enc_hex);
|
||||
console.log("E: " + encrypted);
|
||||
|
||||
// Now we can login
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
// to the Login URL
|
||||
url: notebook_login_url,
|
||||
// With our password
|
||||
data: {
|
||||
'v': encrypted,
|
||||
'persist': 1,
|
||||
'clientPath': '/rstudio/auth-sign-in',
|
||||
'appUri': '',
|
||||
},
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
// If that is successful, load the notebook
|
||||
success: function(){
|
||||
append_notebook(notebook_access_url);
|
||||
},
|
||||
error: function(jqxhr, status, error){
|
||||
message_failed_connection();
|
||||
// Do we want to try and load the notebook anyway? Just in case?
|
||||
append_notebook(notebook_access_url);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
// If that is successful, load the notebook
|
||||
success: function(){
|
||||
append_notebook(notebook_access_url);
|
||||
},
|
||||
error: function(jqxhr, status, error){
|
||||
message_failed_connection();
|
||||
// Do we want to try and load the notebook anyway? Just in case?
|
||||
append_notebook(notebook_access_url);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ requirejs([
|
||||
'crypto/base64',
|
||||
'plugin/rstudio'
|
||||
], function(){
|
||||
load_notebook(notebook_login_url, notebook_access_url, notebook_pubkey_url, "${ USERNAME }");
|
||||
load_when_ready(ie_readiness_url, function(){
|
||||
load_notebook(notebook_login_url, notebook_access_url, notebook_pubkey_url, "${ USERNAME }");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="main">
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: searchover
|
||||
type:
|
||||
- masthead
|
||||
activate: true
|
||||
|
||||
icon: fa-search
|
||||
tooltip: Click to activate search and press Ctrl + Alt + q to open the overlay
|
||||
|
||||
function: >
|
||||
$.getJSON("/api/webhooks", function( data ) {
|
||||
for( var item in data ) {
|
||||
var webhook = data[item];
|
||||
if( webhook.name === "searchover" ) {
|
||||
if( webhook.script && !window.static_search ) {
|
||||
$( '<script/>', { type: 'text/javascript' } ).text( webhook.script ).appendTo( 'head' );
|
||||
$( '<style/>', { type: 'text/css' } ).text( webhook.styles ).appendTo( 'head' );
|
||||
window.static_search = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
.overlay-wrapper {
|
||||
margin-top: -5%;
|
||||
}
|
||||
|
||||
.search-screen {
|
||||
position: fixed;
|
||||
z-index: 202;
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:none;
|
||||
}
|
||||
|
||||
.search-screen-overlay {
|
||||
position: fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
background: rgba(224, 224, 224, 0.75);
|
||||
z-index: 201;
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:none;
|
||||
opacity: 2;
|
||||
}
|
||||
|
||||
.overlay-filters {
|
||||
display: table;
|
||||
margin: 0 auto 5px;
|
||||
}
|
||||
|
||||
.overlay-filters ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overlay-filters li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.overlay-filters li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
.overlay-filters li a:hover i {
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.overlay-filters li a i {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
background-color: #e1e1e1;
|
||||
background: -moz-linear-gradient(top, rgba(225, 225, 225, 1) 0%, rgba(238, 238, 238, 1) 37%, rgba(225, 225, 225, 1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(225, 225, 225, 1) 0%, rgba(238, 238, 238, 1) 37%, rgba(225, 225, 225, 1) 100%);
|
||||
-moz-box-shadow: 0px 2px 5px 0px rgba(158, 158, 158, 0.75);
|
||||
box-shadow: 0px 2px 5px 0px rgba(158, 158, 158, 0.75);
|
||||
|
||||
position: fixed;
|
||||
top: 34px;
|
||||
width: 100%;
|
||||
z-index: 203;
|
||||
}
|
||||
|
||||
.txtbx-search-data {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #2c3143;
|
||||
margin: 20px auto 10px;
|
||||
width: 300px;
|
||||
padding: 11px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
color: #007AB5;
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-section .section-title {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.link-tile {
|
||||
background-color: #0070A8;
|
||||
border: 1px solid #005A85;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
width: 19%;
|
||||
height: 75px;
|
||||
margin: 0.5%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.link-tile.btn-primary:hover,
|
||||
.link-tile.btn-primary:hover {
|
||||
background-color: #005A85;
|
||||
border-color: #005A85;
|
||||
}
|
||||
|
||||
.link-tile.btn-primary:active,
|
||||
.link-tile.btn-primary:focus {
|
||||
background-color: #005A85;
|
||||
border-color: #005A85;
|
||||
}
|
||||
|
||||
.search-results,
|
||||
.removed-items {
|
||||
position: fixed;
|
||||
top: 125px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 15px;
|
||||
width: 100%;
|
||||
height: 83%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.item-actions:hover {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.pin-item {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.restore-item,
|
||||
.remove-fav,
|
||||
.remove-item {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.filter-active i {
|
||||
/*color: gold;*/
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.filter-active i:hover {
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.filter-inactive i {
|
||||
font-size: 0.875em;
|
||||
color: #9e9e9e;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.pinned-item {
|
||||
color: gold;
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
color: white;
|
||||
margin-top: 0.8%;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
# Galaxy docker swarm manager configuration file
|
||||
#
|
||||
# To configure the location of this file, use the `swarm_manager_config_file`
|
||||
# setting in galaxy.ini
|
||||
|
||||
# When the swarm manager daemonizes, it writes a pid file so that only one
|
||||
# manager will run at a time. This is the path to that pid file.
|
||||
# {xdg_data_home} will be templated automatically and defaults to
|
||||
# ~/.local/share as per the XDG specification
|
||||
#pid_file: '{xdg_data_home}/galaxy_swarm_manager.pid'
|
||||
|
||||
# Program output will be written to the log
|
||||
#log_file: '{xdg_data_home}/galaxy_swarm_manager.log'
|
||||
|
||||
# As with GIE plugins, you can modify the base docker command ({docker_args}
|
||||
# must be present and will be filled in with the docker subcommand and
|
||||
# arguments)
|
||||
#command: 'docker {docker_args}'
|
||||
|
||||
# Managed services should be started with this string at the beginning of their
|
||||
# name. It should match the value of CONTAINER_NAME_PREFIX in
|
||||
# lib/galaxy/web/base/interactive_environments.py, so you should not change
|
||||
# this unless you change both.
|
||||
#service_prefix: galaxy_gie_
|
||||
|
||||
# Limits:
|
||||
#
|
||||
# - max_waiting_services: number of services that should be waiting of each
|
||||
# "CPU class" (number of CPUs requested e.g. with --reserve-cpu) before
|
||||
# attempting to spawn a node
|
||||
# - max_wait_time: number of seconds a service should be waiting before
|
||||
# attempting to spawn a node
|
||||
# - max_node_idle_time: number of seconds a node should be idle before
|
||||
# terminating it
|
||||
# - max_node_counts: a dictionary controlling the maximum number of nodes of
|
||||
# each CPU class that the swarm manager will attempt to spawn, e.g.:
|
||||
# max_node_counts:
|
||||
# 1: 10 # spawn up to 10 x 1-CPU nodes
|
||||
# 2: 3 # spawn up to 3 x 2-CPU nodes
|
||||
# 4: 1 # spawn up to 1 x 4-CPU nodes
|
||||
#max_waiting_services: 0
|
||||
#max_wait_time: 5
|
||||
#max_node_idle_time: 120
|
||||
#max_node_counts: {}
|
||||
|
||||
# If set, only manage nodes whose swarm hostnames begin with this prefix.
|
||||
# Otherwise, attempt to manage all nodes
|
||||
#node_prefix: null
|
||||
|
||||
# Amount of time to wait for a spawning node to appear in `docker node ls`
|
||||
# before considering it failed
|
||||
#spawn_wait_time: 30
|
||||
|
||||
# Command to run to spawn new nodes. This command should join the node to the
|
||||
# swarm. Can include template variables:
|
||||
# - {cpu_class}: CPU class as explained above (the value of --reserve-cpu)
|
||||
# - {cpus_needed}: Total number of CPUs of the given class needed to run the
|
||||
# waiting services
|
||||
# If this command does not block until the node is joined to the swarm, make
|
||||
# sure it at least completes that step in `spawn_wait_time` once it returns
|
||||
# control. This command should return a space-separated list of nodes. If the
|
||||
# nodes have a different number of CPUs than the class that they were started
|
||||
# for, you can include that number after a colon (e.g. `node1:4`).
|
||||
#spawn_command: /bin/true
|
||||
|
||||
# Command to run to destroy idle nodes. Can include template variables:
|
||||
# - {nodes}: Space-separated list of node names to destroy
|
||||
# This command should block until at least the point at which any nodes being
|
||||
# deallocated no longer appear in `docker node ls`.
|
||||
#destroy_command: /bin/true
|
||||
|
||||
# Command to run if either of the above commands failed (e.g. to notify an
|
||||
# administrator). Can include template variables:
|
||||
# - {failed_command}: Command line of the command that failed
|
||||
#command_failure_command: /bin/true
|
||||
|
||||
# Number of times to retry spawn/destroy commands before considering them to
|
||||
# have failed, and seconds to wait between retries
|
||||
#command_retries: 0
|
||||
#command_retry_wait: 10
|
||||
|
||||
# Stop the swarm manager daemon when there are no services or nodes to manage
|
||||
#terminate_when_idle: True
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# When running Galaxy Interactive Environments on a swarm (using Docker Engine
|
||||
# swarm mode), GIE sessions that have ended will leave behind "shut down"
|
||||
# Docker services. These must be removed, which you can do with this script.
|
||||
#
|
||||
# Note that this is dependent on the specific ordering and output format of
|
||||
# `docker service ls` and `docker service ps`. As of the time of writing
|
||||
# (Docker version 1.13.1) these formats cannot be controlled as can be done
|
||||
# with `docker ps` and the `--format` option, so be careful when upgrading
|
||||
# Docker releases.
|
||||
|
||||
|
||||
CONTAINER_NAME_PREFIX='galaxy_gie_'
|
||||
LOG_PATH=${1:-'/tmp/galaxy_gie_service_clean.log'}
|
||||
|
||||
|
||||
{
|
||||
echo "Running cleanup at $(date)"
|
||||
for service_name in $(docker service ls | awk "\$2 ~ /^$CONTAINER_NAME_PREFIX/ {print \$2}"); do
|
||||
docker service ps --no-trunc $service_name | tail -1 | awk '{print $6}' | grep -q '^Shutdown$' && docker service rm $service_name;
|
||||
done
|
||||
echo "Done"
|
||||
} >>$LOG_PATH
|
||||
@@ -6,10 +6,12 @@ Dependency Resolvers in Galaxy
|
||||
|
||||
There are two parts to building a link between Galaxy and command line bioinformatics tools: the tool XML that
|
||||
specifies a mapping between the Galaxy web user interface and the tool command line and tool dependencies that specify
|
||||
how to source the actual packages that implement the tool’s commands. The final script that Galaxy submits to run a
|
||||
job uses includes commands, such as changes to the ``PATH`` environment variable, that are generated by *dependency
|
||||
resolvers*. There is a default dependency resolver configuration but administrators can provide their own configuration
|
||||
using the ``dependency_resolvers_conf.xml`` configuration file in the Galaxy ``config/`` directory.
|
||||
how to source the actual packages that implement the tool’s commands. The final script that Galaxy submits to run a job
|
||||
uses includes commands, such as changes to the ``PATH`` environment variable, that are generated by *dependency
|
||||
resolvers*. These same dependency resolvers are used by the Galaxy administrative UI to display whether an installed
|
||||
tool's dependencies have been installed on the Galaxy server, and to show how they will be resolved at job runtime.
|
||||
There is a default dependency resolver configuration but administrators can provide their own configuration using the
|
||||
``dependency_resolvers_conf.xml`` configuration file in the Galaxy ``config/`` directory.
|
||||
|
||||
The binding between tool XML and the tools they need to run is specified in the tool XML using ``<requirement>``
|
||||
tags, for example
|
||||
@@ -34,30 +36,31 @@ The default configuration of dependency resolvers is equivalent to the following
|
||||
.. code-block:: xml
|
||||
|
||||
<dependency_resolvers>
|
||||
<!-- the default configuration, first look for legacy dependencies installed from the toolshed -->
|
||||
<tool_shed_packages />
|
||||
<!-- then look for env.sh files profile according to the "galaxy packages" schema -->
|
||||
<galaxy_packages />
|
||||
<galaxy_packages versionless="true" />
|
||||
<!-- finally look for Conda dependencies. -->
|
||||
<conda />
|
||||
<galaxy_packages versionless="true" />
|
||||
<conda versionless="true" />
|
||||
</dependency_resolvers>
|
||||
|
||||
This default dependency resolver configuration contains five items. First, the *tool shed dependency resolver* is used,
|
||||
then the *Galaxy packages dependency resolver* is used (initially looking for packages by name and version string and then looking for the package just by name), and finally it checks *Conda* for a versioned or unversioned match.
|
||||
The default configuration thus prefers packages installed from the Galaxy Tool Shed using legacy ``tool_dependencies.xml``
|
||||
files, before trying to find a "Galaxy package" satisfying the specific version the dependency requires before
|
||||
falling back to looking for a Galaxy package with merely the correct name, and then looking for Conda recipes with
|
||||
matching name and version, and finally just for a Conda package with the correct name. If any of the dependency
|
||||
resolvers succeeds a dependency resolution object is returned and no more resolvers are called. This dependency
|
||||
resolution object provides shell commands to prepend to the shell script that runs the tool.
|
||||
This default dependency resolver configuration contains five items:
|
||||
|
||||
1. First, the *Tool Shed dependency resolver* is used, which resolves packages installed from the Galaxy Tool Shed
|
||||
using legacy ``tool_dependencies.xml`` files,
|
||||
2. then the *Galaxy packages dependency resolver* is checked for a package matching the requirement name and version,
|
||||
3. then the *Conda dependency resolver* is checked for a package matching the requirement name and version. If no
|
||||
versioned match can be found, it then moves on to searching for unversioned matches, that is,
|
||||
4. the *Galaxy packages dependency resolver* is checked for a package matching the required name only, and
|
||||
5. finally the *Conda dependency resolver* is checked for a package matching the required name only.
|
||||
|
||||
If any of the dependency resolvers succeeds a dependency resolution object is returned and no more resolvers are
|
||||
called. This dependency resolution object provides shell commands to prepend to the shell script that runs the tool.
|
||||
|
||||
This order can be thought of as a descending order of deliberation. Tool Shed dependencies must be declared next to the
|
||||
tool by the tool author and must be selected for installation at tool installation time - this requires specific actions
|
||||
by both the tool author and the deployer who installed the tools. The dependency is therefore expected to highly craft
|
||||
to the individual tool. If Galaxy packages have been setup, the deployer of a Galaxy tool has purposely crafted tool
|
||||
dependency statements for a specific installation - this is slightly less deliberate than tool shed packages but
|
||||
by both the tool author and the deployer who installed the tools. The dependency is therefore expected to highly
|
||||
crafted to the individual tool. If Galaxy packages have been setup, the deployer of a Galaxy tool has purposely crafted
|
||||
tool dependency statements for a specific installation - this is slightly less deliberate than tool shed packages but
|
||||
such requirements are less likely to be incidentally resolved than Conda packages. Conda recipes are neither tied to
|
||||
tools or a specific installation and are maintained in Conda channels such as Bioconda.
|
||||
|
||||
|
||||
@@ -283,11 +283,11 @@ Galaxy supports both Docker Engine swarm mode and the legacy Docker Swarm
|
||||
system. Legacy Docker Swarm is supported without any special configuration,
|
||||
because the containers are still run with ``docker run`` as before. To support
|
||||
Docker Engine swarm mode, additional configuration is required. Begin by
|
||||
editing your GIE config plugin's ini configuration file (e.g. ``jupyter.ini``)
|
||||
and set the ``docker_connect_port`` and ``swarm_mode options`` in addition to
|
||||
any other relevant options. Unless you are using a non-standard Docker image,
|
||||
the correct value for ``docker_connect_port`` should be suggested to you in the
|
||||
sample configuration file:
|
||||
editing your GIE plugin's ini configuration file (e.g. ``jupyter.ini``) and set
|
||||
the ``docker_connect_port`` and ``swarm_mode options`` in addition to any other
|
||||
relevant options. Unless you are using a non-standard Docker image, the correct
|
||||
value for ``docker_connect_port`` should be suggested to you in the sample
|
||||
configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
@@ -295,6 +295,12 @@ sample configuration file:
|
||||
docker_connect_port = 8888
|
||||
swarm_mode = True
|
||||
|
||||
You can also enable swarm mode for *all* GIE plugins by setting
|
||||
``interactive_environment_swarm_mode`` in ``galaxy.ini`` to ``True``. If using
|
||||
this setting, you must still set ``docker_connect_port`` in each GIE plugin's
|
||||
ini configuration file. The ``swarm_mode`` setting in individual GIE plugin
|
||||
config files will override the value set in ``galaxy.ini``.
|
||||
|
||||
Note that your Galaxy server does not need to be a member of the swarm itself.
|
||||
It can use the method outlined above in the `Docker on Another Host`_ section
|
||||
to connect as a client to a Docker daemon acting as a swarm mode manager.
|
||||
@@ -303,18 +309,10 @@ Once configured, you should see that your GIE containers are started and run as
|
||||
services, which you can inspect using the ``docker service ls`` command and
|
||||
other ``docker service`` subcommands.
|
||||
|
||||
**Docker services are not cleaned up by Galaxy**. To clean them up, we have
|
||||
provided a script that can be run from cron which will locate "shut down"
|
||||
services (GIE containers which have stopped themselves) at
|
||||
`cron/clean_docker_swarm_mode_services.sh
|
||||
<https://github.com/galaxyproject/galaxy/blob/dev/cron/clean_docker_swarm_mode_services.sh>`__
|
||||
in the Galaxy source. This script can be run from cron with a crontab entry
|
||||
like this example which runs every 15 minutes:
|
||||
**Galaxy swarm manager**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
*/15 * * * * bash /path/to/clean_docker_swarm_mode_services.sh /path/to/galaxy/log/dir/clean_docker_swarm_mode_services.log
|
||||
|
||||
This entry would be suitable to be run as ``root`` on a swarm mode manager. You
|
||||
could also run it as the Galaxy user on the Galaxy server (with modifications
|
||||
to set the correct daemon socket, if running remotely).
|
||||
Galaxy will start a "swarm manager" process when the first swarm mode GIE is
|
||||
launched. You can control this daemon with the config file
|
||||
``config/swarm_mode_manager.yml``. Consult the sample configuration at
|
||||
``config/swarm_mode_manager.yml.sample`` for syntax. It will automatically shut
|
||||
down when no services or nodes remain to be managed.
|
||||
|
||||
+11
-19
@@ -52,6 +52,7 @@ PATH_DEFAULTS = dict(
|
||||
workflow_schedulers_config_file=['config/workflow_schedulers_conf.xml', 'config/workflow_schedulers_conf.xml.sample'],
|
||||
modules_mapping_files=['config/environment_modules_mapping.yml', 'config/environment_modules_mapping.yml.sample'],
|
||||
local_conda_mapping_file=['config/local_conda_mapping.yml', 'config/local_conda_mapping.yml.sample'],
|
||||
swarm_manager_config_file=['config/swarm_manager_conf.yml', 'config/swarm_manager_conf.yml.sample'],
|
||||
)
|
||||
|
||||
PATH_LIST_DEFAULTS = dict(
|
||||
@@ -903,34 +904,25 @@ class ConfiguresGalaxyMixin:
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def reload_toolbox(self):
|
||||
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
|
||||
|
||||
tool_configs = self.config.tool_configs
|
||||
if self.config.migrated_tools_config not in tool_configs:
|
||||
tool_configs.append( self.config.migrated_tools_config )
|
||||
|
||||
from galaxy import tools
|
||||
old_toolbox = self.toolbox
|
||||
self.toolbox = tools.ToolBox( tool_configs, self.config.tool_path, self )
|
||||
self.reindex_tool_search()
|
||||
if old_toolbox:
|
||||
old_toolbox.shutdown()
|
||||
|
||||
def _configure_toolbox( self ):
|
||||
from galaxy import tools
|
||||
from galaxy.managers.citations import CitationsManager
|
||||
self.citations_manager = CitationsManager( self )
|
||||
|
||||
from galaxy.tools.deps import containers
|
||||
from galaxy.tools.toolbox.cache import ToolCache
|
||||
from galaxy.tools.toolbox.lineages.tool_shed import ToolVersionCache
|
||||
|
||||
self.citations_manager = CitationsManager( self )
|
||||
self.tool_cache = ToolCache()
|
||||
self.tool_version_cache = ToolVersionCache(self)
|
||||
|
||||
self._toolbox_lock = threading.RLock()
|
||||
self.toolbox = None
|
||||
self.reload_toolbox()
|
||||
# Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file.
|
||||
tool_configs = self.config.tool_configs
|
||||
if self.config.migrated_tools_config not in tool_configs:
|
||||
tool_configs.append( self.config.migrated_tools_config )
|
||||
self.toolbox = tools.ToolBox( tool_configs, self.config.tool_path, self )
|
||||
self.reindex_tool_search()
|
||||
|
||||
from galaxy.tools.deps import containers
|
||||
galaxy_root_dir = os.path.abspath(self.config.root)
|
||||
file_path = os.path.abspath(getattr(self.config, "file_path"))
|
||||
app_info = containers.AppInfo(
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
"""
|
||||
Docker Swarm mode management
|
||||
"""
|
||||
import argparse
|
||||
import errno
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import daemon
|
||||
import daemon.pidfile
|
||||
import lockfile
|
||||
except ImportError:
|
||||
daemon = None
|
||||
import yaml
|
||||
|
||||
try:
|
||||
import galaxy # noqa: F401 this is a test import
|
||||
except ImportError:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
os.pardir,
|
||||
os.pardir)))
|
||||
|
||||
from galaxy.config import (
|
||||
configure_logging,
|
||||
find_path,
|
||||
find_root,
|
||||
)
|
||||
from galaxy.util.properties import find_config_file, load_app_properties
|
||||
|
||||
|
||||
DESCRIPTION = "Daemon to manage a Docker Swarm (running in Docker Swarm mode)."
|
||||
SWARM_MANAGER_CONF_DEFAULTS = {
|
||||
'pid_file': '{xdg_data_home}/galaxy_swarm_manager.pid',
|
||||
'log_file': '{xdg_data_home}/galaxy_swarm_manager.log',
|
||||
'command': 'docker {docker_args}',
|
||||
'service_prefix': 'galaxy_gie_',
|
||||
'max_waiting_services': 0,
|
||||
'max_wait_time': 5,
|
||||
'max_node_counts': {}, # max number of nodes per class to spawn
|
||||
'max_node_idle_time': 120,
|
||||
'node_prefix': None,
|
||||
'spawn_wait_time': 30,
|
||||
'spawn_command': '/bin/true',
|
||||
'destroy_command': '/bin/true',
|
||||
'command_failure_command': '/bin/true',
|
||||
'command_retries': 0,
|
||||
'command_retry_wait': 10,
|
||||
'terminate_when_idle': True,
|
||||
}
|
||||
OK_NODE_STATE = 'ready-active'
|
||||
NODE_CPU_CLASS_LABEL = '_galaxy_cpu_class'
|
||||
log = lambda *x: None # noqa: E731
|
||||
|
||||
|
||||
# TODO: pass around instances or at least namedtuples rather than these
|
||||
# arbitrary dictionaries
|
||||
|
||||
|
||||
class DockerInterface(object):
|
||||
|
||||
def __init__(self, swarm_manager_conf):
|
||||
self.swarm_manager_conf = swarm_manager_conf
|
||||
self.command = swarm_manager_conf['command']
|
||||
self.service_prefix = swarm_manager_conf['service_prefix']
|
||||
|
||||
def _run_docker(self, docker_args):
|
||||
raw_cmd = self.command.format(docker_args=docker_args)
|
||||
p = subprocess.Popen(raw_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
log.error("%s\n%s" % (stdout, stderr))
|
||||
return None
|
||||
else:
|
||||
return stdout
|
||||
|
||||
def _parse_docker_column_output(self, output):
|
||||
"""Many docker commands do not provide an option to format the output
|
||||
or output in a machine-readily-parseable format (e.g. json). In order
|
||||
to deal with such output and hopefully stay compatible with future
|
||||
column order changes, key returned rows based on column headers.
|
||||
|
||||
An assumption is made that a single space in the header row does not
|
||||
separate columns - column names can have spaces in them, and columns
|
||||
are separated by at least 2 spaces. This seems to be true as of Docker
|
||||
1.13.1.
|
||||
"""
|
||||
parsed = []
|
||||
output = output.splitlines()
|
||||
header = output[0]
|
||||
colstarts = [0]
|
||||
colidx = 0
|
||||
spacect = 0
|
||||
if not output:
|
||||
return parsed
|
||||
for i, c in enumerate(header):
|
||||
if c != ' ' and spacect > 1:
|
||||
colidx += 1
|
||||
colstarts.append(i)
|
||||
spacect = 0
|
||||
elif c == ' ':
|
||||
spacect += 1
|
||||
colstarts.append(None)
|
||||
colheadings = []
|
||||
for i in range(0, len(colstarts) - 1):
|
||||
colheadings.append(header[colstarts[i]:colstarts[i + 1]].strip())
|
||||
for line in output[1:]:
|
||||
row = {}
|
||||
for i, key in enumerate(colheadings):
|
||||
row[key] = line[colstarts[i]:colstarts[i + 1]].strip()
|
||||
parsed.append(row)
|
||||
return parsed
|
||||
|
||||
def _service_inspect(self, service_id):
|
||||
return self._run_docker(docker_args='service inspect {service_id}'.format(service_id=service_id))
|
||||
|
||||
def _get_reserved_cpu_count(self, service_id=None, inspect_output=None):
|
||||
assert service_id or inspect_output, "Either `service_id` or `inspect_output` is required"
|
||||
if not inspect_output:
|
||||
inspect_output = self._service_inspect(service_id)
|
||||
try:
|
||||
return json.loads(inspect_output)[0]['Spec']['Resources']['Reservations']['NanoCPUs'] / 1000000000
|
||||
except KeyError:
|
||||
return 1
|
||||
|
||||
def _node_inspect(self, node_name):
|
||||
return self._run_docker(docker_args='node inspect {node_name}'.format(node_name=node_name))
|
||||
|
||||
def _get_node_cpu_class(self, node_name=None, inspect_output=None):
|
||||
assert node_name or inspect_output, "Either `node_name` or `inspect_output` is required"
|
||||
if not inspect_output:
|
||||
inspect_output = self._node_inspect(node_name)
|
||||
try:
|
||||
return json.loads(inspect_output)[0]['Spec']['Labels'][NODE_CPU_CLASS_LABEL]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def waiting_services_by_cpu_class(self):
|
||||
rval = {}
|
||||
service_ids = self._services_in_state('Running', 'Pending')
|
||||
for service_id in service_ids:
|
||||
cpu_class = self._get_reserved_cpu_count(service_id=service_id)
|
||||
if cpu_class not in rval:
|
||||
rval[cpu_class] = []
|
||||
rval[cpu_class].append(service_id)
|
||||
return rval
|
||||
|
||||
def active_nodes_by_cpu_class(self):
|
||||
rval = {}
|
||||
ls_output = self._run_docker(docker_args='node ls')
|
||||
for node in self._parse_docker_column_output(ls_output):
|
||||
cpu_class = self._get_node_cpu_class(node_name=node['HOSTNAME'])
|
||||
if cpu_class:
|
||||
cpu_class = int(cpu_class)
|
||||
rval[cpu_class] = rval.get(cpu_class, 0) + 1
|
||||
return rval
|
||||
|
||||
def completed_services(self):
|
||||
return self._services_in_state('Shutdown', 'Complete')
|
||||
|
||||
def _services_in_state(self, desired, current):
|
||||
service_ids = []
|
||||
for service_detail in self._service_details():
|
||||
if service_detail['DESIRED STATE'] == desired and service_detail['CURRENT STATE'].startswith(current):
|
||||
service_ids.append(service_detail['ID'])
|
||||
return service_ids
|
||||
|
||||
def _service_details(self):
|
||||
ls_output = self._run_docker(docker_args='service ls')
|
||||
for service in self._parse_docker_column_output(ls_output):
|
||||
if not service['NAME'].startswith(self.service_prefix):
|
||||
continue
|
||||
ps_output = self._run_docker(docker_args='service ps --no-trunc {service_id}'.format(
|
||||
service_id=service['ID']))
|
||||
service_details = self._parse_docker_column_output(ps_output)[0]
|
||||
for col in ('ID', 'NAME'):
|
||||
service_details['PROCESS ' + col] = service_details[col]
|
||||
service_details[col] = service[col]
|
||||
yield service_details
|
||||
|
||||
def clean_services(self):
|
||||
cleaned_services = []
|
||||
services = self.completed_services()
|
||||
if services:
|
||||
cleaned_services = self._run_docker(docker_args='service rm {service_ids}'.format(
|
||||
service_ids=' '.join(services))).splitlines()
|
||||
return cleaned_services
|
||||
|
||||
def node_states(self):
|
||||
nodes = {}
|
||||
ls_output = self._run_docker(docker_args='node ls')
|
||||
for node in self._parse_docker_column_output(ls_output):
|
||||
nodes[node['HOSTNAME']] = {
|
||||
'state': ('%s-%s' % (node['STATUS'], node['AVAILABILITY'])).lower(),
|
||||
'manager': True if node['MANAGER STATUS'] else False,
|
||||
}
|
||||
return nodes
|
||||
|
||||
def node_job_count(self, node_name):
|
||||
ps_output = self._run_docker(docker_args='node ps --no-trunc {node_name}'.format(
|
||||
node_name=node_name))
|
||||
jobs = filter(lambda x: x['NAME'].startswith(self.service_prefix), self._parse_docker_column_output(ps_output))
|
||||
return len(jobs)
|
||||
|
||||
def ensure_node_cpu_class(self, node, cpu_class):
|
||||
cur_cpu_class = self._get_node_cpu_class(node_name=node)
|
||||
if str(cpu_class) != cur_cpu_class:
|
||||
log.info("setting node '%s' cpu class from '%s' to '%s'", node, cur_cpu_class, cpu_class)
|
||||
self._run_docker(docker_args='node update --label-add {label_name}={label_val} {node}'.format(
|
||||
label_name=NODE_CPU_CLASS_LABEL,
|
||||
label_val=cpu_class,
|
||||
node=node))
|
||||
else:
|
||||
log.debug("node '%s' cpu class is '%s'", node, cur_cpu_class)
|
||||
|
||||
def node_cpu_class(self, node):
|
||||
return self._get_node_cpu_class(node_name=node)
|
||||
|
||||
|
||||
class SwarmManager(object):
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
self.docker_interface = DockerInterface(conf)
|
||||
self.state = SwarmState(conf)
|
||||
self.spawn_wait_time = conf['spawn_wait_time']
|
||||
self.spawn_command = conf['spawn_command']
|
||||
self.destroy_command = conf['destroy_command']
|
||||
self.command_retries = conf['command_retries']
|
||||
self.command_retry_wait = conf['command_retry_wait']
|
||||
self.node_prefix = conf['node_prefix']
|
||||
self.terminate_when_idle = conf['terminate_when_idle']
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
node_states = None
|
||||
self._spawn_if_waiting()
|
||||
self._check_for_new_nodes(node_states=node_states)
|
||||
self._destroy_if_surplus(node_states=node_states)
|
||||
self._clean_services()
|
||||
self._terminate_if_idle()
|
||||
time.sleep(1)
|
||||
|
||||
def _run_command(self, command, command_retries=None, **kwargs):
|
||||
stdout = None
|
||||
attempt = 0
|
||||
if not command_retries:
|
||||
command_retries = self.command_retries
|
||||
raw_cmd = command.format(**kwargs)
|
||||
log.debug('running command: %s', raw_cmd)
|
||||
while not stdout and attempt < command_retries + 1:
|
||||
attempt += 1
|
||||
p = subprocess.Popen(raw_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
msg = "error running '%s'" % raw_cmd
|
||||
if attempt < command_retries + 1:
|
||||
msg += ', waiting %s seconds' % self.command_retry_wait
|
||||
time.sleep(self.command_retry_wait)
|
||||
log.warning(msg + "\nstdout: %s\nstderr: %s\n", stdout, stderr)
|
||||
else:
|
||||
msg += ' (final attempt)'
|
||||
log.error(msg + "\nstdout: %s\nstderr: %s\n", stdout, stderr)
|
||||
self._run_command(self.conf['command_failure_command'].format(failed_command=raw_cmd), command_retries=0)
|
||||
stdout = None
|
||||
else:
|
||||
stdout = stdout.strip()
|
||||
return stdout
|
||||
|
||||
def _spawn_if_waiting(self):
|
||||
waiting = self.docker_interface.waiting_services_by_cpu_class()
|
||||
active = self.docker_interface.active_nodes_by_cpu_class()
|
||||
cpus_needed = self.state.need_nodes(waiting, active)
|
||||
for cpu_class in cpus_needed.keys():
|
||||
log.info("requesting node(s) for services requesting %d CPUs total (%d CPUs each): %s", cpus_needed[cpu_class], cpu_class, ' '.join(waiting[cpu_class]))
|
||||
command = '{spawn_command}'.format(spawn_command=self.spawn_command).format(
|
||||
cpu_class=cpu_class,
|
||||
cpus_needed=cpus_needed[cpu_class])
|
||||
new_nodes = self._run_command(command)
|
||||
if not new_nodes:
|
||||
log.warning('spawn_command returned no new nodes, cannot manage nodes')
|
||||
else:
|
||||
log.info("node allocator will spawn: %s", new_nodes)
|
||||
self.state.nodes_requested(cpu_class, new_nodes.split(), waiting[cpu_class])
|
||||
self.state.mark_services_handled(waiting[cpu_class])
|
||||
|
||||
def _check_for_new_nodes(self, node_states=None):
|
||||
for node_name, elapsed, cpu_class, node in self.state.spawning_nodes():
|
||||
if not node_states:
|
||||
node_states = self.docker_interface.node_states()
|
||||
if node_name not in node_states:
|
||||
if elapsed > self.spawn_wait_time:
|
||||
log.warning("spawning node '%s' not found in `docker node ls` and spawn_wait_time exceeded! %d seconds have elapsed", node_name, elapsed)
|
||||
self._run_command(self.conf['command_failure_command'].format(failed_command='wait_for_spawning_node %s' % node_name), command_retries=0)
|
||||
self.mark_spawning_node_timeout(node_name)
|
||||
elif node_states[node_name]['state'] == OK_NODE_STATE:
|
||||
self.docker_interface.ensure_node_cpu_class(node_name, cpu_class)
|
||||
self.state.mark_spawning_node_ready(node_name)
|
||||
log.info("spawning node '%s' is ready!", node_name)
|
||||
elif node_states[node_name]['state'] != node['state']:
|
||||
log.info("spawning node '%s' state changed from '%s' to '%s'", node_name, node['state'], node_states[node_name]['state'])
|
||||
self.docker_interface.ensure_node_cpu_class(node_name, cpu_class)
|
||||
self.state.mark_spawning_node_state(node_name, node_states[node_name]['state'])
|
||||
elif elapsed > self.spawn_wait_time:
|
||||
log.warning("spawning node '%s' state is '%s' after %s seconds", node_name, node_states[node_name]['state'], elapsed)
|
||||
|
||||
def _destroy_if_surplus(self, node_states=None):
|
||||
destroy_nodes = []
|
||||
if not node_states:
|
||||
node_states = self.docker_interface.node_states()
|
||||
for node_name, node_state in node_states.items():
|
||||
if self._node_ready_for_destruction(node_name, node_state):
|
||||
destroy_nodes.append(node_name)
|
||||
if destroy_nodes:
|
||||
command = '{destroy_command}'.format(destroy_command=self.destroy_command).format(
|
||||
nodes=' '.join(destroy_nodes))
|
||||
destroyed_nodes = self._run_command(command)
|
||||
if not destroyed_nodes:
|
||||
log.warning('destroy_command returned no destroyed nodes')
|
||||
else:
|
||||
log.info("destroyed nodes: %s", destroyed_nodes)
|
||||
|
||||
def _node_is_managed(self, node_name, node_state):
|
||||
return (not self.node_prefix or node_name.startswith(self.node_prefix)) and not node_state['manager']
|
||||
|
||||
def _node_ready_for_destruction(self, node_name, node_state):
|
||||
ready = False
|
||||
if (self._node_is_managed(node_name, node_state) and
|
||||
node_state['state'] == 'ready-active'):
|
||||
if self.docker_interface.node_job_count(node_name) == 0:
|
||||
self.state.mark_node_idle(node_name)
|
||||
ready = self.state.is_destruction_time(node_name)
|
||||
else:
|
||||
self.state.clear_node_idle(node_name)
|
||||
return ready
|
||||
|
||||
def _clean_services(self):
|
||||
cleaned_services = self.docker_interface.clean_services()
|
||||
if cleaned_services:
|
||||
self.state.clean_services(cleaned_services)
|
||||
log.info("cleaned services: %s", ', '.join(cleaned_services))
|
||||
|
||||
def _terminate_if_idle(self):
|
||||
if not self.terminate_when_idle:
|
||||
return
|
||||
node_states = self.docker_interface.node_states()
|
||||
for node_name, node_state in node_states.items():
|
||||
if self._node_is_managed(node_name, node_state):
|
||||
return # nonterminated managed nodes remain
|
||||
elif self.docker_interface.node_job_count(node_name) > 0:
|
||||
return # unmanaged nodes are running a galaxy service
|
||||
# FIXME: there's a race condition here
|
||||
if self.docker_interface.waiting_services_by_cpu_class():
|
||||
return # waiting jobs remain
|
||||
log.info('nothing to manage, shutting down')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
class SwarmState(object):
|
||||
|
||||
def __init__(self, conf):
|
||||
self._handled_services = set()
|
||||
self._waiting_since = {}
|
||||
self._spawning_nodes = {}
|
||||
self._surplus_nodes = {}
|
||||
self.max_waiting_services = conf['max_waiting_services']
|
||||
self.max_wait_time = conf['max_wait_time']
|
||||
self.max_node_idle_time = conf['max_node_idle_time']
|
||||
self.max_node_counts = conf['max_node_counts']
|
||||
|
||||
def need_nodes(self, waiting_services, active_nodes):
|
||||
rval = {}
|
||||
need_cpus = self._needed_cpu_counts(waiting_services)
|
||||
spawning_cpus = self._spawning_cpu_counts()
|
||||
for cpu_class in need_cpus.keys():
|
||||
cpus_needed = need_cpus[cpu_class] - spawning_cpus.get(cpu_class, 0)
|
||||
if cpus_needed > 0 and active_nodes.get(cpu_class, 0) < self.max_node_counts.get(cpu_class, sys.maxint):
|
||||
rval[cpu_class] = cpus_needed
|
||||
return rval
|
||||
|
||||
def _needed_cpu_counts(self, waiting_services):
|
||||
"""Given a count of services waiting of each cpu class, return the
|
||||
count of nodes needed of each node type if the maximum wait times and
|
||||
waiting service count thresholds have been reached.
|
||||
"""
|
||||
rval = {}
|
||||
new_waiting_since = {}
|
||||
for cpu_class in waiting_services.keys():
|
||||
new_waiting_since[cpu_class] = self._waiting_since.get(cpu_class, time.time())
|
||||
# filter out any services that have already been handled
|
||||
unhandled_waiting_services = [ s for s in waiting_services[cpu_class] if s not in self._handled_services ]
|
||||
if (len(unhandled_waiting_services) > self.max_waiting_services and
|
||||
time.time() - new_waiting_since[cpu_class] > self.max_wait_time):
|
||||
# need waiting[cpu_class] nodes of this class
|
||||
rval[cpu_class] = len(unhandled_waiting_services)
|
||||
# drop any cpu_classes from waiting_since that are no longer waiting
|
||||
self._waiting_since = new_waiting_since
|
||||
return rval
|
||||
|
||||
def spawning_nodes(self):
|
||||
now = time.time()
|
||||
for cpu_class in self._spawning_nodes.keys():
|
||||
for node_name in self._spawning_nodes[cpu_class].keys():
|
||||
node = self._spawning_nodes[cpu_class][node_name]
|
||||
yield (node_name, now - node['time_requested'], cpu_class, node)
|
||||
|
||||
def _spawning_cpu_counts(self):
|
||||
rval = {}
|
||||
for cpu_class in self._spawning_nodes.keys():
|
||||
rval[cpu_class] = sum([ v['cpu_count'] for k, v in self._spawning_nodes[cpu_class].items() ])
|
||||
return rval
|
||||
|
||||
def nodes_requested(self, cpu_class, nodes, services):
|
||||
if cpu_class not in self._spawning_nodes:
|
||||
self._spawning_nodes[cpu_class] = {}
|
||||
for node in nodes:
|
||||
node_name = node.split(':')[0]
|
||||
try:
|
||||
cpu_count = node.split(':')[1]
|
||||
except IndexError:
|
||||
cpu_count = cpu_class
|
||||
self._spawning_nodes[cpu_class][node_name] = {
|
||||
'state': 'requested',
|
||||
'cpu_count': cpu_count,
|
||||
'time_requested': time.time(),
|
||||
}
|
||||
|
||||
def mark_services_handled(self, services):
|
||||
self._handled_services.update(services)
|
||||
|
||||
def mark_spawning_node_ready(self, node_name):
|
||||
self._delete_spawning_node(node_name)
|
||||
|
||||
def mark_spawning_node_timeout(self, node_name):
|
||||
self._delete_spawning_node(node_name)
|
||||
|
||||
def _delete_spawning_node(self, node_name):
|
||||
for cpu_class in self._spawning_nodes.keys():
|
||||
if node_name in self._spawning_nodes[cpu_class]:
|
||||
del self._spawning_nodes[cpu_class][node_name]
|
||||
|
||||
def mark_spawning_node_state(self, node_name, state):
|
||||
for cpu_class in self._spawning_nodes.keys():
|
||||
if node_name in self._spawning_nodes[cpu_class]:
|
||||
self._spawning_nodes[cpu_class][node_name]['state'] = state
|
||||
|
||||
def is_destruction_time(self, node_name):
|
||||
now = time.time()
|
||||
return now - self._surplus_nodes.get(node_name, now) > self.max_node_idle_time
|
||||
|
||||
def mark_node_idle(self, node_name):
|
||||
if node_name not in self._surplus_nodes:
|
||||
self._surplus_nodes[node_name] = time.time()
|
||||
|
||||
def clear_node_idle(self, node_name):
|
||||
if node_name in self._surplus_nodes:
|
||||
del self._surplus_nodes[node_name]
|
||||
|
||||
def clean_services(self, services):
|
||||
self._handled_services.difference_update(services)
|
||||
|
||||
|
||||
def main(argv=None, fork=False):
|
||||
if not daemon:
|
||||
log.warning('The daemon module is required to use the swarm manager, install it with `pip install python-daemon`')
|
||||
return
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
if fork:
|
||||
p = subprocess.Popen([sys.executable, __file__] + argv)
|
||||
p.wait()
|
||||
else:
|
||||
args = _arg_parser().parse_args(argv)
|
||||
kwargs = _app_properties(args)
|
||||
_run_swarm_manager(kwargs, args)
|
||||
|
||||
|
||||
def _app_properties(args):
|
||||
galaxy_config_file = find_config_file("config/galaxy.ini", "universe_wsgi.ini", args.galaxy_config_file)
|
||||
app_properties = load_app_properties(ini_file=galaxy_config_file)
|
||||
return app_properties
|
||||
|
||||
|
||||
def _arg_parser():
|
||||
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||
parser.add_argument("-c", "--galaxy-config-file", default=None)
|
||||
return parser
|
||||
|
||||
|
||||
def _run_swarm_manager(kwargs, args):
|
||||
configure_logging(kwargs)
|
||||
global log
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
root = find_root(kwargs)
|
||||
swarm_manager_config_file = find_path(kwargs, "swarm_manager_config_file", root)
|
||||
swarm_manager_conf = _parse_swarm_manager_conf(swarm_manager_config_file)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(swarm_manager_conf['pid_file']))
|
||||
except (IOError, OSError) as exc:
|
||||
if exc.errno != errno.EEXIST:
|
||||
raise
|
||||
log.debug("daemonizing, logs will be written to '%s'", swarm_manager_conf['log_file'])
|
||||
pidfile = daemon.pidfile.PIDLockFile(swarm_manager_conf['pid_file'])
|
||||
with open(swarm_manager_conf['log_file'], 'a') as logfh:
|
||||
try:
|
||||
with daemon.DaemonContext(
|
||||
pidfile=pidfile,
|
||||
stdout=logfh,
|
||||
stderr=logfh,
|
||||
):
|
||||
_swarm_manager(swarm_manager_conf)
|
||||
except lockfile.AlreadyLocked:
|
||||
log.debug("attempt to daemonize with swarm manager already running ignored")
|
||||
|
||||
|
||||
def _load_xdg_environment():
|
||||
return dict(
|
||||
data_home=os.path.expanduser(os.environ.get('XDG_DATA_HOME', '~/.local/share')),
|
||||
)
|
||||
|
||||
|
||||
def _parse_swarm_manager_conf(swarm_manager_config_file):
|
||||
conf = SWARM_MANAGER_CONF_DEFAULTS.copy()
|
||||
xdg_env = _load_xdg_environment()
|
||||
try:
|
||||
with open(swarm_manager_config_file) as fh:
|
||||
conf.update(yaml.load(fh))
|
||||
except (OSError, IOError) as exc:
|
||||
if exc.errno == errno.ENOENT:
|
||||
log.warning("config file '%s' does not exist, running with default config", swarm_manager_config_file)
|
||||
else:
|
||||
raise
|
||||
for opt in ('pid_file', 'log_file'):
|
||||
conf[opt] = conf[opt].format(xdg_data_home=xdg_env['data_home'])
|
||||
return conf
|
||||
|
||||
|
||||
def _swarm_manager(conf):
|
||||
swarm_manager = SwarmManager(conf)
|
||||
log.debug("swarm manager loaded, running...")
|
||||
swarm_manager.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
__name__ = 'swarm_manager'
|
||||
main()
|
||||
@@ -86,7 +86,7 @@ PasteDeploy==1.5.2 --hash:sha256=3922127d3acc6e274a800978b9293c874b3ef4ac2eb8bf4
|
||||
docutils==0.12 --hash:sha256=732dfc2d706ea390c264bc69110d3d6c7c3a520628a052ccc5998466f95d5c29
|
||||
wchartype==0.1 --hash:sha256=2932471fe3a4e5cac4539c1b49bbc2bbd24ec3f00532c7f27c80b7756fdc177d
|
||||
repoze.lru==0.6 --hash:sha256=731cc0b0a184c9fd270a4f29d9635099bb0398823e9a1a39bf4f488d1ead57b3
|
||||
Routes==2.2 --hash:sha256=1d61dc9f1bbd86504de221568743133fbdfeb70f11cee4222cef72aa108e834c
|
||||
Routes==2.4.1 --hash:sha256=cc3a4b9a34bfe6b64b1d10d18b845bb2b6cb3ee1fbf6107295dfafe629bb84aa
|
||||
WebOb==1.4.1 --hash:sha256=dc3c45ac0b56a3c65f47f1da6c23760dfe0139e8a84caaf1cfc9276b3e10875a
|
||||
WebHelpers==1.3 --hash:sha256=8969ec3fb851872096067a805d512c3bed1952e6fc66f1ef1d1c6a7470937688
|
||||
Mako==1.0.2 --hash:sha256=d3f372cbc2e7de080b5f7056d160f3ac8279353a2ea8f1ef8fd5b0cf0a6a3b17
|
||||
|
||||
@@ -17,7 +17,7 @@ PasteDeploy==1.5.2
|
||||
docutils==0.12
|
||||
wchartype==0.1
|
||||
repoze.lru==0.6
|
||||
Routes==2.2
|
||||
Routes==2.4.1
|
||||
WebOb==1.4.1
|
||||
WebHelpers==1.3
|
||||
Mako==1.0.2
|
||||
|
||||
@@ -28,6 +28,7 @@ import galaxy.model.orm.now
|
||||
import galaxy.security.passwords
|
||||
import galaxy.util
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.model.util import pgcalc
|
||||
from galaxy.security import get_permitted_actions
|
||||
from galaxy.util import (directory_hash_id, Params, ready_name_for_url,
|
||||
restore_text, send_mail, unicodify, unique_id)
|
||||
@@ -275,6 +276,31 @@ class User( object, Dictifiable ):
|
||||
total += hda.dataset.get_total_size()
|
||||
return total
|
||||
|
||||
def calculate_and_set_disk_usage( self ):
|
||||
"""
|
||||
Calculates and sets user disk usage.
|
||||
"""
|
||||
new = None
|
||||
db_session = object_session(self)
|
||||
current = self.get_disk_usage()
|
||||
if db_session.get_bind().dialect.name not in ( 'postgres', 'postgresql' ):
|
||||
done = False
|
||||
while not done:
|
||||
new = self.calculate_disk_usage()
|
||||
db_session.refresh( self )
|
||||
# make sure usage didn't change while calculating
|
||||
# set done if it has not, otherwise reset current and iterate again.
|
||||
if self.get_disk_usage() == current:
|
||||
done = True
|
||||
else:
|
||||
current = self.get_disk_usage()
|
||||
else:
|
||||
new = pgcalc(db_session, self.id)
|
||||
if new not in (current, None):
|
||||
self.set_disk_usage( new )
|
||||
db_session.add( self )
|
||||
db_session.flush()
|
||||
|
||||
@staticmethod
|
||||
def user_template_environment( user ):
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,6 @@ import time
|
||||
|
||||
import galaxy.queues
|
||||
from galaxy import util
|
||||
from galaxy.model.util import pgcalc
|
||||
|
||||
from kombu import Connection
|
||||
from kombu.mixins import ConsumerMixin
|
||||
@@ -143,13 +142,11 @@ def recalculate_user_disk_usage(app, **kwargs):
|
||||
if user_id:
|
||||
user = sa_session.query( app.model.User ).get( app.security.decode_id( user_id ) )
|
||||
if user:
|
||||
if sa_session.get_bind().dialect.name not in ( 'postgres', 'postgresql' ):
|
||||
new = user.calculate_disk_usage()
|
||||
else:
|
||||
new = pgcalc(sa_session, user.id)
|
||||
user.set_disk_usage(new)
|
||||
sa_session.add(user)
|
||||
sa_session.flush()
|
||||
user.calculate_and_set_disk_usage()
|
||||
else:
|
||||
log.error("Recalculate user disk usage task failed, user %s not found" % user_id)
|
||||
else:
|
||||
log.error("Recalculate user disk usage task received without user_id.")
|
||||
|
||||
|
||||
def reload_tool_data_tables(app, **kwargs):
|
||||
@@ -226,13 +223,13 @@ class GalaxyQueueWorker(ConsumerMixin, threading.Thread):
|
||||
if body.get('noop', None) != self.app.config.server_name:
|
||||
try:
|
||||
f = self.task_mapping[body['task']]
|
||||
log.info("Instance '%s' recieved '%s' task, executing now.", self.app.config.server_name, body['task'])
|
||||
log.info("Instance '%s' received '%s' task, executing now.", self.app.config.server_name, body['task'])
|
||||
f(self.app, **body['kwargs'])
|
||||
except Exception:
|
||||
# this shouldn't ever throw an exception, but...
|
||||
log.exception("Error running control task type: %s" % body['task'])
|
||||
else:
|
||||
log.warning("Recieved a malformed task message:\n%s" % body)
|
||||
log.warning("Received a malformed task message:\n%s" % body)
|
||||
message.ack()
|
||||
|
||||
def shutdown(self):
|
||||
|
||||
@@ -198,8 +198,8 @@ class DependencyManager( object ):
|
||||
return [
|
||||
ToolShedPackageDependencyResolver(self),
|
||||
GalaxyPackageDependencyResolver(self),
|
||||
GalaxyPackageDependencyResolver(self, versionless=True),
|
||||
CondaDependencyResolver(self),
|
||||
GalaxyPackageDependencyResolver(self, versionless=True),
|
||||
CondaDependencyResolver(self, versionless=True),
|
||||
]
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ class CondaContext(installable.InstallableContext):
|
||||
return self.exec_command("create", create_base_args)
|
||||
|
||||
def exec_remove(self, args):
|
||||
"""Remove a conda environment using conda env remove -y --name `args`."""
|
||||
remove_base_args = [
|
||||
"remove",
|
||||
"-y",
|
||||
|
||||
@@ -19,7 +19,7 @@ class DependencyResolver(Dictifiable, object):
|
||||
"""Abstract description of a technique for resolving container images for tool execution."""
|
||||
|
||||
# Keys for dictification.
|
||||
dict_collection_visible_keys = ['resolver_type', 'resolves_simple_dependencies']
|
||||
dict_collection_visible_keys = ['resolver_type', 'resolves_simple_dependencies', 'can_uninstall_dependencies']
|
||||
# A "simple" dependency is one that does not depend on the the tool
|
||||
# resolving the dependency. Classic tool shed dependencies are non-simple
|
||||
# because the repository install context is used in dependency resolution
|
||||
@@ -27,6 +27,7 @@ class DependencyResolver(Dictifiable, object):
|
||||
# resolution.
|
||||
disabled = False
|
||||
resolves_simple_dependencies = True
|
||||
can_uninstall_dependencies = False
|
||||
config_options = {}
|
||||
|
||||
@abstractmethod
|
||||
@@ -47,8 +48,17 @@ class MultipleDependencyResolver:
|
||||
"""Variant of DependencyResolver that can optionally resolve multiple dependencies together."""
|
||||
|
||||
@abstractmethod
|
||||
def resolve_all( self, requirements, **kwds ):
|
||||
"""Given multiple requirements yield Dependency objects if and only if they may all be resolved together.
|
||||
def resolve_all(self, requirements, **kwds):
|
||||
"""
|
||||
Given multiple requirements yields a list of Dependency objects if and only if they may all be resolved together.
|
||||
|
||||
Unsuccessfull attempts should return an empty list.
|
||||
|
||||
:param requirements: list of tool requirements
|
||||
:param type: [ToolRequirement] or ToolRequirements
|
||||
|
||||
:returns: list of resolved dependencies
|
||||
:rtype: [Dependency]
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
|
||||
_specification_pattern = re.compile(r"https\:\/\/anaconda.org\/\w+\/\w+")
|
||||
|
||||
def __init__(self, dependency_manager, **kwds):
|
||||
self.can_uninstall_dependencies = True
|
||||
self._setup_mapping(dependency_manager, **kwds)
|
||||
self.versionless = _string_as_bool(kwds.get('versionless', 'false'))
|
||||
self.dependency_manager = dependency_manager
|
||||
@@ -116,6 +117,25 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
|
||||
def clean(self, **kwds):
|
||||
return self.conda_context.exec_clean()
|
||||
|
||||
def uninstall(self, requirements):
|
||||
"""Uninstall requirements installed by install_all or multiple install statements."""
|
||||
all_resolved = [r for r in self.resolve_all(requirements) if r.dependency_type]
|
||||
if not all_resolved:
|
||||
all_resolved = [self.resolve(requirement) for requirement in requirements]
|
||||
all_resolved = [r for r in all_resolved if r.dependency_type]
|
||||
if not all_resolved:
|
||||
return None
|
||||
environments = set([os.path.basename(dependency.environment_path) for dependency in all_resolved])
|
||||
return_codes = [self.conda_context.exec_remove([env]) for env in environments]
|
||||
final_return_code = 0
|
||||
for env, return_code in zip(environments, return_codes):
|
||||
if return_code == 0:
|
||||
log.debug("Conda environment '%s' successfully removed." % env)
|
||||
else:
|
||||
log.debug("Conda environment '%s' could not be removed." % env)
|
||||
final_return_code = return_code
|
||||
return final_return_code
|
||||
|
||||
def install_all(self, conda_targets):
|
||||
env = self.merged_environment_name(conda_targets)
|
||||
return_code = install_conda_targets(conda_targets, env, conda_context=self.conda_context)
|
||||
@@ -132,15 +152,30 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
|
||||
return is_installed
|
||||
|
||||
def resolve_all(self, requirements, **kwds):
|
||||
"""
|
||||
Some combinations of tool requirements need to be resolved all at once, so that Conda can select a compatible
|
||||
combination of dependencies. This method returns a list of MergedCondaDependency instances (one for each requirement)
|
||||
if all requirements have been successfully resolved, or an empty list if any of the requirements could not be resolved.
|
||||
|
||||
Parameters specific to this resolver are:
|
||||
|
||||
preserve_python_environment: Boolean, controls whether the python environment should be maintained during job creation for tools
|
||||
that rely on galaxy being importable.
|
||||
|
||||
install: Controls if `requirements` should be installed. If `install` is True and the requirements are not installed
|
||||
an attempt is made to install the requirements. If `install` is None requirements will only be installed if
|
||||
`conda_auto_install` has been activated and the requirements are not yet installed. If `install` is
|
||||
False will not install requirements.
|
||||
"""
|
||||
if len(requirements) == 0:
|
||||
return False
|
||||
return []
|
||||
|
||||
if not os.path.isdir(self.conda_context.conda_prefix):
|
||||
return False
|
||||
return []
|
||||
|
||||
for requirement in requirements:
|
||||
if requirement.type != "package":
|
||||
return False
|
||||
return []
|
||||
|
||||
ToolRequirements = galaxy.tools.deps.requirements.ToolRequirements
|
||||
expanded_requirements = ToolRequirements([self._expand_requirement(r) for r in requirements])
|
||||
@@ -232,7 +267,7 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
|
||||
if job_directory:
|
||||
conda_environment = os.path.join(job_directory, conda_env)
|
||||
else:
|
||||
conda_environment = None
|
||||
conda_environment = self.conda_context.env_path(conda_target.install_environment)
|
||||
|
||||
return CondaDependency(
|
||||
self.conda_context,
|
||||
|
||||
@@ -55,6 +55,22 @@ class DependencyResolversView(object):
|
||||
dependencies_per_tool = {tool: self._dependency_manager.requirements_to_dependencies(requirements, **kwds) for tool, requirements in tool_requirements_d.items()}
|
||||
return dependencies_per_tool
|
||||
|
||||
def uninstall_dependencies(self, index=None, **payload):
|
||||
"""Attempt to uninstall requirements. Returns 0 if successfull, else None."""
|
||||
requirements = payload.get('requirements')
|
||||
if not requirements:
|
||||
return None
|
||||
if index:
|
||||
resolver = self._dependency_resolvers[index]
|
||||
if resolver.can_uninstall_dependencies:
|
||||
return resolver.uninstall(requirements)
|
||||
else:
|
||||
for index in self.uninstallable_resolvers:
|
||||
return_code = self._dependency_resolvers[index].uninstall(requirements)
|
||||
if return_code == 0:
|
||||
return return_code
|
||||
return None
|
||||
|
||||
def install_dependencies(self, requirements):
|
||||
return self._dependency_manager._requirements_to_dependencies_dict(requirements, **{'install': True})
|
||||
|
||||
@@ -134,10 +150,17 @@ class DependencyResolversView(object):
|
||||
@property
|
||||
def installable_resolvers(self):
|
||||
"""
|
||||
List index for all active resolvers that have the 'install_dependency' attribute
|
||||
List index for all active resolvers that have the 'install_dependency' attribute.
|
||||
"""
|
||||
return [index for index, resolver in enumerate(self._dependency_resolvers) if hasattr(resolver, "install_dependency") and not resolver.disabled ]
|
||||
|
||||
@property
|
||||
def uninstallable_resolvers(self):
|
||||
"""
|
||||
List index for all active resolvers that can uninstall dependencies that have been installed through this resolver.
|
||||
"""
|
||||
return [index for index, resolver in enumerate(self._dependency_resolvers) if resolver.can_uninstall_dependencies and not resolver.disabled]
|
||||
|
||||
def get_requirements_status(self, tool_requirements_d, installed_tool_dependencies=None):
|
||||
dependencies = self.show_dependencies(tool_requirements_d, installed_tool_dependencies)
|
||||
# dependencies is a dict keyed on tool_ids, value is a ToolRequirements object for that tool.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Basic tool parameters.
|
||||
"""
|
||||
import logging
|
||||
import numbers
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
@@ -131,8 +130,6 @@ class ToolParameter( object, Dictifiable ):
|
||||
|
||||
def to_python( self, value, app ):
|
||||
"""Convert a value created with to_json back to an object representation"""
|
||||
if isinstance( value, numbers.Number ):
|
||||
return unicodify( value )
|
||||
return value
|
||||
|
||||
def value_to_basic( self, value, app, use_security=False ):
|
||||
|
||||
@@ -356,6 +356,10 @@ Read more about configuring Galaxy to run Docker jobs
|
||||
<xs:documentation xml:lang="en"><![CDATA[
|
||||
**Deprecated** do not use this unless absolutely necessary.
|
||||
|
||||
The extensions described here can cause problems using your tool with certain components
|
||||
of Galaxy (like the workflow system). It is highly recommended to avoid these constructs
|
||||
unless absolutely necessary.
|
||||
|
||||
This tag set provides detailed control of the way the tool is executed. This
|
||||
(optional) code can be deployed in a separate file in the same directory as the
|
||||
tool's config file. These hooks are being replaced by new tool config features
|
||||
@@ -444,6 +448,76 @@ def get_field_components_options( dataset, field_name ):
|
||||
return options
|
||||
```
|
||||
|
||||
#### Parameter Validation
|
||||
|
||||
This function is called before the tool is executed. If it raises any exceptions the tool execution will be aborted and the exception's value will be displayed in an error message box. Here is an example:
|
||||
|
||||
```python
|
||||
def validate(incoming):
|
||||
"""Validator for the plotting program"""
|
||||
|
||||
|
||||
bins = incoming.get("bins","")
|
||||
col = incoming.get("col","")
|
||||
|
||||
|
||||
if not bins or not col:
|
||||
raise Exception, "You need to specify a number for bins and columns"
|
||||
|
||||
|
||||
try:
|
||||
bins = int(bins)
|
||||
col = int(col)
|
||||
except:
|
||||
raise Exception, "Parameters are not integers, columns:%s, bins:%s" % (col, bins)
|
||||
|
||||
|
||||
if not 1<bins<100:
|
||||
raise Exception, "The number of bins %s must be a number between 1 and 100" % bins
|
||||
```
|
||||
|
||||
This code will intercept a number of parameter errors and return corresponding error messages. The parameter ``incoming`` contains a dictionary with all the parameters that were sent through the web.
|
||||
|
||||
#### Pre-job and pre-process code
|
||||
|
||||
The signature of both of these is the same:
|
||||
|
||||
```python
|
||||
def exec_before_job(inp_data, out_data, param_dict, tool):
|
||||
def exec_before_process(inp_data, out_data, param_dict, tool):
|
||||
```
|
||||
|
||||
The ``param_dict`` is a dictionary that contains all the values in the ``incoming`` parameter above plus a number of keys and values generated internally by galaxy. The ``inp_data`` and the ``out_data`` are dictionaries keyed by parameter name containing the classes that represent the data.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
def exec_before_process(inp_data, out_data, param_dict, tool):
|
||||
for name, data in out_data.items():
|
||||
data.name = 'New name'
|
||||
```
|
||||
|
||||
This custom code will change the name of the data that was created for this tool to **New name**. The difference between these two functions is that the ``exec_before_job`` executes before the page returns and the user will see the new name right away. If one were to use ``exec_before_process`` the new name would be set only once the job starts to execute.
|
||||
|
||||
#### Post-process code
|
||||
|
||||
This code executes after the background process running the tool finishes its run. The example below is more advanced one that replaces the type of the output data depending on the parameter named ``extension``:
|
||||
|
||||
```python
|
||||
from galaxy import datatypes
|
||||
def exec_after_process(app, inp_data, out_data, param_dict, tool, stdout, stderr):
|
||||
ext = param_dict.get('extension', 'text')
|
||||
items = out_data.items()
|
||||
for name, data in items:
|
||||
newdata = datatypes.factory(ext)(id=data.id)
|
||||
for key, value in data. __dict__.items():
|
||||
setattr(newdata, key, value)
|
||||
newdata.ext = ext
|
||||
out_data[name] = newdata
|
||||
```
|
||||
|
||||
The content of ``stdout`` and ``stderr`` are strings containing the output of the process.
|
||||
|
||||
]]></xs:documentation>
|
||||
|
||||
</xs:annotation>
|
||||
|
||||
@@ -56,10 +56,13 @@ def swap_inf_nan( val ):
|
||||
def safe_loads( arg ):
|
||||
"""
|
||||
This is a wrapper around loads that returns the parsed value instead of
|
||||
raising a value error.
|
||||
raising a value error. It also avoids autoconversion of non-iterables
|
||||
i.e numeric and boolean values.
|
||||
"""
|
||||
try:
|
||||
loaded = json.loads( arg )
|
||||
if loaded is not None and not isinstance( loaded, collections.Iterable ):
|
||||
loaded = arg
|
||||
except ( TypeError, ValueError ):
|
||||
loaded = arg
|
||||
return loaded
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from six import string_types
|
||||
import six
|
||||
from string import punctuation as PUNCTUATION
|
||||
|
||||
from sqlalchemy import and_, false, func, or_
|
||||
@@ -931,10 +931,7 @@ class Admin( object ):
|
||||
kwd[ 'message' ] = util.sanitize_text( "Invalid user id (%s) received" % str( user_id ) )
|
||||
kwd[ 'status' ] = 'error'
|
||||
else:
|
||||
return trans.response.send_redirect( web.url_for( controller='user',
|
||||
action='manage_user_info',
|
||||
cntrller='admin',
|
||||
**kwd ) )
|
||||
return trans.response.send_redirect( web.url_for( controller='user', action='information', **kwd ) )
|
||||
elif operation == "manage roles and groups":
|
||||
return self.manage_roles_and_groups_for_user( trans, **kwd )
|
||||
if trans.app.config.allow_user_deletion:
|
||||
@@ -1123,6 +1120,35 @@ class Admin( object ):
|
||||
job=job,
|
||||
message="<a href='jobs'>Back</a>" )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def manage_tool_dependencies( self, trans, install_dependencies=False, uninstall_dependencies=False, selected_tool_ids=None, viewkey='View tool-centric dependencies'):
|
||||
if not selected_tool_ids:
|
||||
selected_tool_ids = []
|
||||
tools_by_id = trans.app.toolbox.tools_by_id
|
||||
view = six.next(six.itervalues(trans.app.toolbox.tools_by_id))._view
|
||||
if selected_tool_ids:
|
||||
# install the dependencies for the tools in the selected_tool_ids list
|
||||
if not isinstance(selected_tool_ids, list):
|
||||
selected_tool_ids = [selected_tool_ids]
|
||||
requirements = set([tools_by_id[tid].tool_requirements for tid in selected_tool_ids])
|
||||
if install_dependencies:
|
||||
[view.install_dependencies(r) for r in requirements]
|
||||
elif uninstall_dependencies:
|
||||
[view.uninstall_dependencies(index=None, requirements=r) for r in requirements]
|
||||
tool_ids_by_requirements = {}
|
||||
for tid, tool in trans.app.toolbox.tools_by_id.items():
|
||||
if tool.tool_requirements not in tool_ids_by_requirements:
|
||||
tool_ids_by_requirements[tool.tool_requirements] = [tid]
|
||||
else:
|
||||
tool_ids_by_requirements[tool.tool_requirements].append(tid)
|
||||
requirements_status = {r: view.get_requirements_status({tid: r}, tools_by_id[tids[0]].installed_tool_dependencies) for r, tids in tool_ids_by_requirements.items()}
|
||||
return trans.fill_template( '/webapps/galaxy/admin/manage_dependencies.mako',
|
||||
tools=tools_by_id,
|
||||
requirements_status=requirements_status,
|
||||
tool_ids_by_requirements=tool_ids_by_requirements,
|
||||
viewkey=viewkey )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def sanitize_whitelist( self, trans, submit_whitelist=False, tools_to_whitelist=[]):
|
||||
@@ -1130,7 +1156,7 @@ class Admin( object ):
|
||||
# write the configured sanitize_whitelist_file with new whitelist
|
||||
# and update in-memory list.
|
||||
with open(trans.app.config.sanitize_whitelist_file, 'wt') as f:
|
||||
if isinstance(tools_to_whitelist, string_types):
|
||||
if isinstance(tools_to_whitelist, six.string_types):
|
||||
tools_to_whitelist = [tools_to_whitelist]
|
||||
new_whitelist = sorted([tid for tid in tools_to_whitelist if tid in trans.app.toolbox.tools_by_id])
|
||||
f.write("\n".join(new_whitelist))
|
||||
|
||||
@@ -11,6 +11,7 @@ from subprocess import Popen, PIPE
|
||||
|
||||
from galaxy.util import string_as_bool_or_none
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.container import docker_swarm
|
||||
from galaxy import web, model
|
||||
from galaxy.managers import api_keys
|
||||
from galaxy.tools.deps.docker_util import DockerVolume
|
||||
@@ -370,6 +371,9 @@ class InteractiveEnvironmentRequest(object):
|
||||
log.debug( "Container host: %s", self.attr.docker_hostname )
|
||||
host_port = None
|
||||
|
||||
if self.attr.swarm_mode:
|
||||
docker_swarm.main(argv=['-c', self.trans.app.config.config_file], fork=True)
|
||||
|
||||
if len(port_mappings) > 1:
|
||||
if self.attr.docker_connect_port is not None:
|
||||
for _service, _host_ip, _host_port in port_mappings:
|
||||
@@ -393,7 +397,9 @@ class InteractiveEnvironmentRequest(object):
|
||||
port=host_port,
|
||||
proxy_prefix=self.attr.proxy_prefix,
|
||||
route_name=self.attr.viz_id,
|
||||
container_ids=[container_id],
|
||||
container_ids=[container_id] if not self.attr.swarm_mode else [],
|
||||
service_ids=[container_id] if self.attr.swarm_mode else [],
|
||||
docker_command=self.attr.viz_config.get("docker", "command"),
|
||||
)
|
||||
# These variables then become available for use in templating URLs
|
||||
self.attr.proxy_url = self.attr.proxy_request[ 'proxy_url' ]
|
||||
|
||||
@@ -62,10 +62,10 @@ class WebApplication( object ):
|
||||
self.controllers = dict()
|
||||
self.api_controllers = dict()
|
||||
self.mapper = routes.Mapper()
|
||||
self.clientside_routes = routes.Mapper(controller_scan=None, register=False)
|
||||
# FIXME: The following two options are deprecated and should be
|
||||
# removed. Consult the Routes documentation.
|
||||
self.mapper.minimization = True
|
||||
# self.mapper.explicit = False
|
||||
self.transaction_factory = DefaultWebTransaction
|
||||
# Set if trace logging is enabled
|
||||
self.trace_logger = None
|
||||
@@ -98,7 +98,7 @@ class WebApplication( object ):
|
||||
self.mapper.connect( route, **kwargs )
|
||||
|
||||
def add_client_route( self, route ):
|
||||
self.add_route(route, controller='root', action='client')
|
||||
self.clientside_routes.connect( route, controller='root', action='client' )
|
||||
|
||||
def set_transaction_factory( self, transaction_factory ):
|
||||
"""
|
||||
@@ -114,6 +114,7 @@ class WebApplication( object ):
|
||||
"""
|
||||
# Create/compile the regular expressions for route mapping
|
||||
self.mapper.create_regs( self.controllers.keys() )
|
||||
self.clientside_routes.create_regs()
|
||||
|
||||
def trace( self, **fields ):
|
||||
if self.trace_logger:
|
||||
@@ -137,43 +138,23 @@ class WebApplication( object ):
|
||||
if self.trace_logger:
|
||||
self.trace_logger.context_remove( "request_id" )
|
||||
|
||||
def handle_request( self, environ, start_response, body_renderer=None ):
|
||||
# Grab the request_id (should have been set by middleware)
|
||||
request_id = environ.get( 'request_id', 'unknown' )
|
||||
# Map url using routes
|
||||
path_info = environ.get( 'PATH_INFO', '' )
|
||||
map = self.mapper.match( path_info, environ )
|
||||
if path_info.startswith('/api'):
|
||||
environ[ 'is_api_request' ] = True
|
||||
controllers = self.api_controllers
|
||||
else:
|
||||
environ[ 'is_api_request' ] = False
|
||||
controllers = self.controllers
|
||||
if map is None:
|
||||
raise httpexceptions.HTTPNotFound( "No route for " + path_info )
|
||||
self.trace( path_info=path_info, map=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 )
|
||||
trans.request_id = request_id
|
||||
rc.redirect = trans.response.send_redirect
|
||||
def _resolve_map_match( self, map_match, path_info, controllers, use_default=True):
|
||||
# Get the controller class
|
||||
controller_name = map.pop( 'controller', None )
|
||||
controller_name = map_match.pop( 'controller', None )
|
||||
controller = controllers.get( controller_name, None )
|
||||
if controller_name is None:
|
||||
if controller is None:
|
||||
raise httpexceptions.HTTPNotFound( "No controller for " + path_info )
|
||||
# Resolve action method on controller
|
||||
action = map.pop( 'action', 'index' )
|
||||
# This is the easiest way to make the controller/action accessible for
|
||||
# url_for invocations. Specifically, grids.
|
||||
trans.controller = controller_name
|
||||
trans.action = action
|
||||
action = map_match.pop( 'action', 'index' )
|
||||
method = getattr( controller, action, None )
|
||||
if method is None and not use_default:
|
||||
# Skip default, we do this, for example, when we want to fail
|
||||
# through to another mapper.
|
||||
raise httpexceptions.HTTPNotFound( "No action for " + path_info )
|
||||
if method is None:
|
||||
# no matching method, we try for a default
|
||||
method = getattr( controller, 'default', None )
|
||||
if method is None:
|
||||
raise httpexceptions.HTTPNotFound( "No action for " + path_info )
|
||||
@@ -183,10 +164,50 @@ class WebApplication( object ):
|
||||
# Is the method callable
|
||||
if not callable( method ):
|
||||
raise httpexceptions.HTTPNotFound( "Action not callable for " + path_info )
|
||||
return ( controller_name, controller, action, method )
|
||||
|
||||
def handle_request( self, environ, start_response, body_renderer=None ):
|
||||
# Grab the request_id (should have been set by middleware)
|
||||
request_id = environ.get( 'request_id', 'unknown' )
|
||||
# Map url using routes
|
||||
path_info = environ.get( 'PATH_INFO', '' )
|
||||
client_match = self.clientside_routes.match( path_info, environ )
|
||||
map_match = self.mapper.match( path_info, environ ) or client_match
|
||||
if path_info.startswith('/api'):
|
||||
environ[ 'is_api_request' ] = True
|
||||
controllers = self.api_controllers
|
||||
else:
|
||||
environ[ 'is_api_request' ] = False
|
||||
controllers = self.controllers
|
||||
if map_match is None:
|
||||
raise httpexceptions.HTTPNotFound( "No route for " + path_info )
|
||||
self.trace( path_info=path_info, map_match=map_match )
|
||||
# Setup routes
|
||||
rc = routes.request_config()
|
||||
rc.mapper = self.mapper
|
||||
rc.mapper_dict = map_match
|
||||
rc.environ = environ
|
||||
# Setup the transaction
|
||||
trans = self.transaction_factory( environ )
|
||||
trans.request_id = request_id
|
||||
rc.redirect = trans.response.send_redirect
|
||||
# Resolve mapping to controller/method
|
||||
try:
|
||||
# We don't use default methods if there's a clientside match for this route.
|
||||
use_default = client_match is None
|
||||
controller_name, controller, action, method = self._resolve_map_match( map_match, path_info, controllers, use_default=use_default)
|
||||
except httpexceptions.HTTPNotFound:
|
||||
# Failed, let's check client routes
|
||||
if not environ[ 'is_api_request' ]:
|
||||
controller_name, controller, action, method = self._resolve_map_match( client_match, path_info, controllers )
|
||||
else:
|
||||
raise
|
||||
trans.controller = controller_name
|
||||
trans.action = action
|
||||
environ['controller_action_key'] = "%s.%s.%s" % ('api' if environ['is_api_request'] else 'web', controller_name, action or 'default')
|
||||
# Combine mapper args and query string / form args and call
|
||||
kwargs = trans.request.params.mixed()
|
||||
kwargs.update( map )
|
||||
kwargs.update( map_match )
|
||||
# Special key for AJAX debugging, remove to avoid confusing methods
|
||||
kwargs.pop( '_', None )
|
||||
try:
|
||||
|
||||
@@ -346,7 +346,7 @@ class GridColumn( object ):
|
||||
def __init__( self, label, key=None, model_class=None, method=None, format=None,
|
||||
link=None, attach_popup=False, visible=True, nowrap=False,
|
||||
# Valid values for filterable are ['standard', 'advanced', None]
|
||||
filterable=None, sortable=True, label_id_prefix=None, inbound=False ):
|
||||
filterable=None, sortable=True, label_id_prefix=None, target=None ):
|
||||
"""Create a grid column."""
|
||||
self.label = label
|
||||
self.key = key
|
||||
@@ -354,7 +354,7 @@ class GridColumn( object ):
|
||||
self.method = method
|
||||
self.format = format
|
||||
self.link = link
|
||||
self.inbound = inbound
|
||||
self.target = target
|
||||
self.nowrap = nowrap
|
||||
self.attach_popup = attach_popup
|
||||
self.visible = visible
|
||||
@@ -799,7 +799,7 @@ class SharingStatusColumn( GridColumn ):
|
||||
class GridOperation( object ):
|
||||
def __init__( self, label, key=None, condition=None, allow_multiple=True, allow_popup=True,
|
||||
target=None, url_args=None, async_compatible=False, confirm=None,
|
||||
global_operation=None, inbound=False ):
|
||||
global_operation=None ):
|
||||
self.label = label
|
||||
self.key = key
|
||||
self.allow_multiple = allow_multiple
|
||||
@@ -808,7 +808,6 @@ class GridOperation( object ):
|
||||
self.target = target
|
||||
self.url_args = url_args
|
||||
self.async_compatible = async_compatible
|
||||
self.inbound = inbound
|
||||
# if 'confirm' is set, then ask before completing the operation
|
||||
self.confirm = confirm
|
||||
# specify a general operation that acts on the full grid
|
||||
@@ -842,10 +841,10 @@ class DisplayByUsernameAndSlugGridOperation( GridOperation ):
|
||||
|
||||
|
||||
class GridAction( object ):
|
||||
def __init__( self, label=None, url_args=None, inbound=False ):
|
||||
def __init__( self, label=None, url_args=None, target=None ):
|
||||
self.label = label
|
||||
self.url_args = url_args
|
||||
self.inbound = inbound
|
||||
self.target = target
|
||||
|
||||
|
||||
class GridColumnFilter( object ):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from collections import namedtuple
|
||||
|
||||
from galaxy.util.filelock import FileLock
|
||||
from galaxy.util import sockets
|
||||
@@ -46,7 +47,7 @@ class ProxyManager(object):
|
||||
def shutdown( self ):
|
||||
self.lazy_process.shutdown()
|
||||
|
||||
def setup_proxy( self, trans, host=DEFAULT_PROXY_TO_HOST, port=None, proxy_prefix="", route_name="", container_ids=None ):
|
||||
def setup_proxy( self, trans, host=DEFAULT_PROXY_TO_HOST, port=None, proxy_prefix="", route_name="", container_ids=None, service_ids=None, docker_command=None ):
|
||||
if self.manage_dynamic_proxy:
|
||||
log.info("Attempting to start dynamic proxy process")
|
||||
log.debug("Cmd: " + ' '.join(self.lazy_process.command_and_args))
|
||||
@@ -54,6 +55,8 @@ class ProxyManager(object):
|
||||
|
||||
if container_ids is None:
|
||||
container_ids = []
|
||||
if service_ids is None:
|
||||
service_ids = []
|
||||
|
||||
authentication = AuthenticationToken(trans)
|
||||
proxy_requests = ProxyRequests(host=host, port=port)
|
||||
@@ -61,7 +64,9 @@ class ProxyManager(object):
|
||||
authentication,
|
||||
proxy_requests,
|
||||
'/%s' % route_name,
|
||||
container_ids
|
||||
container_ids,
|
||||
service_ids,
|
||||
docker_command,
|
||||
)
|
||||
# TODO: These shouldn't need to be request.host and request.scheme -
|
||||
# though they are reasonable defaults.
|
||||
@@ -79,6 +84,10 @@ class ProxyManager(object):
|
||||
'proxied_host': proxy_requests.host,
|
||||
}
|
||||
|
||||
def query_proxy( self, trans ):
|
||||
authentication = AuthenticationToken(trans)
|
||||
return self.proxy_ipc.fetch_requests(authentication)
|
||||
|
||||
def __setup_lazy_process( self, config ):
|
||||
launcher = self.proxy_launcher()
|
||||
command = launcher.launch_proxy_command(config)
|
||||
@@ -174,7 +183,10 @@ def proxy_ipc(config):
|
||||
|
||||
class ProxyIpc(object):
|
||||
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids):
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids, service_ids, docker_command):
|
||||
raise NotImplementedError()
|
||||
|
||||
def fetch_requests(self, authentication, key):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -183,50 +195,103 @@ class JsonFileProxyIpc(object):
|
||||
def __init__(self, proxy_session_map):
|
||||
self.proxy_session_map = proxy_session_map
|
||||
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids):
|
||||
key = "%s:%s" % ( proxy_requests.host, proxy_requests.port )
|
||||
secure_id = authentication.cookie_value
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids, service_ids, docker_command):
|
||||
key = authentication.cookie_value
|
||||
with FileLock( self.proxy_session_map ):
|
||||
if not os.path.exists( self.proxy_session_map ):
|
||||
open( self.proxy_session_map, "w" ).write( "{}" )
|
||||
json_data = open( self.proxy_session_map, "r" ).read()
|
||||
session_map = json.loads( json_data )
|
||||
to_remove = []
|
||||
for k, value in session_map.items():
|
||||
if value == secure_id:
|
||||
to_remove.append( k )
|
||||
for k in to_remove:
|
||||
del session_map[ k ]
|
||||
session_map[ key ] = secure_id
|
||||
session_map[ key ] = {
|
||||
'host': proxy_requests.host,
|
||||
'port': proxy_requests.port,
|
||||
'container_ids': container_ids,
|
||||
'service_ids': service_ids,
|
||||
'docker_command': docker_command,
|
||||
}
|
||||
new_json_data = json.dumps( session_map )
|
||||
open( self.proxy_session_map, "w" ).write( new_json_data )
|
||||
|
||||
def fetch_requests(self, authentication):
|
||||
key = authentication.cookie_value
|
||||
try:
|
||||
with open(self.proxy_session_map) as fh:
|
||||
session_map = json.load(fh)
|
||||
m = session_map[key]
|
||||
return ProxyMapping(
|
||||
host=m['host'],
|
||||
port=m['port'],
|
||||
container_ids=m['container_ids'],
|
||||
service_ids=m['service_ids'],
|
||||
docker_command=m['docker_command'],
|
||||
)
|
||||
except (TypeError, KeyError):
|
||||
log.warning('fetch_requests(): invalid key: %s', key)
|
||||
return None
|
||||
|
||||
|
||||
class SqliteProxyIpc(object):
|
||||
|
||||
def __init__(self, proxy_session_map):
|
||||
self.proxy_session_map = proxy_session_map
|
||||
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids):
|
||||
key = "%s:%s" % ( proxy_requests.host, proxy_requests.port )
|
||||
secure_id = authentication.cookie_value
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids, service_ids, docker_command):
|
||||
key = authentication.cookie_value
|
||||
with FileLock( self.proxy_session_map ):
|
||||
conn = sqlite.connect(self.proxy_session_map)
|
||||
try:
|
||||
c = conn.cursor()
|
||||
try:
|
||||
# Create table
|
||||
c.execute('''CREATE TABLE gxproxy
|
||||
(key text PRIMARY_KEY, secret text)''')
|
||||
c.execute('''CREATE TABLE gxproxy2
|
||||
(key text PRIMARY KEY,
|
||||
host text,
|
||||
port integer,
|
||||
container_ids text,
|
||||
service_ids text,
|
||||
docker_command text)''')
|
||||
except Exception:
|
||||
pass
|
||||
insert_tmpl = '''INSERT INTO gxproxy (key, secret) VALUES ('%s', '%s');'''
|
||||
insert = insert_tmpl % (key, secure_id)
|
||||
c.execute(insert)
|
||||
delete = '''DELETE FROM gxproxy2 WHERE key=?'''
|
||||
c.execute(delete, (key,))
|
||||
insert = '''INSERT INTO gxproxy2
|
||||
(key, host, port, container_ids, service_ids, docker_command)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'''
|
||||
c.execute(insert,
|
||||
(key,
|
||||
proxy_requests.host,
|
||||
proxy_requests.port,
|
||||
json.dumps(container_ids),
|
||||
json.dumps(service_ids),
|
||||
docker_command))
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def fetch_requests(self, authentication):
|
||||
key = authentication.cookie_value
|
||||
with FileLock( self.proxy_session_map):
|
||||
conn = sqlite.connect(self.proxy_session_map)
|
||||
try:
|
||||
c = conn.cursor()
|
||||
select = '''SELECT host, port, container_ids, service_ids, docker_command
|
||||
FROM gxproxy2
|
||||
WHERE key=?'''
|
||||
c.execute(select, (key,))
|
||||
try:
|
||||
host, port, container_ids, service_ids, docker_command = c.fetchone()
|
||||
except TypeError:
|
||||
log.warning('fetch_requests(): invalid key: %s', key)
|
||||
return None
|
||||
return ProxyMapping(
|
||||
host=host,
|
||||
port=port,
|
||||
container_ids=json.loads(container_ids),
|
||||
service_ids=json.loads(service_ids),
|
||||
docker_command=docker_command)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
class RestGolangProxyIpc(object):
|
||||
|
||||
@@ -234,7 +299,7 @@ class RestGolangProxyIpc(object):
|
||||
self.config = config
|
||||
self.api_url = 'http://127.0.0.1:%s/api?api_key=%s' % (self.config.dynamic_proxy_bind_port, self.config.dynamic_proxy_golang_api_key)
|
||||
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids, sleep=1):
|
||||
def handle_requests(self, authentication, proxy_requests, route_name, container_ids, service_ids, docker_command, sleep=1):
|
||||
"""Make a POST request to the GO proxy to register a route
|
||||
"""
|
||||
values = {
|
||||
@@ -260,4 +325,8 @@ class RestGolangProxyIpc(object):
|
||||
self.handle_requests(authentication, proxy_requests, route_name, container_ids, sleep=sleep + 1)
|
||||
pass
|
||||
|
||||
|
||||
ProxyMapping = namedtuple('ProxyMapping', ['host', 'port', 'container_ids', 'service_ids', 'docker_command'])
|
||||
|
||||
|
||||
# TODO: MQ diven proxy?
|
||||
|
||||
@@ -15,9 +15,7 @@ var updateFromJson = function(path, map) {
|
||||
var keyToSession = JSON.parse(content);
|
||||
var newSessions = {};
|
||||
for(var key in keyToSession) {
|
||||
var hostAndPort = key.split(":");
|
||||
// 'host': hostAndPort[0],
|
||||
newSessions[keyToSession[key]] = {'target': {'host': hostAndPort[0], 'port': parseInt(hostAndPort[1])}};
|
||||
newSessions[key] = {'target': {'host': keyToSession[key]['host'], 'port': parseInt(keyToSession[key]['port'])}};
|
||||
}
|
||||
for(var oldSession in map) {
|
||||
if(!(oldSession in newSessions)) {
|
||||
@@ -32,12 +30,10 @@ var updateFromJson = function(path, map) {
|
||||
var updateFromSqlite = function(path, map) {
|
||||
var newSessions = {};
|
||||
var loadSessions = function() {
|
||||
db.each("SELECT key, secret FROM gxproxy", function(err, row) {
|
||||
db.each("SELECT key, host, port FROM gxproxy2", function(err, row) {
|
||||
var key = row['key'];
|
||||
var secret = row['secret'];
|
||||
var hostAndPort = key.split(":");
|
||||
var target = {'host': hostAndPort[0], 'port': parseInt(hostAndPort[1])};
|
||||
newSessions[secret] = {'target': target};
|
||||
var target = {'host': row['host'], 'port': parseInt(row['port'])};
|
||||
newSessions[key] = {'target': target};
|
||||
}, finish);
|
||||
};
|
||||
|
||||
@@ -75,4 +71,4 @@ var mapFor = function(path) {
|
||||
return map;
|
||||
};
|
||||
|
||||
exports.mapFor = mapFor;
|
||||
exports.mapFor = mapFor;
|
||||
|
||||
@@ -23,7 +23,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def index(self, trans, **kwd):
|
||||
"""
|
||||
GET /api/dependencies_resolvers
|
||||
GET /api/dependency_resolvers
|
||||
"""
|
||||
return self._view.index()
|
||||
|
||||
@@ -31,7 +31,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def show(self, trans, id):
|
||||
"""
|
||||
GET /api/dependencies_resolver/<id>
|
||||
GET /api/dependency_resolvers/<id>
|
||||
"""
|
||||
return self._view.show(id)
|
||||
|
||||
@@ -39,7 +39,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def update(self, trans):
|
||||
"""
|
||||
PUT /api/dependencies_resolvers
|
||||
PUT /api/dependency_resolvers
|
||||
|
||||
Reload tool dependency resolution configuration.
|
||||
"""
|
||||
@@ -49,7 +49,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def resolver_dependency(self, trans, id, **kwds):
|
||||
"""
|
||||
GET /api/dependencies_resolver/{index}/dependency
|
||||
GET /api/dependency_resolvers/{index}/dependency
|
||||
|
||||
Resolve described requirement against specified dependency resolver.
|
||||
|
||||
@@ -75,7 +75,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def install_dependency(self, trans, id=None, **kwds):
|
||||
"""
|
||||
POST /api/dependencies_resolver/{index}/dependency
|
||||
POST /api/dependency_resolvers/{index}/dependency
|
||||
|
||||
Install described requirement against specified dependency resolver.
|
||||
|
||||
@@ -102,7 +102,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def manager_dependency(self, trans, **kwds):
|
||||
"""
|
||||
GET /api/dependencies_resolvers/dependency
|
||||
GET /api/dependency_resolvers/dependency
|
||||
|
||||
Resolve described requirement against all dependency resolvers, returning
|
||||
the match with highest priority.
|
||||
@@ -129,7 +129,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def resolver_requirements(self, trans, id, **kwds):
|
||||
"""
|
||||
GET /api/dependencies_resolver/{index}/requirements
|
||||
GET /api/dependency_resolvers/{index}/requirements
|
||||
|
||||
Find all "simple" requirements that could be resolved "exactly"
|
||||
by this dependency resolver. The dependency resolver must implement
|
||||
@@ -148,7 +148,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def manager_requirements(self, trans, **kwds):
|
||||
"""
|
||||
GET /api/dependencies_resolver/requirements
|
||||
GET /api/dependency_resolvers/requirements
|
||||
|
||||
Find all "simple" requirements that could be resolved "exactly"
|
||||
by all dependency resolvers that support this operation.
|
||||
@@ -167,7 +167,7 @@ class ToolDependenciesAPIController( BaseAPIController ):
|
||||
@require_admin
|
||||
def clean(self, trans, id=None, **kwds):
|
||||
"""
|
||||
POST /api/dependencies_resolver/{index}/clean
|
||||
POST /api/dependency_resolvers/{index}/clean
|
||||
|
||||
Cleans up intermediate files created by resolvers during the dependency
|
||||
installation.
|
||||
|
||||
@@ -135,12 +135,16 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ):
|
||||
@web.require_admin
|
||||
def install_dependencies(self, trans, id, **kwds):
|
||||
"""
|
||||
POST /api/tools/{tool_id}/install_dependencies
|
||||
POST /api/tools/{tool_id}/dependencies
|
||||
|
||||
This endpoint is also available through POST /api/tools/{tool_id}/install_dependencies,
|
||||
but will be deprecated in the future.
|
||||
|
||||
Attempts to install requirements via the dependency resolver
|
||||
|
||||
parameters:
|
||||
build_dependency_cache: If true, attempts to cache dependencies for this tool
|
||||
force_rebuild: If true and chache dir exists, attempts to delete cache dir
|
||||
force_rebuild: If true and cache dir exists, attempts to delete cache dir
|
||||
"""
|
||||
tool = self._get_tool(id)
|
||||
tool._view.install_dependencies(tool.requirements)
|
||||
@@ -150,6 +154,19 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ):
|
||||
# _view.install_dependencies should return a dict with stdout, stderr and success status
|
||||
return tool.tool_requirements_status
|
||||
|
||||
@expose_api
|
||||
@web.require_admin
|
||||
def uninstall_dependencies(self, trans, id, **kwds):
|
||||
"""
|
||||
DELETE /api/tools/{tool_id}/dependencies
|
||||
Attempts to uninstall requirements via the dependency resolver
|
||||
|
||||
"""
|
||||
tool = self._get_tool(id)
|
||||
tool._view.uninstall_dependencies(index=None, requirements=tool.requirements)
|
||||
# TODO: rework resolver install system to log and report what has been done.
|
||||
return tool.tool_requirements_status
|
||||
|
||||
@expose_api
|
||||
@web.require_admin
|
||||
def build_dependency_cache(self, trans, id, **kwds):
|
||||
|
||||
@@ -64,15 +64,6 @@ def paste_app_factory( global_conf, **kwargs ):
|
||||
# Create the universe WSGI application
|
||||
webapp = GalaxyWebApplication( app, session_cookie='galaxysession', name='galaxy' )
|
||||
|
||||
# CLIENTSIDE ROUTES
|
||||
# The following are routes that are handled completely on the clientside.
|
||||
# The following routes don't bootstrap any information, simply provide the
|
||||
# base analysis interface at which point the application takes over.
|
||||
|
||||
webapp.add_client_route( '/tours' )
|
||||
webapp.add_client_route( '/tours/{tour_id}' )
|
||||
webapp.add_client_route( '/users' )
|
||||
|
||||
# STANDARD CONTROLLER ROUTES
|
||||
webapp.add_ui_controllers( 'galaxy.webapps.galaxy.controllers', app )
|
||||
# Force /history to go to view of current
|
||||
@@ -110,6 +101,16 @@ def paste_app_factory( global_conf, **kwargs ):
|
||||
# isolation as well.
|
||||
populate_api_routes( webapp, app )
|
||||
|
||||
# CLIENTSIDE ROUTES
|
||||
# The following are routes that are handled completely on the clientside.
|
||||
# The following routes don't bootstrap any information, simply provide the
|
||||
# base analysis interface at which point the application takes over.
|
||||
|
||||
webapp.add_client_route( '/tours' )
|
||||
webapp.add_client_route( '/tours/{tour_id}' )
|
||||
webapp.add_client_route( '/user' )
|
||||
webapp.add_client_route( '/user/{form_id}' )
|
||||
|
||||
# ==== Done
|
||||
# Indicate that all configuration settings have been provided
|
||||
webapp.finalize_config()
|
||||
@@ -183,11 +184,6 @@ def populate_api_routes( webapp, app ):
|
||||
path_prefix='/api/histories/{history_id}/contents',
|
||||
parent_resources=dict( member_name='history', collection_name='histories' ),
|
||||
)
|
||||
|
||||
contents_archive_mapper = webapp.mapper.submapper( action='archive', controller='history_contents' )
|
||||
contents_archive_mapper.connect( '/api/histories/{history_id}/contents/archive' )
|
||||
contents_archive_mapper.connect( '/api/histories/{history_id}/contents/archive/{filename}{.format}' )
|
||||
|
||||
# Legacy access to HDA details via histories/{history_id}/contents/{hda_id}
|
||||
webapp.mapper.resource( 'content',
|
||||
'contents',
|
||||
@@ -269,6 +265,8 @@ def populate_api_routes( webapp, app ):
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/download', action='download', controller="tools" )
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/requirements', action='requirements', controller="tools")
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/install_dependencies', action='install_dependencies', controller="tools", conditions=dict( method=[ "POST" ] ))
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/dependencies', action='install_dependencies', controller="tools", conditions=dict( method=[ "POST" ] ))
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/dependencies', action='uninstall_dependencies', controller="tools", conditions=dict( method=[ "DELETE" ] ))
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}/build_dependency_cache', action='build_dependency_cache', controller="tools", conditions=dict( method=[ "POST" ] ))
|
||||
webapp.mapper.connect( '/api/tools/{id:.+?}', action='show', controller="tools" )
|
||||
webapp.mapper.resource( 'tool', 'tools', path_prefix='/api' )
|
||||
@@ -337,6 +335,11 @@ def populate_api_routes( webapp, app ):
|
||||
"/api/histories/{id}/exports/{jeha_id}", controller="histories",
|
||||
action="archive_download", conditions=dict( method=[ "GET" ] ) )
|
||||
|
||||
webapp.mapper.connect( '/api/histories/{history_id}/contents/archive',
|
||||
controller='history_contents', action='archive')
|
||||
webapp.mapper.connect( '/api/histories/{history_id}/contents/archive/{filename}{.format}',
|
||||
controller='history_contents', action='archive')
|
||||
|
||||
# ---- visualizations registry ---- generic template renderer
|
||||
# @deprecated: this route should be considered deprecated
|
||||
webapp.add_route( '/visualization/show/{visualization_name}', controller='visualization', action='render', visualization_name=None )
|
||||
|
||||
@@ -10,7 +10,6 @@ from galaxy import web
|
||||
from galaxy.actions.admin import AdminActions
|
||||
from galaxy.exceptions import MessageException
|
||||
from galaxy.model import tool_shed_install as install_model
|
||||
from galaxy.model.util import pgcalc
|
||||
from galaxy.util import nice_size, sanitize_text, url_get
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.web import url_for
|
||||
@@ -92,7 +91,8 @@ class UserListGrid( grids.Grid ):
|
||||
model_class=model.User,
|
||||
link=( lambda item: dict( operation="information", id=item.id, webapp="galaxy" ) ),
|
||||
attach_popup=True,
|
||||
filterable="advanced" ),
|
||||
filterable="advanced",
|
||||
target="top" ),
|
||||
UserNameColumn( "User Name",
|
||||
key="username",
|
||||
model_class=model.User,
|
||||
@@ -874,27 +874,9 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP
|
||||
user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
|
||||
if not user:
|
||||
return trans.show_error_message( "User not found for id (%s)" % sanitize_text( str( user_id ) ) )
|
||||
engine = None
|
||||
if trans.app.config.database_connection:
|
||||
engine = trans.app.config.database_connection.split(':')[0]
|
||||
if engine not in ( 'postgres', 'postgresql' ):
|
||||
done = False
|
||||
while not done:
|
||||
current = user.get_disk_usage()
|
||||
new = user.calculate_disk_usage()
|
||||
trans.sa_session.refresh( user )
|
||||
# make sure usage didn't change while calculating, set done
|
||||
if user.get_disk_usage() == current:
|
||||
done = True
|
||||
if new not in (current, None):
|
||||
user.set_disk_usage( new )
|
||||
trans.sa_session.add( user )
|
||||
trans.sa_session.flush()
|
||||
else:
|
||||
# We can use the lightning fast pgcalc!
|
||||
current = user.get_disk_usage()
|
||||
new = pgcalc( self.sa_session, user.id )
|
||||
# yes, still a small race condition between here and the flush
|
||||
current = user.get_disk_usage()
|
||||
user.calculate_and_set_disk_usage()
|
||||
new = user.get_disk_usage()
|
||||
if new in ( current, None ):
|
||||
message = 'Usage is unchanged at %s.' % nice_size( current )
|
||||
else:
|
||||
|
||||
@@ -58,7 +58,7 @@ class HistoryDatasetAssociationListGrid( grids.Grid ):
|
||||
grids.TextColumn( "Name", key="name",
|
||||
# Link name to dataset's history.
|
||||
link=( lambda item: iff( item.history.deleted, None, dict( operation="switch", id=item.id ) ) ), filterable="advanced", attach_popup=True ),
|
||||
HistoryColumn( "History", key="history", sortable=False, inbound=True,
|
||||
HistoryColumn( "History", key="history", sortable=False, target="inbound",
|
||||
link=( lambda item: iff( item.history.deleted, None, dict( operation="switch_history", id=item.id ) ) ) ),
|
||||
grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryDatasetAssociationTagAssociation, filterable="advanced", grid_name="HistoryDatasetAssocationListGrid" ),
|
||||
StatusColumn( "Status", key="deleted", attach_popup=False ),
|
||||
|
||||
@@ -111,7 +111,7 @@ class HistoryListGrid( grids.Grid ):
|
||||
grids.GridOperation( "View", allow_multiple=False ),
|
||||
grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
|
||||
grids.GridOperation( "Copy", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
|
||||
grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False, inbound=True ),
|
||||
grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False, target="inbound" ),
|
||||
grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), async_compatible=True ),
|
||||
grids.GridOperation( "Delete Permanently", condition=( lambda item: not item.purged ), confirm="History contents will be removed from disk, this cannot be undone. Continue?", async_compatible=True ),
|
||||
grids.GridOperation( "Undelete", condition=( lambda item: item.deleted and not item.purged ), async_compatible=True ),
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
API check for whether the current session's interactive environment launch is ready
|
||||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from galaxy.web import expose, json
|
||||
from galaxy.web.base.controller import BaseUIController
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InteractiveEnvironmentsController(BaseUIController):
|
||||
|
||||
@expose
|
||||
@json
|
||||
def ready(self, trans, **kwd):
|
||||
"""
|
||||
GET /interactive_environments/ready/
|
||||
|
||||
Queries the GIE proxy IPC to determine whether the current user's session's GIE launch is ready
|
||||
|
||||
:returns: ``true`` if ready else ``false``
|
||||
:rtype: boolean
|
||||
"""
|
||||
proxy_map = self.app.proxy_manager.query_proxy(trans)
|
||||
if proxy_map.container_ids:
|
||||
command = proxy_map.docker_command.format(
|
||||
docker_args='ps --format {{{{.Status}}}} --filter id={container_id}'.format(
|
||||
container_id=proxy_map.container_ids[0]))
|
||||
match_col = 0
|
||||
match_str = 'Up'
|
||||
elif proxy_map.service_ids:
|
||||
command = proxy_map.docker_command.format(
|
||||
docker_args='service ps --no-trunc {service_id}'.format(service_id=proxy_map.service_ids[0]))
|
||||
match_col = 5
|
||||
match_str = 'Running'
|
||||
else:
|
||||
raise Exception('Proxy map has neither container ids nor service ids stored')
|
||||
p = Popen(command, stdout=PIPE, stderr=PIPE, close_fds=True, shell=True)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
log.error( "%s\n%s" % (stdout, stderr) )
|
||||
return None
|
||||
else:
|
||||
return stdout.splitlines()[-1].strip().split()[match_col].startswith(match_str)
|
||||
@@ -1299,104 +1299,3 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
|
||||
action='manage_user_info',
|
||||
cntrller=cntrller,
|
||||
**kwd ) )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def manage_user_info( self, trans, cntrller, **kwd ):
|
||||
'''TEMPORARY ENDPOINT - added back to support admin-level user info
|
||||
editing prior to adminjs. This is code that was prematurely removed
|
||||
from the user controller when the user-side editing functionality was
|
||||
replaced.
|
||||
|
||||
When this is removed, templates/webapps/galaxy/user/manage_info.mako
|
||||
should go as well.
|
||||
|
||||
Manage a user's login, password, public username, type,
|
||||
addresses, etc.'''
|
||||
|
||||
def __get_user_type_form_definition( trans, user=None, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
if user and user.values:
|
||||
user_type_fd_id = trans.security.encode_id( user.values.form_definition.id )
|
||||
else:
|
||||
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
|
||||
if user_type_fd_id not in [ 'none' ]:
|
||||
user_type_form_definition = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id( user_type_fd_id ) )
|
||||
else:
|
||||
user_type_form_definition = None
|
||||
return user_type_form_definition
|
||||
|
||||
def __get_widgets( trans, user_type_form_definition, user=None, **kwd ):
|
||||
widgets = []
|
||||
if user_type_form_definition:
|
||||
if user:
|
||||
if user.values:
|
||||
widgets = user_type_form_definition.get_widgets( user=user,
|
||||
contents=user.values.content,
|
||||
**kwd )
|
||||
else:
|
||||
widgets = user_type_form_definition.get_widgets( None, contents={}, **kwd )
|
||||
else:
|
||||
widgets = user_type_form_definition.get_widgets( None, contents={}, **kwd )
|
||||
return widgets
|
||||
|
||||
def __build_user_type_fd_id_select_field( trans, selected_value ):
|
||||
from galaxy.web.form_builder import build_select_field
|
||||
# Get all the user information forms
|
||||
user_info_forms = self.get_all_forms( trans,
|
||||
filter=dict( deleted=False ),
|
||||
form_type=trans.model.FormDefinition.types.USER_INFO )
|
||||
return build_select_field( trans,
|
||||
objs=user_info_forms,
|
||||
label_attr='name',
|
||||
select_field_name='user_type_fd_id',
|
||||
initial_value='none',
|
||||
selected_value=selected_value,
|
||||
refresh_on_change=True )
|
||||
|
||||
params = util.Params( kwd )
|
||||
user_id = params.get( 'id', None )
|
||||
if user_id:
|
||||
user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( user_id ) )
|
||||
else:
|
||||
user = trans.user
|
||||
if not user:
|
||||
raise AssertionError("The user id (%s) is not valid" % str( user_id ))
|
||||
email = util.restore_text( params.get( 'email', user.email ) )
|
||||
username = util.restore_text( params.get( 'username', '' ) )
|
||||
if not username:
|
||||
username = user.username
|
||||
message = escape( util.restore_text( params.get( 'message', '' ) ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
user_type_form_definition = __get_user_type_form_definition( trans, user=user, **kwd )
|
||||
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
|
||||
if user_type_fd_id == 'none' and user_type_form_definition is not None:
|
||||
user_type_fd_id = trans.security.encode_id( user_type_form_definition.id )
|
||||
user_type_fd_id_select_field = __build_user_type_fd_id_select_field( trans, selected_value=user_type_fd_id )
|
||||
widgets = __get_widgets( trans, user_type_form_definition, user=user, **kwd )
|
||||
# user's addresses
|
||||
show_filter = util.restore_text( params.get( 'show_filter', 'Active' ) )
|
||||
if show_filter == 'All':
|
||||
addresses = [address for address in user.addresses]
|
||||
elif show_filter == 'Deleted':
|
||||
addresses = [address for address in user.addresses if address.deleted]
|
||||
else:
|
||||
addresses = [address for address in user.addresses if not address.deleted]
|
||||
user_info_forms = self.get_all_forms( trans,
|
||||
filter=dict( deleted=False ),
|
||||
form_type=trans.app.model.FormDefinition.types.USER_INFO )
|
||||
return trans.fill_template( '/webapps/galaxy/user/manage_info.mako',
|
||||
cntrller=cntrller,
|
||||
user=user,
|
||||
email=email,
|
||||
is_admin=True,
|
||||
username=username,
|
||||
user_type_fd_id_select_field=user_type_fd_id_select_field,
|
||||
user_info_forms=user_info_forms,
|
||||
user_type_form_definition=user_type_form_definition,
|
||||
user_type_fd_id=user_type_fd_id,
|
||||
widgets=widgets,
|
||||
addresses=addresses,
|
||||
show_filter=show_filter,
|
||||
message=message,
|
||||
status=status )
|
||||
|
||||
@@ -65,7 +65,7 @@ class HistorySelectionGrid( grids.Grid ):
|
||||
datasets_action = 'list_history_datasets'
|
||||
datasets_param = "f-history"
|
||||
columns = [
|
||||
NameColumn( "History Name", key="name", filterable="standard", inbound=True ),
|
||||
NameColumn( "History Name", key="name", filterable="standard", target="inbound" ),
|
||||
grids.GridColumn( "Last Updated", key="update_time", format=time_ago, visible=False ),
|
||||
DbKeyPlaceholderColumn( "Dbkey", key="dbkey", model_class=model.HistoryDatasetAssociation, visible=False )
|
||||
]
|
||||
@@ -88,7 +88,7 @@ class LibrarySelectionGrid( LibraryListGrid ):
|
||||
datasets_action = 'list_library_datasets'
|
||||
datasets_param = "f-library"
|
||||
columns = [
|
||||
NameColumn( "Library Name", key="name", filterable="standard", inbound=True )
|
||||
NameColumn( "Library Name", key="name", filterable="standard", target="inbound" )
|
||||
]
|
||||
num_rows_per_page = 10
|
||||
use_async = True
|
||||
@@ -224,12 +224,12 @@ class VisualizationListGrid( grids.Grid ):
|
||||
key="free-text-search", visible=False, filterable="standard" )
|
||||
)
|
||||
global_actions = [
|
||||
grids.GridAction( "Create new visualization", dict( action='create' ), inbound=True )
|
||||
grids.GridAction( "Create new visualization", dict( action='create' ), target="inbound" )
|
||||
]
|
||||
operations = [
|
||||
grids.GridOperation( "Open", allow_multiple=False, url_args=get_url_args ),
|
||||
grids.GridOperation( "Open in Circster", allow_multiple=False, condition=( lambda item: item.type == 'trackster' ), url_args=dict( action='circster' ) ),
|
||||
grids.GridOperation( "Edit Attributes", allow_multiple=False, url_args=dict( action='edit'), inbound=True),
|
||||
grids.GridOperation( "Edit Attributes", allow_multiple=False, url_args=dict( action='edit'), target="inbound" ),
|
||||
grids.GridOperation( "Copy", allow_multiple=False, condition=( lambda item: not item.deleted )),
|
||||
grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
|
||||
grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), confirm="Are you sure you want to delete this visualization?" ),
|
||||
|
||||
@@ -52,7 +52,12 @@ def edgelist_for_workflow_steps( steps ):
|
||||
for step in steps:
|
||||
edges.append( ( steps_to_index[step], steps_to_index[step] ) )
|
||||
for conn in step.input_connections:
|
||||
edges.append( ( steps_to_index[conn.output_step], steps_to_index[conn.input_step] ) )
|
||||
output_index = steps_to_index[conn.output_step]
|
||||
input_index = steps_to_index[conn.input_step]
|
||||
# self connection - a cycle not detectable by topsort function.
|
||||
if output_index == input_index:
|
||||
raise CycleError([], 0, 0)
|
||||
edges.append( ( output_index, input_index ) )
|
||||
return edges
|
||||
|
||||
|
||||
|
||||
@@ -155,8 +155,7 @@ class InstalledRepositoryGrid( grids.Grid ):
|
||||
grids.GridAction( label="Update tool shed status",
|
||||
url_args=dict( controller='admin_toolshed',
|
||||
action='update_tool_shed_status_for_installed_repository',
|
||||
all_installed_repositories=True ),
|
||||
inbound=False )
|
||||
all_installed_repositories=True ) )
|
||||
]
|
||||
operations = [ grids.GridOperation( label="Update tool shed status",
|
||||
condition=( lambda item: not item.deleted ),
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"galaxy.interactive_environments.js","sources":["../src/galaxy.interactive_environments.js"],"names":["append_notebook","url","clear_main_area","$","append","remove","children","display_spinner","galaxy_root","test_ie_availability","success_callback","request_count","interval","setInterval","ajax","xhrFields","withCredentials","type","timeout","success","console","log","clearInterval","error","toastr","closeButton","timeOut","tapToDismiss"],"mappings":"AAIA,QAASA,iBAAgBC,GACrBC,kBACAC,EAAE,SAASC,OAAO,uHAAwHH,EAAK,eAInJ,QAASC,mBACLC,EAAE,YAAYE,SACdF,EAAE,SAASG,WAAWD,SAG1B,QAASE,mBACDJ,EAAE,SAASC,OAAO,0BAA4BI,YAAc,wGAWpE,QAASC,sBAAqBR,EAAKS,GAC/B,GAAIC,GAAgB,CACpBJ,mBACAK,SAAWC,YAAY,WACnBV,EAAEW,MACEb,IAAKA,EACLc,WACIC,iBAAiB,GAErBC,KAAM,MACNC,QAAS,IACTC,QAAS,WACLC,QAAQC,IAAI,8BACZC,cAAcV,UACdF,KAEJa,MAAO,WACHZ,IACAS,QAAQC,IAAI,WAAaV,GACtBA,EAAgB,KACfW,cAAcV,UACdV,kBACAsB,OAAOD,MACH,sDACA,SACCE,aAAe,EAAMC,QAAW,IAAOC,cAAgB,SAKzE"}
|
||||
{"version":3,"file":"galaxy.interactive_environments.js","sources":["../src/galaxy.interactive_environments.js"],"names":["append_notebook","url","clear_main_area","$","append","remove","children","display_spinner","galaxy_root","load_when_ready","success_callback","request_count","timeout_time","timeout_time_max","timeout_time_step","timeout","ajax","xhrFields","withCredentials","type","dataType","success","data","console","log","toastr","clear","info","closeButton","tapToDismiss","window","setTimeout","error","test_ie_availability","interval","setInterval","clearInterval","timeOut"],"mappings":"AAIA,QAASA,iBAAgBC,GACrBC,kBACAC,EAAE,SAASC,OAAO,uHAAwHH,EAAK,eAInJ,QAASC,mBACLC,EAAE,YAAYE,SACdF,EAAE,SAASG,WAAWD,SAG1B,QAASE,mBACDJ,EAAE,SAASC,OAAO,0BAA4BI,YAAc,wGAMpE,QAASC,iBAAgBR,EAAKS,GAC1B,GAAIC,GAAgB,EAChBC,EAAe,IACfC,EAAmB,KACnBC,EAAoB,IACpBC,EAAU,WACVZ,EAAEa,MACEf,IAAKA,EACLgB,WACIC,iBAAiB,GAErBC,KAAM,MACNJ,QAAS,IACTK,SAAU,OACVC,QAAS,SAASC,GACH,GAARA,GACCC,QAAQC,IAAI,gDACZtB,kBACAuB,OAAOC,QACPhB,KACa,GAARY,GACe,GAAjBX,IACCJ,kBACAkB,OAAOE,KACH,gGACCC,aAAe,EAAMC,cAAgB,KAG9ClB,IACkBE,EAAfD,IACCA,GAAgBE,GAEpBS,QAAQC,IAAI,qBAAuBb,EAAgB,aAAeC,EAAe,IAAO,KACxFkB,OAAOC,WAAWhB,EAASH,KAE3BV,kBACAuB,OAAOC,QACPD,OAAOO,MACH,gHACA,SACCJ,aAAe,EAAMC,cAAgB,QAM1DC,QAAOC,WAAWhB,EAASH,GAY/B,QAASqB,sBAAqBhC,EAAKS,GAC/B,GAAIC,GAAgB,CACpBJ,mBACA2B,SAAWC,YAAY,WACnBhC,EAAEa,MACEf,IAAKA,EACLgB,WACIC,iBAAiB,GAErBC,KAAM,MACNJ,QAAS,IACTM,QAAS,WACLE,QAAQC,IAAI,8BACZY,cAAcF,UACdxB,KAEJsB,MAAO,WACHrB,IACAY,QAAQC,IAAI,wBAA0Bb,GACnCA,EAAgB,KACfyB,cAAcF,UACdhC,kBACAuB,OAAOO,MACH,sDACA,SACCJ,aAAe,EAAMS,QAAW,IAAOR,cAAgB,SAKzE"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"user-preferences.js","sources":["../../../src/mvc/user/user-preferences.js"],"names":["define","Form","Ui","View","Backbone","extend","initialize","this","defs","information","title","description","url","Galaxy","user","id","icon","password","submit_title","communication","permissions","api_key","submit_icon","toolbox_filters","openids","onclick","window","location","href","root","logout","modal","show","body","buttons","Cancel","hide","Sign out","message","Message","setElement","render","self","config","$","getJSON","data","$preferences","addClass","append","$el","_","escape","email","$table","use_remote_user","_link","enable_communication_server","has_user_tool_filters","enable_openid","_templateFooter","empty","page","$page_item","_templateRow","find","on","ajax","type","done","response","options","form","inputs","operations","submit","ButtonIcon","tooltip","submit_tooltip","_submit","back","remove","fail","update","status","JSON","stringify","create","contentType","updated_values","matchModel","input","input_id","field_list","value","responseJSON","err_msg","nice_total_disk_usage","enable_quotas","quota"],"mappings":"AACAA,QAAU,qBAAsB,kBAAoB,SAAUC,EAAMC,GAEhE,GAAIC,GAAOC,SAASD,KAAKE,QAErBC,WAAY,WACRC,KAAKC,MACDC,aACIC,MAAkB,qBAClBC,YAAkB,4EAClBC,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,sBAClDC,KAAkB,WAEtBC,UACIP,MAAkB,kBAClBC,YAAkB,+CAClBK,KAAkB,gBAClBJ,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,mBAClDG,aAAkB,iBAEtBC,eACIT,MAAkB,gCAClBC,YAAkB,wEAClBC,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,wBAClDC,KAAkB,iBAEtBI,aACIV,MAAkB,4CAClBC,YAAkB,sJAClBC,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,sBAClDC,KAAkB,WAClBE,aAAkB,oBAEtBG,SACIX,MAAkB,iBAClBC,YAAkB,mDAClBC,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,kBAClDC,KAAkB,SAClBE,aAAkB,mBAClBI,YAAkB,YAEtBC,iBACIb,MAAkB,yBAClBC,YAAkB,kEAClBC,IAAkB,aAAeC,OAAOC,KAAKC,GAAK,0BAClDC,KAAkB,YAClBE,aAAkB,gBAEtBM,SACId,MAAkB,iBAClBC,YAAkB,uCAClBK,KAAkB,YAClBS,QAAkB,WACdC,OAAOC,SAASC,KAAOf,OAAOgB,KAAO,qDAG7CC,QACIpB,MAAkB,WAClBC,YAAkB,0CAClBK,KAAkB,cAClBS,QAAkB,WACdZ,OAAOkB,MAAMC,MACTtB,MAAU,WACVuB,KAAU,+DACVC,SACIC,OAAc,WAAatB,OAAOkB,MAAMK,QACxCC,WAAc,WAAaX,OAAOC,SAASC,KAAOf,OAAOgB,KAAO,qBAMpFtB,KAAK+B,QAAU,GAAIpC,GAAGqC,QACtBhC,KAAKiC,WAAY,UACjBjC,KAAKkC,UAGTA,OAAQ,WACJ,GAAIC,GAAOnC,KACPoC,EAAS9B,OAAO8B,MACpBC,GAAEC,QAAShC,OAAOgB,KAAO,aAAehB,OAAOC,KAAKC,GAAI,SAAU+B,GAC9DJ,EAAKK,aAAeH,EAAG,UAAWI,SAAU,YACVC,OAAQP,EAAKJ,QAAQY,KACrBD,OAAQL,EAAG,SAAUK,OAAQ,qBAC7BA,OAAQL,EAAG,QAASK,OAAQ,gCAAmCE,EAAEC,OAAQN,EAAKO,OAAU,eACxFJ,OAAQP,EAAKY,OAASV,EAAG,YAAaI,SAAU,mBAC7EL,EAAOY,kBACRb,EAAKc,MAAOd,EAAKlC,KAAKC,aACtBiC,EAAKc,MAAOd,EAAKlC,KAAKS,WAEtB0B,EAAOc,6BACPf,EAAKc,MAAOd,EAAKlC,KAAKW,eAE1BuB,EAAKc,MAAOd,EAAKlC,KAAKY,aACtBsB,EAAKc,MAAOd,EAAKlC,KAAKa,SAClBsB,EAAOe,uBACPhB,EAAKc,MAAOd,EAAKlC,KAAKe,iBAEtBoB,EAAOgB,gBAAkBhB,EAAOY,iBAChCb,EAAKc,MAAOd,EAAKlC,KAAKgB,SAE1BkB,EAAKc,MAAOd,EAAKlC,KAAKsB,QACtBY,EAAKK,aAAaE,OAAQP,EAAKkB,gBAAiBd,IAChDJ,EAAKQ,IAAIW,QAAQZ,OAAQP,EAAKK,iBAItCS,MAAO,SAAUM,GACb,GAAIpB,GAAOnC,KACPwD,EAAanB,EAAGrC,KAAKyD,aAAcF,GACvCvD,MAAK+C,OAAOL,OAAQc,GACpBA,EAAWE,KAAM,KAAMC,GAAI,QAAS,WAC3BJ,EAAKlD,IACNgC,EAAEuB,MACEvD,IAAUC,OAAOgB,KAAOiC,EAAKlD,IAC7BwD,KAAU,QACXC,KAAM,SAAUC,GACf,GAAIC,GAAU3B,EAAEvC,UAAYyD,EAAMQ,GAC9BE,EAAO,GAAIvE,IACXS,MAAS6D,EAAQ7D,MACjBM,KAASuD,EAAQvD,KACjByD,OAASF,EAAQE,OACjBC,YACIC,OAAU,GAAIzE,GAAG0E,YACbC,QAAWN,EAAQO,eACnBpE,MAAW6D,EAAQrD,cAAgB,gBACnCF,KAAWuD,EAAQjD,aAAe,UAClCG,QAAW,WAAaiB,EAAKqC,QAASP,EAAMD,MAEhDS,KAAQ,GAAI9E,GAAG0E,YACX5D,KAAW,gBACX6D,QAAW,6BACXnE,MAAW,cACXe,QAAW,WAAa+C,EAAKS,SAAUvC,EAAKK,aAAaf,YAIrEU,GAAKK,aAAaX,OAClBM,EAAKQ,IAAID,OAAQuB,EAAKtB,OACvBgC,KAAM,WACLxC,EAAKJ,QAAQ6C,QAAU7C,QAAS,2BAA6BwB,EAAKlD,IAAM,IAAKwE,OAAQ,aAGzFtB,EAAKrC,aAKjBsD,QAAS,SAAUP,EAAMD,GACrB,GAAI7B,GAAOnC,IACXqC,GAAEuB,MACEvD,IAAc2D,EAAQ3D,IACtBkC,KAAcuC,KAAKC,UAAUd,EAAK1B,KAAKyC,UACvCnB,KAAc,MACdoB,YAAc,qBACfnB,KAAM,SAAUC,GACf,GAAImB,IAAiB,CACrBjB,GAAK1B,KAAK4C,WAAYpB,EAAU,SAAWqB,EAAOC,GAC9CpB,EAAKqB,WAAYD,GAAWE,MAAOH,EAAMG,OACzCL,GAAiB,IAEhBA,EACDjB,EAAKlC,QAAQ6C,QAAU7C,QAASgC,EAAShC,QAAS8C,OAAQ,aAE1DZ,EAAKS,SACLvC,EAAKK,aAAaf,OAClBU,EAAKJ,QAAQ6C,QAAU7C,QAASgC,EAAShC,QAAS8C,OAAQ,eAE/DF,KAAM,SAAUZ,GACfE,EAAKlC,QAAQ6C,QAAU7C,QAASgC,EAASyB,aAAaC,QAASZ,OAAQ,cAI/EpB,aAAc,SAAUO,GACpB,MAAQ,wCAE0CA,EAAQvD,KAAO,mEAGSuD,EAAQ7D,MAAQ,iCAC3C6D,EAAQ5D,YAAc,oBAKzEiD,gBAAiB,SAAUW,GACvB,MAAQ,oDAC+BA,EAAQ0B,sBAAwB,qDACzDpF,OAAO8B,OAAOuD,cAAgB,+BAAiC3B,EAAQ4B,MAAQ,cAAgB,IACjG,6MAKpB,QACIhG,KAAMA"}
|
||||
{"version":3,"file":"user-preferences.js","sources":["../../../src/mvc/user/user-preferences.js"],"names":["define","Form","Ui","Model","Backbone","extend","initialize","options","user_id","Galaxy","user","id","this","set","information","title","description","url","icon","password","submit_title","communication","permissions","api_key","submit_icon","toolbox_filters","openids","onclick","window","location","href","root","logout","modal","show","body","buttons","Cancel","hide","Sign out","View","model","message","Message","setElement","render","self","config","$","getJSON","data","$preferences","addClass","append","$el","_","escape","email","$table","use_remote_user","_addLink","enable_communication_server","has_user_tool_filters","enable_openid","_templateFooter","empty","action","get","$row","_templateLink","$a","find","on","attr","nice_total_disk_usage","enable_quotas","quota","Forms","page","form_id","ajax","type","done","response","form","inputs","operations","submit","ButtonIcon","tooltip","submit_tooltip","_submit","fail","status","persistent","JSON","stringify","create","contentType","updated_values","matchModel","input","input_id","field_list","value","update","console","log","responseJSON","err_msg"],"mappings":"AACAA,QAAU,qBAAsB,kBAAoB,SAAUC,EAAMC,GAGhE,GAAIC,GAAQC,SAASD,MAAME,QACvBC,WAAY,SAAUC,GAClBA,EAAUA,MACVA,EAAQC,QAAUD,EAAQC,SAAWC,OAAOC,KAAKC,GACjDC,KAAKC,KACDL,QAAsBD,EAAQC,QAC9BM,aACIC,MAAkB,qBAClBC,YAAkB,4EAClBC,IAAkB,aAAeV,EAAQC,QAAU,sBACnDU,KAAkB,WAEtBC,UACIJ,MAAkB,kBAClBC,YAAkB,+CAClBE,KAAkB,gBAClBD,IAAkB,aAAeV,EAAQC,QAAU,mBACnDY,aAAkB,iBAEtBC,eACIN,MAAkB,gCAClBC,YAAkB,wEAClBC,IAAkB,aAAeV,EAAQC,QAAU,wBACnDU,KAAkB,iBAEtBI,aACIP,MAAkB,4CAClBC,YAAkB,sJAClBC,IAAkB,aAAeV,EAAQC,QAAU,sBACnDU,KAAkB,WAClBE,aAAkB,oBAEtBG,SACIR,MAAkB,iBAClBC,YAAkB,mDAClBC,IAAkB,aAAeV,EAAQC,QAAU,kBACnDU,KAAkB,SAClBE,aAAkB,mBAClBI,YAAkB,YAEtBC,iBACIV,MAAkB,yBAClBC,YAAkB,kEAClBC,IAAkB,aAAeV,EAAQC,QAAU,0BACnDU,KAAkB,YAClBE,aAAkB,gBAEtBM,SACIX,MAAkB,iBAClBC,YAAkB,uCAClBE,KAAkB,YAClBS,QAAkB,WACdC,OAAOC,SAASC,KAAOrB,OAAOsB,KAAO,qDAG7CC,QACIjB,MAAkB,WAClBC,YAAkB,0CAClBE,KAAkB,cAClBS,QAAkB,WACdlB,OAAOwB,MAAMC,MACTnB,MAAU,WACVoB,KAAU,+DACVC,SACIC,OAAc,WAAa5B,OAAOwB,MAAMK,QACxCC,WAAc,WAAaX,OAAOC,SAASC,KAAOrB,OAAOsB,KAAO,yBAUxFS,EAAOpC,SAASoC,KAAKnC,QAErBC,WAAY,WACRM,KAAK6B,MAAQ,GAAItC,GACjBS,KAAK8B,QAAU,GAAIxC,GAAGyC,QACtB/B,KAAKgC,WAAY,UACjBhC,KAAKiC,UAGTA,OAAQ,WACJ,GAAIC,GAAOlC,KACPmC,EAAStC,OAAOsC,MACpBC,GAAEC,QAASxC,OAAOsB,KAAO,aAAetB,OAAOC,KAAKC,GAAI,SAAUuC,GAC9DJ,EAAKK,aAAeH,EAAG,UAAWI,SAAU,YACVC,OAAQP,EAAKJ,QAAQY,KACrBD,OAAQL,EAAG,SAAUK,OAAQ,qBAC7BA,OAAQL,EAAG,QAASK,OAAQ,gCAAmCE,EAAEC,OAAQN,EAAKO,OAAU,eACxFJ,OAAQP,EAAKY,OAASV,EAAG,YAAaI,SAAU,mBAC7EL,EAAOY,kBACRb,EAAKc,SAAU,eACfd,EAAKc,SAAU,aAEfb,EAAOc,6BACPf,EAAKc,SAAU,iBAEnBd,EAAKc,SAAU,eACfd,EAAKc,SAAU,WACXb,EAAOe,uBACPhB,EAAKc,SAAU,mBAEfb,EAAOgB,gBAAkBhB,EAAOY,iBAChCb,EAAKc,SAAU,WAEnBd,EAAKc,SAAU,UACfd,EAAKK,aAAaE,OAAQP,EAAKkB,gBAAiBd,IAChDJ,EAAKQ,IAAIW,QAAQZ,OAAQP,EAAKK,iBAItCS,SAAU,SAAUM,GAChB,GAAI3D,GAAUK,KAAK6B,MAAM0B,IAAKD,GAC1BE,EAASpB,EAAGpC,KAAKyD,cAAe9D,IAChC+D,EAAKF,EAAKG,KAAM,IACfhE,GAAQoB,QACT2C,EAAGE,GAAI,QAAS,WAAajE,EAAQoB,YAErC2C,EAAGG,KAAM,OAAQhE,OAAOsB,KAAO,QAAUmC,GAE7CtD,KAAK8C,OAAOL,OAAQe,IAGxBC,cAAe,SAAU9D,GACrB,MAAQ,wCAE0CA,EAAQW,KAAO,mEAGSX,EAAQQ,MAAQ,iCAC3CR,EAAQS,YAAc,oBAKzEgD,gBAAiB,SAAUzD,GACvB,MAAQ,oDAC+BA,EAAQmE,sBAAwB,qDACzDjE,OAAOsC,OAAO4B,cAAgB,+BAAiCpE,EAAQqE,MAAQ,cAAgB,IACjG,8MAMhBC,EAAQzE,SAASoC,KAAKnC,QAEtBC,WAAY,SAAUC,GAClBK,KAAK6B,MAAQ,GAAItC,GAAOI,GACxBK,KAAKkE,KAAOlE,KAAK6B,MAAM0B,IAAK5D,EAAQwE,SACpCnE,KAAKgC,WAAY,UACjBhC,KAAKiC,UAGTA,OAAQ,WACJ,GAAIC,GAAOlC,IACXoC,GAAEgC,MACE/D,IAAUR,OAAOsB,KAAOnB,KAAKkE,KAAK7D,IAClCgE,KAAU,QACXC,KAAM,SAAUC,GACf,GAAI5E,GAAUyC,EAAE3C,UAAYyC,EAAKgC,KAAMK,GACnCC,EAAO,GAAInF,IACXc,MAASR,EAAQQ,MACjBG,KAASX,EAAQW,KACjBmE,OAAS9E,EAAQ8E,OACjBC,YACIC,OAAU,GAAIrF,GAAGsF,YACbC,QAAWlF,EAAQmF,eACnB3E,MAAWR,EAAQa,cAAgB,gBACnCF,KAAWX,EAAQiB,aAAe,UAClCG,QAAW,WAAamB,EAAK6C,QAASP,EAAM7E,QAIxDuC,GAAKQ,IAAIW,QAAQZ,OAAQ+B,EAAK9B,OAC/BsC,KAAM,WACL9C,EAAKQ,IAAIW,QAAQZ,OAAQ,GAAInD,GAAGyC,SAC5BD,QAAc,2BAA6BI,EAAKgC,KAAK7D,IAAM,IAC3D4E,OAAc,SACdC,YAAc,IACfxC,QAIXqC,QAAS,SAAUP,EAAM7E,GAErByC,EAAEgC,MACE/D,IAAcR,OAAOsB,KAAOxB,EAAQU,IACpCiC,KAAc6C,KAAKC,UAAWZ,EAAKlC,KAAK+C,UACxChB,KAAc,MACdiB,YAAc,qBACfhB,KAAM,SAAUC,GACf,GAAIgB,IAAiB,CACrBf,GAAKlC,KAAKkD,WAAYjB,EAAU,SAAWkB,EAAOC,GAC9ClB,EAAKmB,WAAYD,GAAWE,MAAOH,EAAMG,OACzCL,GAAiB,IAErBf,EAAK1C,QAAQ+D,QAAU/D,QAASyC,EAASzC,QAASmD,OAAQ,cAC3DD,KAAM,SAAUT,GACfvD,OAAO8E,QAAQC,IAAKxB,GACpBC,EAAK1C,QAAQ+D,QAAU/D,QAASyC,EAASyB,aAAaC,QAAShB,OAAQ,eAKnF,QACIrD,KAAQA,EACRqC,MAAQA"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
function append_notebook(a){clear_main_area(),$("#main").append('<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="'+a+'"></iframe>')}function clear_main_area(){$("#spinner").remove(),$("#main").children().remove()}function display_spinner(){$("#main").append('<img id="spinner" src="'+galaxy_root+'static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">')}function test_ie_availability(a,b){var c=0;display_spinner(),interval=setInterval(function(){$.ajax({url:a,xhrFields:{withCredentials:!0},type:"GET",timeout:500,success:function(){console.log("Connected to IE, returning"),clearInterval(interval),b()},error:function(){c++,console.log("Request "+c),c>30&&(clearInterval(interval),clear_main_area(),toastr.error("Could not connect to IE, contact your administrator","Error",{closeButton:!0,timeOut:2e4,tapToDismiss:!1}))}})},1e3)}
|
||||
function append_notebook(a){clear_main_area(),$("#main").append('<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="'+a+'"></iframe>')}function clear_main_area(){$("#spinner").remove(),$("#main").children().remove()}function display_spinner(){$("#main").append('<img id="spinner" src="'+galaxy_root+'static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">')}function load_when_ready(a,b){var c=0,d=1e3,e=15e3,f=1e3,g=function(){$.ajax({url:a,xhrFields:{withCredentials:!0},type:"GET",timeout:500,dataType:"json",success:function(a){1==a?(console.log("Galaxy reports IE container ready, returning"),clear_main_area(),toastr.clear(),b()):0==a?(0==c&&(display_spinner(),toastr.info("Galaxy is launching a container in which to run this interactive environment. Please wait...",{closeButton:!0,tapToDismiss:!1})),c++,e>d&&(d+=f),console.log("Readiness request "+c+" sleeping "+d/1e3+"s"),window.setTimeout(g,d)):(clear_main_area(),toastr.clear(),toastr.error("Galaxy failed to launch a container in which to run this interactive environment, contact your administrator.","Error",{closeButton:!0,tapToDismiss:!1}))}})};window.setTimeout(g,d)}function test_ie_availability(a,b){var c=0;display_spinner(),interval=setInterval(function(){$.ajax({url:a,xhrFields:{withCredentials:!0},type:"GET",timeout:500,success:function(){console.log("Connected to IE, returning"),clearInterval(interval),b()},error:function(){c++,console.log("Availability request "+c),c>30&&(clearInterval(interval),clear_main_area(),toastr.error("Could not connect to IE, contact your administrator","Error",{closeButton:!0,timeOut:2e4,tapToDismiss:!1}))}})},1e3)}
|
||||
//# sourceMappingURL=../maps/galaxy.interactive_environments.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
define(["mvc/form/form-view","mvc/ui/ui-misc"],function(a,b){var c=Backbone.View.extend({initialize:function(){this.defs={information:{title:"Manage information",description:"Edit your email, addresses and custom parameters or change your username.",url:"api/users/"+Galaxy.user.id+"/information/inputs",icon:"fa-user"},password:{title:"Change password",description:"Allows you to change your login credentials.",icon:"fa-unlock-alt",url:"api/users/"+Galaxy.user.id+"/password/inputs",submit_title:"Save password"},communication:{title:"Change communication settings",description:"Enable or disable the communication feature to chat with other users.",url:"api/users/"+Galaxy.user.id+"/communication/inputs",icon:"fa-comments-o"},permissions:{title:"Set dataset permissions for new histories",description:"Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored.",url:"api/users/"+Galaxy.user.id+"/permissions/inputs",icon:"fa-users",submit_title:"Save permissions"},api_key:{title:"Manage API key",description:"Access your current API key or create a new one.",url:"api/users/"+Galaxy.user.id+"/api_key/inputs",icon:"fa-key",submit_title:"Create a new key",submit_icon:"fa-check"},toolbox_filters:{title:"Manage Toolbox filters",description:"Customize your Toolbox by displaying or omitting sets of Tools.",url:"api/users/"+Galaxy.user.id+"/toolbox_filters/inputs",icon:"fa-filter",submit_title:"Save filters"},openids:{title:"Manage OpenIDs",description:"Associate OpenIDs with your account.",icon:"fa-openid",onclick:function(){window.location.href=Galaxy.root+"user/openid_manage?cntrller=user&use_panels=True"}},logout:{title:"Sign out",description:"Click here to sign out of all sessions.",icon:"fa-sign-out",onclick:function(){Galaxy.modal.show({title:"Sign out",body:"Do you want to continue and sign out of all active sessions?",buttons:{Cancel:function(){Galaxy.modal.hide()},"Sign out":function(){window.location.href=Galaxy.root+"user/logout"}}})}}},this.message=new b.Message,this.setElement("<div/>"),this.render()},render:function(){var a=this,b=Galaxy.config;$.getJSON(Galaxy.root+"api/users/"+Galaxy.user.id,function(c){a.$preferences=$("<div/>").addClass("ui-panel").append(a.message.$el).append($("<h2/>").append("User preferences")).append($("<p/>").append("You are logged in as <strong>"+_.escape(c.email)+"</strong>.")).append(a.$table=$("<table/>").addClass("ui-panel-table")),b.use_remote_user||(a._link(a.defs.information),a._link(a.defs.password)),b.enable_communication_server&&a._link(a.defs.communication),a._link(a.defs.permissions),a._link(a.defs.api_key),b.has_user_tool_filters&&a._link(a.defs.toolbox_filters),b.enable_openid&&!b.use_remote_user&&a._link(a.defs.openids),a._link(a.defs.logout),a.$preferences.append(a._templateFooter(c)),a.$el.empty().append(a.$preferences)})},_link:function(c){var d=this,e=$(this._templateRow(c));this.$table.append(e),e.find("a").on("click",function(){c.url?$.ajax({url:Galaxy.root+c.url,type:"GET"}).done(function(e){var f=$.extend({},c,e),g=new a({title:f.title,icon:f.icon,inputs:f.inputs,operations:{submit:new b.ButtonIcon({tooltip:f.submit_tooltip,title:f.submit_title||"Save settings",icon:f.submit_icon||"fa-save",onclick:function(){d._submit(g,f)}}),back:new b.ButtonIcon({icon:"fa-caret-left",tooltip:"Return to user preferences",title:"Preferences",onclick:function(){g.remove(),d.$preferences.show()}})}});d.$preferences.hide(),d.$el.append(g.$el)}).fail(function(){d.message.update({message:"Failed to load resource "+c.url+".",status:"danger"})}):c.onclick()})},_submit:function(a,b){var c=this;$.ajax({url:b.url,data:JSON.stringify(a.data.create()),type:"PUT",contentType:"application/json"}).done(function(b){var d=!1;a.data.matchModel(b,function(b,c){a.field_list[c].value(b.value),d=!0}),d?a.message.update({message:b.message,status:"success"}):(a.remove(),c.$preferences.show(),c.message.update({message:b.message,status:"success"}))}).fail(function(b){a.message.update({message:b.responseJSON.err_msg,status:"danger"})})},_templateRow:function(a){return'<tr><td><div class="ui-panel-icon fa '+a.icon+'"></td><td><a class="ui-panel-anchor" href="javascript:void(0)">'+a.title+'</a><div class="ui-form-info">'+a.description+"</div></td></tr>"},_templateFooter:function(a){return'<p class="ui-panel-footer">You are using <strong>'+a.nice_total_disk_usage+"</strong> of disk space in this Galaxy instance. "+(Galaxy.config.enable_quotas?"Your disk quota is: <strong>"+a.quota+"</strong>. ":"")+'Is your usage more than expected? See the <a href="https://wiki.galaxyproject.org/Learn/ManagingDatasets" target="_blank">documentation</a> for tips on how to find all of the data in your account.</p>'}});return{View:c}});
|
||||
define(["mvc/form/form-view","mvc/ui/ui-misc"],function(a,b){var c=Backbone.Model.extend({initialize:function(a){a=a||{},a.user_id=a.user_id||Galaxy.user.id,this.set({user_id:a.user_id,information:{title:"Manage information",description:"Edit your email, addresses and custom parameters or change your username.",url:"api/users/"+a.user_id+"/information/inputs",icon:"fa-user"},password:{title:"Change password",description:"Allows you to change your login credentials.",icon:"fa-unlock-alt",url:"api/users/"+a.user_id+"/password/inputs",submit_title:"Save password"},communication:{title:"Change communication settings",description:"Enable or disable the communication feature to chat with other users.",url:"api/users/"+a.user_id+"/communication/inputs",icon:"fa-comments-o"},permissions:{title:"Set dataset permissions for new histories",description:"Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored.",url:"api/users/"+a.user_id+"/permissions/inputs",icon:"fa-users",submit_title:"Save permissions"},api_key:{title:"Manage API key",description:"Access your current API key or create a new one.",url:"api/users/"+a.user_id+"/api_key/inputs",icon:"fa-key",submit_title:"Create a new key",submit_icon:"fa-check"},toolbox_filters:{title:"Manage Toolbox filters",description:"Customize your Toolbox by displaying or omitting sets of Tools.",url:"api/users/"+a.user_id+"/toolbox_filters/inputs",icon:"fa-filter",submit_title:"Save filters"},openids:{title:"Manage OpenIDs",description:"Associate OpenIDs with your account.",icon:"fa-openid",onclick:function(){window.location.href=Galaxy.root+"user/openid_manage?cntrller=user&use_panels=True"}},logout:{title:"Sign out",description:"Click here to sign out of all sessions.",icon:"fa-sign-out",onclick:function(){Galaxy.modal.show({title:"Sign out",body:"Do you want to continue and sign out of all active sessions?",buttons:{Cancel:function(){Galaxy.modal.hide()},"Sign out":function(){window.location.href=Galaxy.root+"user/logout"}}})}}})}}),d=Backbone.View.extend({initialize:function(){this.model=new c,this.message=new b.Message,this.setElement("<div/>"),this.render()},render:function(){var a=this,b=Galaxy.config;$.getJSON(Galaxy.root+"api/users/"+Galaxy.user.id,function(c){a.$preferences=$("<div/>").addClass("ui-panel").append(a.message.$el).append($("<h2/>").append("User preferences")).append($("<p/>").append("You are logged in as <strong>"+_.escape(c.email)+"</strong>.")).append(a.$table=$("<table/>").addClass("ui-panel-table")),b.use_remote_user||(a._addLink("information"),a._addLink("password")),b.enable_communication_server&&a._addLink("communication"),a._addLink("permissions"),a._addLink("api_key"),b.has_user_tool_filters&&a._addLink("toolbox_filters"),b.enable_openid&&!b.use_remote_user&&a._addLink("openids"),a._addLink("logout"),a.$preferences.append(a._templateFooter(c)),a.$el.empty().append(a.$preferences)})},_addLink:function(a){var b=this.model.get(a),c=$(this._templateLink(b)),d=c.find("a");b.onclick?d.on("click",function(){b.onclick()}):d.attr("href",Galaxy.root+"user/"+a),this.$table.append(c)},_templateLink:function(a){return'<tr><td><div class="ui-panel-icon fa '+a.icon+'"></td><td><a class="ui-panel-anchor" href="javascript:void(0)">'+a.title+'</a><div class="ui-form-info">'+a.description+"</div></td></tr>"},_templateFooter:function(a){return'<p class="ui-panel-footer">You are using <strong>'+a.nice_total_disk_usage+"</strong> of disk space in this Galaxy instance. "+(Galaxy.config.enable_quotas?"Your disk quota is: <strong>"+a.quota+"</strong>. ":"")+'Is your usage more than expected? See the <a href="https://wiki.galaxyproject.org/Learn/ManagingDatasets" target="_blank">documentation</a> for tips on how to find all of the data in your account.</p>'}}),e=Backbone.View.extend({initialize:function(a){this.model=new c(a),this.page=this.model.get(a.form_id),this.setElement("<div/>"),this.render()},render:function(){var c=this;$.ajax({url:Galaxy.root+this.page.url,type:"GET"}).done(function(d){var e=$.extend({},c.page,d),f=new a({title:e.title,icon:e.icon,inputs:e.inputs,operations:{submit:new b.ButtonIcon({tooltip:e.submit_tooltip,title:e.submit_title||"Save settings",icon:e.submit_icon||"fa-save",onclick:function(){c._submit(f,e)}})}});c.$el.empty().append(f.$el)}).fail(function(){c.$el.empty().append(new b.Message({message:"Failed to load resource "+c.page.url+".",status:"danger",persistent:!0}).$el)})},_submit:function(a,b){$.ajax({url:Galaxy.root+b.url,data:JSON.stringify(a.data.create()),type:"PUT",contentType:"application/json"}).done(function(b){var c=!1;a.data.matchModel(b,function(b,d){a.field_list[d].value(b.value),c=!0}),a.message.update({message:b.message,status:"success"})}).fail(function(b){window.console.log(b),a.message.update({message:b.responseJSON.err_msg,status:"danger"})})}});return{View:d,Forms:e}});
|
||||
//# sourceMappingURL=../../../maps/mvc/user/user-preferences.js.map
|
||||
@@ -158,6 +158,7 @@
|
||||
'sortable' : column.sortable,
|
||||
'label' : column.label,
|
||||
'filterable' : column.filterable,
|
||||
'target' : column.target,
|
||||
'is_text' : isinstance(column, TextColumn),
|
||||
'href' : href,
|
||||
'extra' : extra
|
||||
@@ -172,7 +173,6 @@
|
||||
'target' : operation.target,
|
||||
'label' : operation.label,
|
||||
'confirm' : operation.confirm,
|
||||
'inbound' : operation.inbound,
|
||||
'global_operation' : False
|
||||
})
|
||||
if operation.allow_multiple:
|
||||
@@ -187,7 +187,7 @@
|
||||
self.grid_config['global_actions'].append({
|
||||
'url_args' : url(**action.url_args),
|
||||
'label' : action.label,
|
||||
'inbound' : action.inbound
|
||||
'target' : action.target
|
||||
})
|
||||
endfor
|
||||
|
||||
@@ -224,8 +224,8 @@
|
||||
link = None
|
||||
endif
|
||||
|
||||
## inbound
|
||||
inbound = column.inbound
|
||||
## target
|
||||
target = column.target
|
||||
|
||||
## get value
|
||||
value = column.get_value( trans, grid, item )
|
||||
@@ -240,7 +240,7 @@
|
||||
item_dict['column_config'][column.label] = {
|
||||
'link' : link,
|
||||
'value' : value,
|
||||
'inbound' : inbound
|
||||
'target' : target
|
||||
}
|
||||
endif
|
||||
endfor
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='review_tool_migration_stages' )}" target="galaxy_main">Review tool migration stages</a></div>
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='tool_errors' )}" target="galaxy_main">View Tool Error Logs</a></div>
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='sanitize_whitelist' )}" target="galaxy_main">Manage Display Whitelist</a></div>
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='manage_tool_dependencies' )}" target="galaxy_main">Manage Tool Dependencies</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolSectionPad"></div>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<%namespace file="/webapps/tool_shed/repository/common.mako" import="render_dependency_status"/>
|
||||
<%namespace file="/message.mako" import="render_msg" />
|
||||
|
||||
<%def name="render_tool_dependencies( requirements_status, ctr=False, ncols_extra=4, show_environment_path=False )">
|
||||
%for i, dependency in enumerate(requirements_status):
|
||||
%if i != 0:
|
||||
</tr>
|
||||
%if ctr % 2 == 1:
|
||||
<tr class="odd_row">
|
||||
%else:
|
||||
<tr class="tr">
|
||||
%endif
|
||||
%for i in range(ncols_extra-1):
|
||||
<td></td>
|
||||
%endfor
|
||||
%endif
|
||||
%if show_environment_path:
|
||||
<td>${dependency.get('environment_path', '') | h}</td>
|
||||
%endif
|
||||
${render_dependency_status(dependency)}
|
||||
%endfor
|
||||
</%def>
|
||||
|
||||
<%def name="render_tool_centric_table( tools, requirements_status)">
|
||||
<tr>
|
||||
<th bgcolor="#D8D8D8">Select</th>
|
||||
<th bgcolor="#D8D8D8">Name</th>
|
||||
<th bgcolor="#D8D8D8">ID</th>
|
||||
<th bgcolor="#D8D8D8">Requirement</th>
|
||||
<th bgcolor="#D8D8D8">Version</th>
|
||||
<th bgcolor="#D8D8D8">Resolver</th>
|
||||
<th bgcolor="#D8D8D8">Exact</th>
|
||||
<th bgcolor="#D8D8D8"></th>
|
||||
</tr>
|
||||
<% ctr = 0 %>
|
||||
%for tool in tools.values():
|
||||
%if tool.tool_requirements:
|
||||
%if ctr % 2 == 1:
|
||||
<tr class="odd_row">
|
||||
%else:
|
||||
<tr class="tr">
|
||||
%endif
|
||||
<td>
|
||||
<input type="checkbox" name="selected_tool_ids" value="${tool.id}"/>
|
||||
</td>
|
||||
<td>${ tool.name | h }</td>
|
||||
<td>${ tool.id | h }</td>
|
||||
${render_tool_dependencies( requirements_status[tool.tool_requirements], ctr=ctr) }
|
||||
</tr>
|
||||
<% ctr += 1 %>
|
||||
%endif
|
||||
%endfor
|
||||
</%def>
|
||||
|
||||
<%def name="render_dependencies_details( tools, requirements_status, tool_ids_by_requirements)">
|
||||
<tr>
|
||||
<th bgcolor="#D8D8D8">Select</th>
|
||||
<th bgcolor="#D8D8D8">Used by</th>
|
||||
<th bgcolor="#D8D8D8">Environment Path</th>
|
||||
<th bgcolor="#D8D8D8">Requirement</th>
|
||||
<th bgcolor="#D8D8D8">Version</th>
|
||||
<th bgcolor="#D8D8D8">Resolver</th>
|
||||
<th bgcolor="#D8D8D8">Exact</th>
|
||||
<th bgcolor="#D8D8D8"></th>
|
||||
</tr>
|
||||
<% ctr = 0 %>
|
||||
%for requirements, r_status in requirements_status.items():
|
||||
%if requirements:
|
||||
<% tool_ids = tool_ids_by_requirements[requirements] %>
|
||||
%if ctr % 2 == 1:
|
||||
<tr class="odd_row">
|
||||
%else:
|
||||
<tr class="tr">
|
||||
%endif
|
||||
<td>
|
||||
<input type="checkbox" name="selected_tool_ids" value="${tool_ids[0]}"/>
|
||||
</td>
|
||||
<td>${ ", ".join([tools[tid].name for tid in tool_ids]) | h }</td>
|
||||
${render_tool_dependencies( r_status, ctr=ctr, show_environment_path=True, ncols_extra=3) }
|
||||
</tr>
|
||||
%endif
|
||||
<% ctr += 1 %>
|
||||
%endfor
|
||||
</%def>
|
||||
|
||||
%if message:
|
||||
${render_msg( message, status )}
|
||||
%endif
|
||||
|
||||
<h2>Manage Tool Dependencies</h2>
|
||||
<p>This page gives an overview of all tool dependencies required by all tools currently loaded, including tools not installed through the Tool Shed.</p>
|
||||
|
||||
<form name="manage_tool_dependencies" action="${h.url_for( controller='admin', action='manage_tool_dependencies' )}">
|
||||
%if viewkey == "Switch to tool-centric view":
|
||||
<input type="submit" name="viewkey" value="Switch to details view"/>
|
||||
<div class="toolForm">
|
||||
<div class="toolFormTitle">Tool-centric dependencies</div>
|
||||
<div class="toolFormBody">
|
||||
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
${render_tool_centric_table(tools, requirements_status)}
|
||||
%else:
|
||||
<input type="submit" name="viewkey" value="Switch to tool-centric view"/>
|
||||
<div class="toolForm">
|
||||
<div class="toolFormTitle">Dependency details</div>
|
||||
<div class="toolFormBody">
|
||||
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
${render_dependencies_details(tools, requirements_status, tool_ids_by_requirements)}
|
||||
%endif
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" name="install_dependencies" value="Install checked dependencies using Conda"/>
|
||||
<input type="submit" name="uninstall_dependencies" value="Uninstall checked dependencies using Conda"/>
|
||||
</form>
|
||||
@@ -1,40 +0,0 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<%namespace file="/user/info.mako" import="render_user_info" />
|
||||
<%namespace file="/message.mako" import="render_msg" />
|
||||
|
||||
%if message:
|
||||
${render_msg( message, status )}
|
||||
%endif
|
||||
|
||||
${render_user_info()}
|
||||
|
||||
%if user.values or user_info_forms:
|
||||
<p></p>
|
||||
<div class="toolForm">
|
||||
<form name="user_info" id="user_info" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
|
||||
<div class="toolFormTitle">User information</div>
|
||||
%if user_type_fd_id_select_field and len( user_type_fd_id_select_field.options ) >= 1:
|
||||
<div class="form-row">
|
||||
<label>User type:</label>
|
||||
${user_type_fd_id_select_field.get_html()}
|
||||
</div>
|
||||
%else:
|
||||
<input type="hidden" name="user_type_fd_id" value="${trans.security.encode_id( user_type_fd_id )}"/>
|
||||
%endif
|
||||
%for field in widgets:
|
||||
<div class="form-row">
|
||||
<label>${field['label']}:</label>
|
||||
${field['widget'].get_html()}
|
||||
<div class="toolParamHelp" style="clear: both;">
|
||||
${field['helptext']}
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
</div>
|
||||
%endfor
|
||||
<div class="form-row">
|
||||
<input type="submit" name="edit_user_info_button" value="Save"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p></p>
|
||||
%endif
|
||||
@@ -1026,6 +1026,38 @@
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<%def name="render_dependency_status( dependency, prepare_for_install=False)">
|
||||
<td>${dependency['name'] | h}</td>
|
||||
<td>${dependency['version'] | h}</td>
|
||||
%if not prepare_for_install:
|
||||
%if dependency['dependency_type']:
|
||||
<td>${dependency['dependency_type'].title() | h}</td>
|
||||
%else:
|
||||
<td>${dependency['dependency_type'] | h}</td>
|
||||
%endif
|
||||
<td>${dependency['exact'] | h}</td>
|
||||
%endif
|
||||
%if dependency['dependency_type'] == None:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/images/icon_error_sml.gif" title='Dependency not resolved'/>
|
||||
%if prepare_for_install:
|
||||
Not Installed
|
||||
%endif
|
||||
</td>
|
||||
%elif not dependency['exact']:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/images/icon_warning_sml.gif" title='Dependency resolved, but version ${dependency['version']} not found'/>
|
||||
</td>
|
||||
%else:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/june_2007_style/blue/ok_small.png"/>
|
||||
%if prepare_for_install:
|
||||
Installed through ${dependency['dependency_type'].title() | h}
|
||||
%endif
|
||||
</td>
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_tool_dependency_resolver( requirements_status, prepare_for_install=False )">
|
||||
<tr class="datasetRow">
|
||||
<td style="padding-left: 20 px;">
|
||||
@@ -1043,37 +1075,8 @@
|
||||
</head>
|
||||
<body>
|
||||
%for dependency in requirements_status:
|
||||
${render_dependency_status(dependency, prepare_for_install)}
|
||||
<tr>
|
||||
<td>${dependency['name'] | h}</td>
|
||||
<td>${dependency['version'] | h}</td>
|
||||
%if not prepare_for_install:
|
||||
%if dependency['dependency_type']:
|
||||
<td>${dependency['dependency_type'].title() | h}</td>
|
||||
%else:
|
||||
<td>${dependency['dependency_type'] | h}</td>
|
||||
%endif
|
||||
<td>${dependency['exact'] | h}</td>
|
||||
%endif
|
||||
%if dependency['dependency_type'] == None:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/images/icon_error_sml.gif" title='Dependency not resolved'/>
|
||||
%if prepare_for_install:
|
||||
Not Installed
|
||||
%endif
|
||||
</td>
|
||||
%elif not dependency['exact']:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/images/icon_warning_sml.gif" title='Dependency resolved, but version ${dependency['version']} not found'/>
|
||||
</td>
|
||||
%else:
|
||||
<td>
|
||||
<img src="${h.url_for('/static')}/june_2007_style/blue/ok_small.png"/>
|
||||
%if prepare_for_install:
|
||||
Installed through ${dependency['dependency_type'].title() | h}
|
||||
%endif
|
||||
</td>
|
||||
%endif
|
||||
</tr>
|
||||
%endfor
|
||||
</body>
|
||||
</table>
|
||||
|
||||
@@ -118,6 +118,20 @@ class CondaResolutionIntegrationTestCase(integration_util.IntegrationTestCase):
|
||||
create_response = self._post(endpoint, data=data, admin=True)
|
||||
self._assert_status_code_is( create_response, 200 )
|
||||
|
||||
def test_uninstall_through_tools_api(self):
|
||||
tool_id = 'mulled_example_multi_1'
|
||||
endpoint = "tools/%s/dependencies" % tool_id
|
||||
data = {'id': tool_id}
|
||||
create_response = self._post(endpoint, data=data, admin=True)
|
||||
self._assert_status_code_is( create_response, 200 )
|
||||
response = create_response.json()
|
||||
assert any([True for d in response if d['dependency_type'] == 'conda'])
|
||||
endpoint = "tools/%s/dependencies" % tool_id
|
||||
create_response = self._delete(endpoint, data=data, admin=True)
|
||||
self._assert_status_code_is(create_response, 200)
|
||||
response = create_response.json()
|
||||
assert not [True for d in response if d['dependency_type'] == 'conda']
|
||||
|
||||
def test_conda_clean( self ):
|
||||
endpoint = 'dependency_resolvers/clean'
|
||||
create_response = self._post(endpoint, data={}, admin=True)
|
||||
|
||||
Reference in New Issue
Block a user