mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Merge branch 'dev' of https://github.com/galaxyproject/galaxy into workflow_form_002
Conflicts: client/galaxy/scripts/galaxy.workflows.js static/maps/galaxy.workflows.js.map static/scripts/galaxy.workflows.js
This commit is contained in:
+73
-3
@@ -27,9 +27,8 @@ the [development mailing list](http://dev.list.galaxyproject.org/).
|
||||
## How to Contribute
|
||||
|
||||
* All changes to the [core galaxy project](https://github.com/galaxyproject/galaxy)
|
||||
should be made through pull requests to this repository except when
|
||||
serious security vulnerabilities must simulatenously be made to multiple
|
||||
previously released versions of Galaxy.
|
||||
should be made through pull requests to this repository (with just two
|
||||
exceptions outlined below).
|
||||
|
||||
* If you are new to Git - please check out the very useful tutorials on the [GitHub training hub](https://training.github.com/kit/)
|
||||
|
||||
@@ -102,3 +101,74 @@ developers.
|
||||
|
||||
More information about tool development can be found [on the
|
||||
wiki](https://wiki.galaxyproject.org/Develop).
|
||||
|
||||
## Handling Pull Requests
|
||||
|
||||
Everyone is encouraged to express opinions and issue non-binding votes on pull
|
||||
requests, but only members of the *committers* group may issue binding votes
|
||||
on pull requests. Information on the *committers* group can be found in the
|
||||
[organization document](https://github.com/galaxyproject/galaxy/blob/dev/doc/source/project/organization.rst)
|
||||
describing governance of the core Galaxy code base.
|
||||
|
||||
Votes on pull requests should take the form of
|
||||
[+1, 0, -1, and fractions](http://www.apache.org/foundation/voting.html)
|
||||
as outlined by the Apache Foundation.
|
||||
|
||||
Pull requests modifying pre-existing releases should be restricted to bug fixes
|
||||
and require at least 2 *+1* binding votes from someone other than the author of
|
||||
the pull request with no *-1* binding votes.
|
||||
|
||||
Pull requests changing or clarifying the procedures governing this repository:
|
||||
|
||||
- Must be made to the ``dev`` branch of this repository.
|
||||
- Must remain open for at least 192 hours (unless every qualified committer has
|
||||
voted).
|
||||
- Require binding *+1* votes from at least 25% of qualified *committers* with no
|
||||
*-1* binding votes.
|
||||
- Should be titled with the prefix *[PROCEDURES]* and tagged with
|
||||
the *procedures* tag in Github.
|
||||
- Should not be modified once open. If changes are needed, the pull request
|
||||
should be closed, re-opened with modifications, and votes reset.
|
||||
- Should be restricted to just modifying the procedures and generally should not
|
||||
contain code modifications.
|
||||
- If the pull request adds or removes committers, there must be a separate
|
||||
pull request for each person added or removed.
|
||||
|
||||
Any other pull request requires at least 1 *+1* binding vote from someone other
|
||||
than the author of the pull request.
|
||||
|
||||
### Timelines
|
||||
|
||||
Except in the case of pull requests modifying governance procedures, there are
|
||||
generally no objective guidelines defining how long pull requests must remain
|
||||
open for comment. Subjectively speaking though - larger and more potentially
|
||||
controversial pull requests containing enhancements should remain open for a at
|
||||
least a few days to give everyone the opportunity to weigh in.
|
||||
|
||||
### Vetos
|
||||
|
||||
A note on vetos (*-1* votes) taken verbatim from the
|
||||
[Apache Foundation](http://www.apache.org/foundation/voting.html):
|
||||
|
||||
>"A code-modification proposal may be stopped dead in its tracks by a -1 vote
|
||||
by a qualified voter. This constitutes a veto, and it cannot be overruled nor
|
||||
overridden by anyone. Vetos stand until and unless withdrawn by their casters.
|
||||
>
|
||||
>To prevent vetos from being used capriciously, they must be accompanied by a
|
||||
technical justification showing why the change is bad (opens a security
|
||||
exposure, negatively affects performance, etc. ). A veto without a
|
||||
justification is invalid and has no weight."
|
||||
|
||||
For votes regarding non-coding issues such as procedure changes, the requirement
|
||||
that a veto is accompanied by a *technical* justification is relaxed somewhat,
|
||||
though a well reasoned justification must still be included.
|
||||
|
||||
### Direct Commit Access
|
||||
|
||||
The Galaxy *committers* group may only commit directly to Galaxy (i.e. outside
|
||||
of a pull request and not following the procedures described here) the
|
||||
following two categories of patches:
|
||||
|
||||
* Patches for serious security vulnerabilities.
|
||||
* Cherry-picking and/or merging of existing approved commits to other
|
||||
branches.
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
module.exports = function(grunt) {
|
||||
"use strict";
|
||||
|
||||
var GALAXY_PATHS = {
|
||||
dist : '../static/scripts',
|
||||
maps : '../static/maps',
|
||||
// this symlink allows us to serve uncompressed scripts in DEV_PATH for use with sourcemaps
|
||||
srcSymlink : '../static/src',
|
||||
},
|
||||
TOOLSHED_PATHS = {
|
||||
dist : '../static/toolshed/scripts',
|
||||
maps : '../static/toolshed/maps',
|
||||
srcSymlink : '../static/toolshed/src',
|
||||
};
|
||||
|
||||
grunt.config.set( 'app', 'galaxy' );
|
||||
grunt.config.set( 'paths', GALAXY_PATHS );
|
||||
if( grunt.option( 'app' ) === 'toolshed' ){
|
||||
grunt.config.set( 'app', grunt.option( 'app' ) );
|
||||
grunt.config.set( 'paths', TOOLSHED_PATHS );
|
||||
}
|
||||
|
||||
// see the sub directory grunt-tasks/ for individual task definitions
|
||||
grunt.loadTasks( 'grunt-tasks' );
|
||||
grunt.registerTask( 'default', [ 'uglify' ] );
|
||||
|
||||
@@ -69,3 +69,13 @@ version, you'll need to use the full, local path when calling it:
|
||||
./node_modules/.bin/grunt
|
||||
# or
|
||||
./node_modules/.bin/grunt watch
|
||||
|
||||
|
||||
The Toolshed Client Build
|
||||
=========================
|
||||
|
||||
The commands mentioned above in 'Rebuilding' and 'Grunt watch' also can be applied to toolshed scripts by using the
|
||||
`--app=toolshed` option:
|
||||
|
||||
grunt watch --app=toolshed
|
||||
grunt --app=toolshed
|
||||
@@ -0,0 +1,134 @@
|
||||
define([
|
||||
"libs/toastr",
|
||||
"mvc/groups/group-model"
|
||||
],
|
||||
function(
|
||||
mod_toastr,
|
||||
mod_group_model
|
||||
) {
|
||||
|
||||
// toolshed group detail view
|
||||
var GroupDetailView = Backbone.View.extend({
|
||||
el: "#groups_element",
|
||||
options: {},
|
||||
app: null,
|
||||
|
||||
initialize : function( options ){
|
||||
this.options = _.extend( this.options, options );
|
||||
this.app = window.globalTS.groups;
|
||||
|
||||
if ( this.app.collection !== null ){
|
||||
this.model = this.app.collection.get( this.options.group_id );
|
||||
this.render();
|
||||
} else {
|
||||
this.fetchGroup();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
fetchGroup: function( options ){
|
||||
var that = this;
|
||||
this.options = _.extend( this.options, options );
|
||||
this.model = new mod_group_model.Group( { id: this.options.group_id } );
|
||||
this.model.fetch({
|
||||
success: function( model ){
|
||||
console.log( 'received data: ' );
|
||||
console.log( model );
|
||||
that.render();
|
||||
},
|
||||
error: function( model, response ){
|
||||
if ( typeof response.responseJSON !== "undefined" ){
|
||||
mod_toastr.error( response.responseJSON.err_msg );
|
||||
} else {
|
||||
mod_toastr.error( 'An error ocurred.' );
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function(){
|
||||
var template = this.templateRow();
|
||||
this.$el.html( template( { group: this.model } ) );
|
||||
$("#center [data-toggle]").tooltip();
|
||||
},
|
||||
|
||||
templateRow: function() {
|
||||
return _.template([
|
||||
'<div>',
|
||||
'<h3><%= group.get("name") %></h3>',
|
||||
'<p class="" style="color:gray;">',
|
||||
'A group of <%= group.get("members").length %> members with <%= group.get("repositories").length %> repositories and a total of <%= group.get("total_downloads") %> combined tool downloads.</p>',
|
||||
|
||||
'<h3>Members</h3>',
|
||||
'<table class="grid table table-condensed">',
|
||||
'<thead>',
|
||||
'<th>Name</th>',
|
||||
'<th>Repositories</th>',
|
||||
'<th>Registered</th>',
|
||||
'</thead>',
|
||||
'<tbody>',
|
||||
'<% _.each(group.get("members"), function(member) { %>',
|
||||
'<tr>',
|
||||
'<td>',
|
||||
'<%= member.username %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<a data-toggle="tooltip" data-placement="top" title="Repositories of <%= member.username %>" href="/repository/browse_repositories_by_user?user_id=<%= member.id %>&use_panels=true" id="<%= member.id %>"><%= member.user_repos_count %></a>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= member.time_created %>',
|
||||
'</td>',
|
||||
'</tr>',
|
||||
'<% }); %>',
|
||||
'</tbody>',
|
||||
'</table>',
|
||||
|
||||
'<h3>Repositories</h3>',
|
||||
'<table class="grid table table-condensed">',
|
||||
'<thead>',
|
||||
'<th>Name</th>',
|
||||
'<th>Description</th>',
|
||||
'<th>Downloads</th>',
|
||||
'<th>Owner</th>',
|
||||
'<th>Created</th>',
|
||||
'<th>Rating</th>',
|
||||
'<th>Verified</th>',
|
||||
'</thead>',
|
||||
'<tbody>',
|
||||
'<% _.each(group.get("repositories"), function(repo) { %>',
|
||||
'<tr>',
|
||||
'<td>',
|
||||
'<a data-toggle="tooltip" data-placement="top" title="Details of <%= repo.name %>" href="/view/<%= repo.owner %>/<%= repo.name %>" id="<%= repo.id %>"><%= repo.name %></a>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.description %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.times_downloaded %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.owner %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.time_created %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.ratings_mean %>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<%= repo.approved %>',
|
||||
'</td>',
|
||||
'</tr>',
|
||||
'<% }); %>',
|
||||
'</tbody>',
|
||||
'</table>',
|
||||
'</div>'
|
||||
].join(''));
|
||||
}
|
||||
});
|
||||
return {
|
||||
GroupDetailView: GroupDetailView
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
define([
|
||||
"libs/toastr",
|
||||
"mvc/groups/group-model",
|
||||
"mvc/groups/group-listrow-view"
|
||||
|
||||
], function(
|
||||
mod_toastr,
|
||||
mod_group_model,
|
||||
mod_group_listrow_view
|
||||
){
|
||||
|
||||
var GroupListView = Backbone.View.extend({
|
||||
el: '#groups_element',
|
||||
defaults: {},
|
||||
|
||||
/**
|
||||
* Initialize and fetch the groups from server.
|
||||
* Async render afterwards.
|
||||
* @param {object} options an object with options
|
||||
*/
|
||||
initialize : function( options ){
|
||||
this.options = _.defaults( this.options || {}, this.defaults, options );
|
||||
var that = this;
|
||||
window.globalTS.groups.collection = new mod_group_model.Groups();
|
||||
window.globalTS.groups.collection.fetch({
|
||||
success: function( model ){
|
||||
console.log( 'received data: ' );
|
||||
console.log( model );
|
||||
that.render();
|
||||
},
|
||||
error: function( model, response ){
|
||||
if ( typeof response.responseJSON !== "undefined" ){
|
||||
mod_toastr.error( response.responseJSON.err_msg );
|
||||
} else {
|
||||
mod_toastr.error( 'An error ocurred.' );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
fetch: function(){
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the groups table from the object's own collection.
|
||||
*/
|
||||
render: function ( options ) {
|
||||
this.options = _.extend( this.options, options );
|
||||
$( ".tooltip" ).hide();
|
||||
var template = this.templateGroupsList();
|
||||
this.$el.html( template( { length: 10, order: 'asc' } ) );
|
||||
this.renderRows( window.globalTS.groups.collection.models );
|
||||
$( "#center [data-toggle]" ).tooltip();
|
||||
$( "#center" ).css( 'overflow','auto' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Render all given models as rows in the groups list
|
||||
* @param {array} groups_to_render array of group models to render
|
||||
*/
|
||||
renderRows: function( groups_to_render ){
|
||||
for ( var i = 0; i < groups_to_render.length; i++ ) {
|
||||
var group = groups_to_render[i];
|
||||
this.renderOne( { group: group } )
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a view for the given model and add it to the groups view.
|
||||
* @param {Group} model of the view that will be rendered
|
||||
*/
|
||||
renderOne: function( options ){
|
||||
var rowView = new mod_group_listrow_view.GroupListRowView( options );
|
||||
this.$el.find( '#group_list_body' ).append( rowView.el );
|
||||
},
|
||||
|
||||
/**
|
||||
* Table heading was clicked, update sorting preferences and re-render.
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
// sort_clicked : function(){
|
||||
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
|
||||
// Galaxy.libraries.preferences.set({'sort_order': 'desc'});
|
||||
// } else {
|
||||
// Galaxy.libraries.preferences.set({'sort_order': 'asc'});
|
||||
// }
|
||||
// this.render();
|
||||
// },
|
||||
|
||||
/**
|
||||
* Sort the underlying collection according to the parameters received.
|
||||
* Currently supports only sorting by name.
|
||||
*/
|
||||
// sortLibraries: function(){
|
||||
// if (Galaxy.libraries.preferences.get('sort_by') === 'name'){
|
||||
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
|
||||
// this.collection.sortByNameAsc();
|
||||
// } else if (Galaxy.libraries.preferences.get('sort_order') === 'desc'){
|
||||
// this.collection.sortByNameDesc();
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
|
||||
// redirectToHome: function(){
|
||||
// window.location = '../';
|
||||
// },
|
||||
// redirectToLogin: function(){
|
||||
// window.location = '/user/login';
|
||||
// },
|
||||
|
||||
// MMMMMMMMMMMMMMMMMM
|
||||
// === TEMPLATES ====
|
||||
// MMMMMMMMMMMMMMMMMM
|
||||
|
||||
templateGroupsList: function(){
|
||||
tmpl_array = [];
|
||||
|
||||
tmpl_array.push('<div id="groups">');
|
||||
tmpl_array.push('</div>');
|
||||
tmpl_array.push('<div class="groups_container table-responsive">');
|
||||
tmpl_array.push('<% if(length === 0) { %>');
|
||||
tmpl_array.push('<div>There are no groups yet.</div>');
|
||||
tmpl_array.push('<% } else{ %>');
|
||||
tmpl_array.push('<table class="grid table table-condensed">');
|
||||
tmpl_array.push(' <thead>');
|
||||
tmpl_array.push(' <th style="width:30%;"><a class="sort-groups-link" title="Click to reverse order" href="#">name</a> <span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');
|
||||
tmpl_array.push(' <th style="width:22%;">description</th>');
|
||||
tmpl_array.push(' <th style="width:22%;">members</th> ');
|
||||
tmpl_array.push(' <th style="width:26%;">repositories</th>');
|
||||
tmpl_array.push(' </thead>');
|
||||
tmpl_array.push(' <tbody id="group_list_body">');
|
||||
// group item views will attach here
|
||||
tmpl_array.push(' </tbody>');
|
||||
tmpl_array.push('</table>');
|
||||
tmpl_array.push('<% }%>');
|
||||
tmpl_array.push('</div>');
|
||||
|
||||
return _.template(tmpl_array.join(''));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
GroupListView: GroupListView
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
define([],
|
||||
function() {
|
||||
|
||||
// toolshed group row view
|
||||
var GroupListRowView = Backbone.View.extend({
|
||||
events: {},
|
||||
|
||||
initialize : function( options ){
|
||||
this.render( options.group );
|
||||
},
|
||||
|
||||
render: function( group ){
|
||||
var tmpl = this.templateRow();
|
||||
this.setElement(tmpl( { group:group } ));
|
||||
this.$el.show();
|
||||
return this;
|
||||
},
|
||||
|
||||
templateRow: function() {
|
||||
return _.template([
|
||||
'<tr class="" data-id="<%- group.get("id") %>">',
|
||||
'<td><a href="groups#/<%= group.get("id") %>"><%= group.get("name") %></a></td>',
|
||||
'<td>description</td>',
|
||||
'<td><%= group.get("total_members") %></td>',
|
||||
'<td><%= group.get("total_repos") %></td>',
|
||||
'</tr>'
|
||||
].join(''));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
GroupListRowView: GroupListRowView
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
define([], function() {
|
||||
|
||||
// ============================================================================
|
||||
// LIBRARY RELATED MODELS
|
||||
|
||||
var Group = Backbone.Model.extend({
|
||||
urlRoot: '/api/groups',
|
||||
});
|
||||
|
||||
var Groups = Backbone.Collection.extend({
|
||||
url: '/api/groups',
|
||||
|
||||
model: Group,
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
Group: Group,
|
||||
Groups: Groups
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
|
||||
// === MAIN TOOLSHED GROUP MODULE ====
|
||||
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
|
||||
|
||||
define([
|
||||
"mvc/groups/group-list-view",
|
||||
"mvc/groups/group-detail-view",
|
||||
"mvc/groups/group-model"
|
||||
],
|
||||
function(
|
||||
mod_group_list,
|
||||
mod_group_detail,
|
||||
mod_group_model
|
||||
) {
|
||||
|
||||
// ============================================================================
|
||||
// ROUTER
|
||||
var ToolshedRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
"" : "groups",
|
||||
":group_id" : "group_page"
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var ToolshedGroups = Backbone.View.extend({
|
||||
groupListView: null,
|
||||
groupDetailView: null,
|
||||
collection: null,
|
||||
|
||||
initialize : function(){
|
||||
window.globalTS.groups = this;
|
||||
|
||||
this.ts_router = new ToolshedRouter();
|
||||
this.ts_router.on( 'route:groups', function() {
|
||||
window.globalTS.groups.groupListView = new mod_group_list.GroupListView();
|
||||
});
|
||||
|
||||
this.ts_router.on( 'route:group_page', function( group_id ) {
|
||||
window.globalTS.groups.groupDetailView = new mod_group_detail.GroupDetailView( { group_id: group_id } );
|
||||
});
|
||||
|
||||
Backbone.history.start({pushState: false});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ToolshedGroups: ToolshedGroups
|
||||
};
|
||||
|
||||
});
|
||||
@@ -134,6 +134,14 @@ body {
|
||||
#left-border {
|
||||
left: 250px;
|
||||
}
|
||||
.reports-panel-container {
|
||||
position: absolute;
|
||||
top: @panel_header_height;
|
||||
bottom: 10px;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
#center {
|
||||
left:250 + @border-default-color_padding;
|
||||
right: 250 + @border-default-color_padding;
|
||||
|
||||
@@ -6,25 +6,22 @@
|
||||
module.exports = function( grunt ){
|
||||
"use strict";
|
||||
|
||||
var DIST_PATH = '../static/scripts',
|
||||
MAPS_PATH = '../static/maps',
|
||||
// this symlink allows us to serve uncompressed scripts in DEV_PATH for use with sourcemaps
|
||||
SRC_SYMLINK = '../static/src';
|
||||
var app = grunt.config.get( 'app' ),
|
||||
paths = grunt.config.get( 'paths' );
|
||||
|
||||
// compress javascript
|
||||
grunt.config( 'uglify', {
|
||||
target : {
|
||||
files: [{
|
||||
expand : true,
|
||||
cwd : SRC_SYMLINK,
|
||||
cwd : paths.srcSymlink,
|
||||
src : '**/*.js',
|
||||
dest : DIST_PATH
|
||||
dest : paths.dist
|
||||
}],
|
||||
options : {
|
||||
sourceMap : true,
|
||||
sourceMapName : function( path ){
|
||||
// rewrite name to have all source maps in 'static/maps'
|
||||
return path.replace( DIST_PATH, MAPS_PATH ) + '.map';
|
||||
return path.replace( paths.dist, paths.maps ) + '.map';
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -53,19 +50,6 @@ module.exports = function( grunt ){
|
||||
}
|
||||
});
|
||||
|
||||
// use 'grunt watch' (from a new tab in your terminal) to have grunt re-copy changed files automatically
|
||||
grunt.config( 'watch', {
|
||||
watch: {
|
||||
// watch for changes in the src dir
|
||||
files: [ SRC_SYMLINK + '/**' ],
|
||||
tasks: [ 'uglify' ],
|
||||
// tasks: [ 'copy', 'pack' ],
|
||||
options: {
|
||||
spawn: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------- decompress for easier debugging
|
||||
grunt.registerTask( 'decompress', function(){
|
||||
grunt.log.writeln( "decompressing... (don't forget to call 'grunt' again before committing)" );
|
||||
@@ -76,10 +60,18 @@ module.exports = function( grunt ){
|
||||
// alias for symmetry
|
||||
grunt.registerTask( 'compress', [ 'uglify' ] );
|
||||
|
||||
// -------------------------------------------------------------------------- copy,pack only those changed
|
||||
// adapted from grunt-contrib-watch jslint example
|
||||
//TODO: a bit hacky and there's prob. a better way
|
||||
//NOTE: copy will fail silently if a file isn't found
|
||||
// -------------------------------------------------------------------------- watch & copy,pack only those changed
|
||||
// use 'grunt watch' (from a new tab in your terminal) to have grunt re-copy changed files automatically
|
||||
grunt.config( 'watch', {
|
||||
watch: {
|
||||
// watch for changes in the src dir
|
||||
files: [ paths.srcSymlink + '/**' ],
|
||||
tasks: [ 'uglify' ],
|
||||
options: {
|
||||
spawn: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// outer scope variable for the event handler and onChange fn - begin with empty hash
|
||||
var changedFiles = Object.create(null);
|
||||
@@ -89,23 +81,24 @@ module.exports = function( grunt ){
|
||||
grunt.log.writeln( 'onChange, changedFiles:', Object.keys( changedFiles ) );
|
||||
grunt.config( 'uglify.target.files', [{
|
||||
expand : true,
|
||||
cwd : SRC_SYMLINK,
|
||||
cwd : paths.srcSymlink,
|
||||
src : Object.keys( changedFiles ),
|
||||
dest : DIST_PATH
|
||||
dest : paths.dist
|
||||
}]);
|
||||
changedFiles = Object.create(null);
|
||||
}, 200);
|
||||
|
||||
grunt.event.on( 'watch', function( action, filepath ) {
|
||||
var addChangedFile = function( action, filepath ) {
|
||||
// store each filepath in a Files obj, the debounced fn above will use it as an aggregate list for copying
|
||||
// we need to take galaxy/scripts out of the filepath or it will be copied to the wrong loc
|
||||
filepath = filepath.replace( SRC_SYMLINK + '/', '' );
|
||||
grunt.log.writeln( 'on.watch, filepath:', filepath );
|
||||
changedFiles[filepath] = action;
|
||||
filepath = filepath.replace( paths.srcSymlink + '/', '' );
|
||||
// grunt.log.writeln( 'on.watch, filepath:', filepath );
|
||||
changedFiles[ filepath ] = action;
|
||||
onChange();
|
||||
});
|
||||
|
||||
};
|
||||
grunt.event.on( 'watch', addChangedFile );
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
grunt.loadNpmTasks( 'grunt-contrib-watch' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-uglify' );
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
define( [], function() {
|
||||
return 'Remove this file';
|
||||
});
|
||||
@@ -272,6 +272,102 @@
|
||||
<datatype extension="rdf" type="galaxy.datatypes.graph:Rdf" display_in_upload="true"/>
|
||||
<!-- Excel datatypes -->
|
||||
<datatype extension="xlsx" type="galaxy.datatypes.binary:Xlsx" display_in_upload="true" />
|
||||
<datatype extension="btwisted" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="cai" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="charge" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="checktrans" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="chips" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="codcmp" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="coderet" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="compseq" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="cpgplot" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="cpgreport" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="cusp" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="cut" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="dan" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="digest" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="dreg" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="einverted" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="epestfind" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="equicktandem" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="est2genome" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="etandem" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="freak" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="fuzznuc" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="fuzzpro" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="fuzztran" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="garnier" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="geecee" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="helixturnhelix" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="hmoment" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="isochore" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="match" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="nametable" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="needle" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="newcpgreport" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="newcpgseek" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="noreturn" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="palindrome" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="pepcoil" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="pepinfo" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="pepstats" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="polydot" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="preg" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="prettyseq" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="primersearch" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="showfeat" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="showorf" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="sixpack" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="strider" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="supermatcher" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="syco" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="textsearch" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="vectorstrip" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="wobble" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="wordcount" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<!-- Report formats http://emboss.sourceforge.net/docs/themes/ReportFormats.html -->
|
||||
<datatype extension="dbmotif" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="diffseq" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="excel" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="feattable" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="motif" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="regions" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="seqtable" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="simple" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="table" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="tagseq" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<!-- Sequence formats http://emboss.sourceforge.net/docs/themes/SequenceFormats.html -->
|
||||
<datatype extension="acedb" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="clustal" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="codata" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="embl" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="fitch" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="gcg" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="genbank" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="hennig86" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="ig" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="jackknifer" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="jackknifernon" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="mega" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="meganon" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="ncbi" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="nexus" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="nexusnon" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="phylip" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="phylipnon" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="pir" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="staden" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="swiss" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<!-- Alignment Formats http://emboss.sourceforge.net/docs/themes/AlignFormats.html -->
|
||||
<datatype extension="markx0" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="markx1" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="markx10" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="markx2" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="markx3" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="pair" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="score" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="srs" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<datatype extension="srspair" type="galaxy.datatypes.data:Text" subclass="True"/>
|
||||
<!-- MSA Datatypes -->
|
||||
<datatype extension="hmm2" type="galaxy.datatypes.msa:Hmmer2" display_in_upload="true" />
|
||||
<datatype extension="hmm3" type="galaxy.datatypes.msa:Hmmer3" display_in_upload="true" />
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
==================================
|
||||
Galaxy Core Governance
|
||||
==================================
|
||||
|
||||
This document informally outlines the organizational structure governing the
|
||||
Galaxy core code base hosted at https://github.com/galaxyproject/galaxy. This
|
||||
governance extends to code-related activities of this repository such as
|
||||
releases and packaging. This governance does not include infrastructure such
|
||||
as Galaxy's Trello board, the Galaxy mailing lists, etc... or other Galaxy-
|
||||
related projects belonging to the ``galaxyproject`` organization on GitHub.
|
||||
|
||||
Committers
|
||||
==========
|
||||
|
||||
The `committers` group is the group of trusted developers and advocates who
|
||||
manage the core Galaxy code base. They assume many roles required to achieve
|
||||
the project's goals, especially those that require a high level of trust.
|
||||
|
||||
Galaxy Project `committers` are the only individuals who may commit to the
|
||||
core Galaxy code base. All commits must be made in accordance with procedures
|
||||
outlined in Galaxy's CONTRIBUTING_ document. In particular, in most cases
|
||||
direct commit access is not allowed and this access is restricted to merging
|
||||
pull requests issued by others.
|
||||
|
||||
Committers may participate in formal votes - these votes typically include
|
||||
votes to modify team membership, merge pull requests, and modify or clarify
|
||||
the procedures outlined in this document and CONTRIBUTING_.
|
||||
|
||||
Members
|
||||
----------
|
||||
|
||||
- Enis Afgan (@afgane)
|
||||
- Dannon Baker (@dannon)
|
||||
- Daniel Blankenberg (@blankenberg)
|
||||
- Dave Bouvier (@davebx)
|
||||
- Martin Čech (@martenson)
|
||||
- John Chilton (@jmchilton)
|
||||
- Dave Clements (@tnabtaf)
|
||||
- Nate Coraor (@natefoo)
|
||||
- Carl Eberhard (@carlfeberhard)
|
||||
- Jeremy Goecks (@jgoecks)
|
||||
- Björn Grüning (@bgruening)
|
||||
- Aysam Guerler (@guerler)
|
||||
- Jennifer Hillman Jackson (@jennaj)
|
||||
- Ross Lazarus (@fubar2)
|
||||
- Anton Nekrutenko (@nekrut)
|
||||
- Eric Rasche (@erasche)
|
||||
- Nicola Soranzo (@nsoranzo)
|
||||
- James Taylor (@jxtx)
|
||||
- Nitesh Turaga (@nitesh1989)
|
||||
|
||||
Membership
|
||||
----------
|
||||
|
||||
The `committers` group was seeded with the group of active developers and
|
||||
advocates with commit access to the repository as of May 2015. This group
|
||||
subsequently voted in new members.
|
||||
|
||||
Any member of the `committers` group may nominate an individual for membership
|
||||
to the `committers` group. Such individuals must have demonstrated:
|
||||
|
||||
- Good grasp of the design of Galaxy core project.
|
||||
- Solid track record of being constructive and helpful.
|
||||
- Significant contributions to the project.
|
||||
- Willingness to dedicate some time to improving Galaxy.
|
||||
|
||||
The above list of `committers` is the canonical source used to determine
|
||||
membership to the `committers` group - as such new members may be added to
|
||||
this group by opening a pull request adding a qualified person to this list.
|
||||
Pull requests modifying the membership of this list are subject to the normal
|
||||
rules for pull requests that modify governance procedures outlined in Galaxy's
|
||||
CONTRIBUTING_ document - with one exception - a `commmitter` may not vote
|
||||
against their own removal from the group (for obvious reasons).
|
||||
|
||||
Given the responsibilities and power invested in this group - it is important
|
||||
that individuals not actively working on Galaxy in some fashion are removed
|
||||
the group. If individuals in this group intend to change jobs or reallocate
|
||||
volunteer activities and will no longer be active in the Galaxy community,
|
||||
they should withdraw from membership of this group. Periodically, active
|
||||
members may review this group and request inactive members are removed - this
|
||||
should not be interpreted as a condemnation of these inactive members but
|
||||
merely as a reflection of a desire to keep this group focused enough to remain
|
||||
effective.
|
||||
|
||||
.. _LICENSE: https://github.com/galaxyproject/galaxy/blob/dev/LICENSE.txt
|
||||
.. _CONTRIBUTING: https://github.com/galaxyproject/galaxy/blob/dev/CONTRIBUTING.md
|
||||
@@ -114,6 +114,27 @@ class Ab1( Binary ):
|
||||
|
||||
Binary.register_unsniffable_binary_ext("ab1")
|
||||
|
||||
|
||||
class Idat( Binary ):
|
||||
"""Binary data in idat format"""
|
||||
file_ext = "idat"
|
||||
|
||||
def __init__( self, **kwd ):
|
||||
Binary.__init__( self, **kwd )
|
||||
|
||||
def sniff( self, filename ):
|
||||
try:
|
||||
header = open( filename ).read(4)
|
||||
if binascii.b2a_hex( header ) == binascii.hexlify( 'IDAT' ):
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
Binary.register_sniffable_binary_format("idat", "idat", Idat)
|
||||
|
||||
|
||||
|
||||
class CompressedArchive( Binary ):
|
||||
"""
|
||||
Class describing an compressed binary file
|
||||
|
||||
@@ -208,7 +208,7 @@ class Registry( object ):
|
||||
for mod in fields:
|
||||
module = getattr( module, mod )
|
||||
datatype_class = getattr( module, datatype_class_name )
|
||||
self.log.debug( 'Retrieved datatype module %s from the datatype registry.' % str( datatype_module ) )
|
||||
self.log.debug( 'Retrieved datatype module %s:%s from the datatype registry.' % ( str( datatype_module ), datatype_class_name ) )
|
||||
except Exception, e:
|
||||
self.log.exception( 'Error importing datatype module %s: %s' % ( str( datatype_module ), str( e ) ) )
|
||||
ok = False
|
||||
|
||||
@@ -2144,7 +2144,7 @@ class Library( object, Dictifiable, HasName ):
|
||||
"""
|
||||
rval = super( Library, self ).to_dict( view=view, value_mapper=value_mapper )
|
||||
if 'root_folder_id' in rval:
|
||||
rval[ 'root_folder_id' ] = 'F' + rval[ 'root_folder_id' ]
|
||||
rval[ 'root_folder_id' ] = 'F' + str(rval[ 'root_folder_id' ])
|
||||
return rval
|
||||
def get_active_folders( self, folder, folders=None ):
|
||||
# TODO: should we make sure the library is not deleted?
|
||||
|
||||
@@ -13,6 +13,8 @@ import threading
|
||||
import types
|
||||
import urllib
|
||||
import copy
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from galaxy import eggs, util
|
||||
|
||||
@@ -80,6 +82,24 @@ JOB_RESOURCE_CONDITIONAL_XML = """<conditional name="__job_resource">
|
||||
HELP_UNINITIALIZED = threading.Lock()
|
||||
|
||||
|
||||
class ToolErrorLog:
|
||||
def __init__(self):
|
||||
self.error_stack = []
|
||||
self.max_errors = 100
|
||||
|
||||
def add_error(self, file, phase, exception):
|
||||
self.error_stack.insert(0, {
|
||||
"file" : file,
|
||||
"time" : str(datetime.now()),
|
||||
"phase" : phase,
|
||||
"error" : str(exception)
|
||||
} )
|
||||
if len(self.error_stack) > self.max_errors:
|
||||
self.error_stack.pop()
|
||||
|
||||
|
||||
global_tool_errors = ToolErrorLog()
|
||||
|
||||
class ToolNotFoundException( Exception ):
|
||||
pass
|
||||
|
||||
@@ -103,7 +123,12 @@ class ToolBox( AbstractToolBox ):
|
||||
return self._tools_by_id
|
||||
|
||||
def create_tool( self, config_file, repository_id=None, guid=None, **kwds ):
|
||||
tool_source = get_tool_source( config_file, getattr( self.app.config, "enable_beta_tool_formats", False ) )
|
||||
try:
|
||||
tool_source = get_tool_source( config_file, getattr( self.app.config, "enable_beta_tool_formats", False ) )
|
||||
except Exception, e:
|
||||
#capture and log parsing errors
|
||||
global_tool_errors.add_error(config_file, "Tool XML parsing", e)
|
||||
raise e
|
||||
# Allow specifying a different tool subclass to instantiate
|
||||
tool_module = tool_source.parse_tool_module()
|
||||
if tool_module is not None:
|
||||
@@ -460,7 +485,11 @@ class Tool( object, Dictifiable ):
|
||||
#populate toolshed repository info, if available
|
||||
self.populate_tool_shed_info()
|
||||
# Parse XML element containing configuration
|
||||
self.parse( tool_source, guid=guid )
|
||||
try:
|
||||
self.parse( tool_source, guid=guid )
|
||||
except Exception, e:
|
||||
global_tool_errors.add_error(config_file, "Tool Loading", e)
|
||||
raise e
|
||||
self.external_runJob_script = app.config.drmaa_external_runjob_script
|
||||
self.history_manager = histories.HistoryManager( app )
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from galaxy.tools.parameters.basic import (
|
||||
SelectToolParameter,
|
||||
)
|
||||
from galaxy.tools.parameters.grouping import Conditional, Repeat, Section
|
||||
from galaxy.tools import global_tool_errors
|
||||
from galaxy.jobs.datasets import dataset_path_rewrites
|
||||
|
||||
import logging
|
||||
@@ -400,10 +401,25 @@ class ToolEvaluator( object ):
|
||||
self.extra_filenames = []
|
||||
self.command_line = None
|
||||
|
||||
self.__build_config_files( )
|
||||
self.__build_param_file( )
|
||||
self.__build_command_line( )
|
||||
|
||||
try:
|
||||
self.__build_config_files( )
|
||||
except Exception, e:
|
||||
#capture and log parsing errors
|
||||
global_tool_errors.add_error(self.tool.config_file, "Building Config Files", e)
|
||||
raise e
|
||||
try:
|
||||
self.__build_param_file( )
|
||||
except Exception, e:
|
||||
#capture and log parsing errors
|
||||
global_tool_errors.add_error(self.tool.config_file, "Building Param File", e)
|
||||
raise e
|
||||
try:
|
||||
self.__build_command_line( )
|
||||
except Exception, e:
|
||||
#capture and log parsing errors
|
||||
global_tool_errors.add_error(self.tool.config_file, "Building Command Line", e)
|
||||
raise e
|
||||
|
||||
return self.command_line, self.extra_filenames
|
||||
|
||||
def __build_command_line( self ):
|
||||
|
||||
@@ -48,7 +48,7 @@ class ToolTestBuilder( object ):
|
||||
self.name = name
|
||||
self.maxseconds = maxseconds
|
||||
self.required_files = []
|
||||
self.inputs = []
|
||||
self.inputs = {}
|
||||
self.outputs = []
|
||||
# By default do not making assertions on number of outputs - but to
|
||||
# test filtering allow explicitly state number of outputs.
|
||||
@@ -134,6 +134,7 @@ class ToolTestBuilder( object ):
|
||||
self.expect_failure = test_dict.get("expect_failure", False)
|
||||
self.md5 = test_dict.get("md5", None)
|
||||
except Exception, e:
|
||||
self.inputs = {}
|
||||
self.error = True
|
||||
self.exception = e
|
||||
|
||||
|
||||
@@ -818,8 +818,7 @@ class GalaxyWebTransaction( base.DefaultWebTransaction,
|
||||
template = template_lookup.get_template( filename )
|
||||
template.output_encoding = 'utf-8'
|
||||
|
||||
data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
|
||||
request=self.request, response=self.response, app=self.app )
|
||||
data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
|
||||
data.update( self.template_context )
|
||||
data.update( kwargs )
|
||||
return template.render( **data )
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
Provides factory methods to assemble the Galaxy web application
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import sys
|
||||
import atexit
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except:
|
||||
import ConfigParser as configparser
|
||||
|
||||
from paste import httpexceptions
|
||||
import pkg_resources
|
||||
|
||||
import galaxy.app
|
||||
from galaxy.config import process_is_uwsgi
|
||||
@@ -19,6 +23,10 @@ from galaxy import util
|
||||
from galaxy.util import asbool
|
||||
from galaxy.util.properties import load_app_properties
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('Paste')
|
||||
from paste import httpexceptions
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
@@ -37,6 +45,10 @@ class GalaxyWebApplication( galaxy.web.framework.webapp.WebApplication ):
|
||||
|
||||
|
||||
def app_factory( global_conf, **kwargs ):
|
||||
return paste_app_factory( global_conf, **kwargs )
|
||||
|
||||
|
||||
def paste_app_factory( global_conf, **kwargs ):
|
||||
"""
|
||||
Return a wsgi application serving the root object
|
||||
"""
|
||||
@@ -119,6 +131,22 @@ def app_factory( global_conf, **kwargs ):
|
||||
return webapp
|
||||
|
||||
|
||||
def uwsgi_app_factory():
|
||||
import uwsgi
|
||||
root = os.path.abspath(uwsgi.opt.get('galaxy_root', os.getcwd()))
|
||||
config_file = uwsgi.opt.get('galaxy_config_file', os.path.join(root, 'config', 'galaxy.ini'))
|
||||
global_conf = {
|
||||
'__file__' : config_file if os.path.exists(__file__) else None,
|
||||
'here' : root }
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read(config_file)
|
||||
try:
|
||||
kwargs = dict(parser.items('app:main'))
|
||||
except configparser.NoSectionError:
|
||||
kwargs = {}
|
||||
return app_factory(global_conf, **kwargs)
|
||||
|
||||
|
||||
@postfork
|
||||
def postfork_setup():
|
||||
from galaxy.app import app
|
||||
@@ -635,7 +663,7 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
|
||||
# Interactive exception debugging, scary dangerous if publicly
|
||||
# accessible, if not enabled we'll use the regular error printing
|
||||
# middleware.
|
||||
pkg_resources.require( "WebError" )
|
||||
eggs.require( "WebError" )
|
||||
from weberror import evalexception
|
||||
app = evalexception.EvalException( app, conf,
|
||||
templating_formatters=build_template_error_formatters() )
|
||||
|
||||
@@ -18,6 +18,7 @@ from galaxy.web.base.controller import BaseUIController, UsesQuotaMixin
|
||||
from galaxy.web.base.controllers.admin import Admin
|
||||
from galaxy.web.framework.helpers import grids, time_ago
|
||||
from galaxy.web.params import QuotaParamParser
|
||||
from galaxy.tools import global_tool_errors
|
||||
from tool_shed.util import common_util
|
||||
from tool_shed.util import encoding_util
|
||||
from tool_shed.util.web_util import escape
|
||||
@@ -824,6 +825,11 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP
|
||||
message=message,
|
||||
status=status )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def tool_errors( self, trans, **kwd ):
|
||||
return trans.fill_template('admin/tool_errors.mako', tool_errors=global_tool_errors.error_stack)
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def view_datatypes_registry( self, trans, **kwd ):
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
from galaxy import util
|
||||
from galaxy import web
|
||||
from galaxy.util import pretty_print_time_interval
|
||||
from galaxy.exceptions import RequestParameterMissingException
|
||||
from galaxy.exceptions import AdminRequiredException
|
||||
from galaxy.exceptions import ObjectNotFound
|
||||
from galaxy.web import require_admin as require_admin
|
||||
from galaxy.web import _future_expose_api as expose_api
|
||||
from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless
|
||||
from galaxy.web.base.controller import BaseAPIController
|
||||
from tool_shed.managers import groups
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class GroupsController( BaseAPIController ):
|
||||
"""RESTful controller for interactions with groups in the Tool Shed."""
|
||||
|
||||
def __init__( self, app ):
|
||||
super( GroupsController, self ).__init__( app )
|
||||
self.group_manager = groups.GroupManager()
|
||||
|
||||
def __get_value_mapper( self, trans ):
|
||||
value_mapper = { 'id' : trans.security.encode_id }
|
||||
return value_mapper
|
||||
|
||||
@expose_api_anonymous_and_sessionless
|
||||
def index( self, trans, deleted=False, **kwd ):
|
||||
"""
|
||||
GET /api/groups
|
||||
Return a list of dictionaries that contain information about each Group.
|
||||
|
||||
:param deleted: flag used to include deleted groups
|
||||
|
||||
Example: GET localhost:9009/api/groups
|
||||
"""
|
||||
group_dicts = []
|
||||
deleted = util.asbool( deleted )
|
||||
if deleted and not trans.user_is_admin():
|
||||
raise AdminRequiredException( 'Only administrators can query deleted groups.' )
|
||||
for group in self.group_manager.list( trans, deleted ):
|
||||
group_dicts.append( self._populate( trans, group ) )
|
||||
return group_dicts
|
||||
|
||||
@expose_api
|
||||
@require_admin
|
||||
def create( self, trans, payload, **kwd ):
|
||||
"""
|
||||
POST /api/groups
|
||||
Return a dictionary of information about the created group.
|
||||
The following parameters are included in the payload:
|
||||
|
||||
:param name (required): the name of the group
|
||||
:param description (optional): the description of the group
|
||||
|
||||
Example: POST /api/groups/?key=XXXYYYXXXYYY
|
||||
Content-Disposition: form-data; name="name" Group_Name
|
||||
Content-Disposition: form-data; name="description" Group_Description
|
||||
"""
|
||||
group_dict = dict( message='', status='ok' )
|
||||
name = payload.get( 'name', '' )
|
||||
if name:
|
||||
description = payload.get( 'description', '' )
|
||||
if not description:
|
||||
description = ''
|
||||
else:
|
||||
# TODO add description field to the model
|
||||
group_dict = self.group_manager.create( trans, name=name ).to_dict( view='element', value_mapper=self.__get_value_mapper( trans ) )
|
||||
else:
|
||||
raise RequestParameterMissingException( 'Missing required parameter "name".' )
|
||||
return group_dict
|
||||
|
||||
@expose_api_anonymous_and_sessionless
|
||||
def show( self, trans, encoded_id, **kwd ):
|
||||
"""
|
||||
GET /api/groups/{encoded_group_id}
|
||||
Return a dictionary of information about a group.
|
||||
|
||||
:param id: the encoded id of the Group object
|
||||
|
||||
Example: GET localhost:9009/api/groups/f9cad7b01a472135
|
||||
"""
|
||||
decoded_id = trans.security.decode_id( encoded_id )
|
||||
group = self.group_manager.get( trans, decoded_id )
|
||||
if group is None:
|
||||
raise ObjectNotFound( 'Unable to locate group record for id %s.' % ( str( encoded_id ) ) )
|
||||
return self._populate( trans, group )
|
||||
|
||||
def _populate( self, trans, group ):
|
||||
"""
|
||||
Turn the given group information from DB into a dict
|
||||
and add other characteristics like members and repositories.
|
||||
"""
|
||||
model = trans.app.model
|
||||
group_dict = group.to_dict( view='collection', value_mapper=self.__get_value_mapper( trans ) )
|
||||
group_members = []
|
||||
group_repos = []
|
||||
total_downloads = 0
|
||||
for uga in group.users:
|
||||
user = trans.sa_session.query( model.User ).filter( model.User.table.c.id == uga.user_id ).one()
|
||||
user_repos_count = 0
|
||||
for repo in trans.sa_session.query( model.Repository ) \
|
||||
.filter( model.Repository.table.c.user_id == uga.user_id ) \
|
||||
.join( model.RepositoryMetadata.table ) \
|
||||
.join( model.User.table ) \
|
||||
.outerjoin( model.RepositoryCategoryAssociation.table ) \
|
||||
.outerjoin( model.Category.table ):
|
||||
time_repo_created = pretty_print_time_interval( repo.create_time, True )
|
||||
approved = ''
|
||||
ratings = []
|
||||
for review in repo.reviews:
|
||||
if review.rating:
|
||||
ratings.append( review.rating )
|
||||
if review.approved == 'yes':
|
||||
approved = 'yes'
|
||||
# TODO add user ratings
|
||||
ratings_mean = str( float( sum( ratings ) ) / len( ratings ) ) if len( ratings ) > 0 else ''
|
||||
total_downloads += repo.times_downloaded
|
||||
group_repos.append( { 'name': repo.name, 'times_downloaded': repo.times_downloaded, 'owner': repo.user.username, 'time_created': time_repo_created, 'description': repo.description, 'approved': approved, 'ratings_mean': ratings_mean, 'tools': 'unknown' } )
|
||||
user_repos_count += 1
|
||||
encoded_user_id = trans.app.security.encode_id( repo.user.id )
|
||||
user_repos_url = web.url_for( controller='repository', action='browse_repositories_by_user', user_id=encoded_user_id )
|
||||
time_created = pretty_print_time_interval( user.create_time, True )
|
||||
member_dict = { 'id': encoded_user_id, 'username': user.username, 'user_repos_url': user_repos_url, 'user_repos_count': user_repos_count, 'user_tools_count': 'unknown', 'time_created': time_created }
|
||||
group_members.append( member_dict )
|
||||
group_dict[ 'members' ] = group_members
|
||||
group_dict[ 'total_members' ] = len( group_members )
|
||||
group_dict[ 'repositories' ] = group_repos
|
||||
group_dict[ 'total_repos' ] = len( group_repos )
|
||||
group_dict[ 'total_downloads' ] = total_downloads
|
||||
return group_dict
|
||||
@@ -86,6 +86,21 @@ def app_factory( global_conf, **kwargs ):
|
||||
controller='authenticate',
|
||||
action='get_tool_shed_api_key',
|
||||
conditions=dict( method=[ "GET" ] ) )
|
||||
webapp.mapper.connect( 'group',
|
||||
'/api/groups/',
|
||||
controller='groups',
|
||||
action='index',
|
||||
conditions=dict( method=[ "GET" ] ) )
|
||||
webapp.mapper.connect( 'group',
|
||||
'/api/groups/',
|
||||
controller='groups',
|
||||
action='create',
|
||||
conditions=dict( method=[ "POST" ] ) )
|
||||
webapp.mapper.connect( 'group',
|
||||
'/api/groups/:encoded_id',
|
||||
controller='groups',
|
||||
action='show',
|
||||
conditions=dict( method=[ "GET" ] ) )
|
||||
webapp.mapper.connect( 'repo_search',
|
||||
'/api/search/',
|
||||
controller='search',
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import logging
|
||||
from galaxy import model, util
|
||||
from galaxy import web
|
||||
from galaxy.model.orm import and_, not_, or_
|
||||
from galaxy.web.base.controller import BaseUIController
|
||||
from galaxy.web.framework.helpers import escape, grids
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class Group( BaseUIController ):
|
||||
|
||||
@web.expose
|
||||
def index( self, trans, **kwd ):
|
||||
# define app configuration for generic mako template
|
||||
app = {
|
||||
'jscript' : "toolshed.groups"
|
||||
}
|
||||
return trans.fill_template( '/webapps/tool_shed/group/index.mako',
|
||||
config = {
|
||||
'title': 'Tool Shed Groups',
|
||||
'app': app } )
|
||||
@@ -30,7 +30,7 @@ class TagAttributeHandler( object ):
|
||||
elem_index=sub_index,
|
||||
elem=sub_elem,
|
||||
message=message )
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
@@ -68,7 +68,7 @@ class TagAttributeHandler( object ):
|
||||
altered, new_sub_elem, error_message = self.rdd.handle_sub_elem( parent_elem=elem,
|
||||
elem_index=sub_index,
|
||||
elem=sub_elem )
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
@@ -98,7 +98,7 @@ class TagAttributeHandler( object ):
|
||||
altered, new_sub_elem, error_message = \
|
||||
self.process_actions_tag_set( elem=sub_elem,
|
||||
message=message )
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
@@ -150,7 +150,7 @@ class TagAttributeHandler( object ):
|
||||
( str( package_version ), str( package_name ) )
|
||||
error_message += 'the recipe for installing the package is missing either an '
|
||||
error_message += '<actions> tag set or an <actions_group> tag set.'
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
@@ -176,7 +176,7 @@ class TagAttributeHandler( object ):
|
||||
elem_index=sub_index,
|
||||
elem=sub_elem,
|
||||
message=message )
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
@@ -191,7 +191,7 @@ class TagAttributeHandler( object ):
|
||||
altered, new_elem, error_message = self.rdd.handle_complex_dependency_elem( parent_elem=parent_elem,
|
||||
elem_index=elem_index,
|
||||
elem=elem )
|
||||
if error_message:
|
||||
if error_message and error_message not in message:
|
||||
message += error_message
|
||||
if altered:
|
||||
if not self.altered:
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Manager and Serializer for TS groups.
|
||||
"""
|
||||
from galaxy.exceptions import InconsistentDatabase
|
||||
from galaxy.exceptions import RequestParameterInvalidException
|
||||
from galaxy.exceptions import ObjectNotFound
|
||||
from galaxy.exceptions import InternalServerError
|
||||
from galaxy.exceptions import ItemAccessibilityException
|
||||
from galaxy.exceptions import Conflict
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class GroupManager( object ):
|
||||
"""
|
||||
Interface/service object for interacting with TS groups.
|
||||
"""
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
super( GroupManager, self ).__init__( *args, **kwargs )
|
||||
|
||||
def get( self, trans, decoded_group_id=None, name=None ):
|
||||
"""
|
||||
Get the group from the DB based on its ID or name.
|
||||
|
||||
:param decoded_group_id: decoded group id
|
||||
:type decoded_group_id: int
|
||||
|
||||
:returns: the requested group
|
||||
:rtype: Group
|
||||
"""
|
||||
if decoded_group_id is None and name is None:
|
||||
raise RequestParameterInvalidException( 'You must supply either ID or a name of the group.' )
|
||||
|
||||
name_query = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name == name )
|
||||
id_query = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.id == decoded_group_id )
|
||||
|
||||
try:
|
||||
group = id_query.one() if decoded_group_id else name_query.one()
|
||||
except MultipleResultsFound:
|
||||
raise InconsistentDatabase( 'Multiple groups found with the same id.' )
|
||||
except NoResultFound:
|
||||
raise ObjectNotFound( 'No group found with the id provided.' )
|
||||
except Exception:
|
||||
raise InternalServerError( 'Error loading from the database.' )
|
||||
return group
|
||||
|
||||
def create( self, trans, name, description=''):
|
||||
"""
|
||||
Create a new group.
|
||||
"""
|
||||
if not trans.user_is_admin:
|
||||
raise ItemAccessibilityException( 'Only administrators can create groups.' )
|
||||
else:
|
||||
if self.get( trans, name=name ):
|
||||
raise Conflict( 'Group with the given name already exists. Name: ' + str( name ) )
|
||||
# TODO add description field to the model
|
||||
group = trans.app.model.Group( name=name )
|
||||
trans.sa_session.add( group )
|
||||
trans.sa_session.flush()
|
||||
return group
|
||||
|
||||
def update( self, trans, group, name=None, description=None ):
|
||||
"""
|
||||
Update the given group
|
||||
"""
|
||||
changed = False
|
||||
if not trans.user_is_admin():
|
||||
raise ItemAccessibilityException( 'Only administrators can update groups.' )
|
||||
if group.deleted:
|
||||
raise RequestParameterInvalidException( 'You cannot modify a deleted group. Undelete it first.' )
|
||||
if name is not None:
|
||||
group.name = name
|
||||
changed = True
|
||||
if description is not None:
|
||||
group.description = description
|
||||
changed = True
|
||||
if changed:
|
||||
trans.sa_session.add( group )
|
||||
trans.sa_session.flush()
|
||||
return group
|
||||
|
||||
def delete( self, trans, group, undelete=False ):
|
||||
"""
|
||||
Mark given group deleted/undeleted based on the flag.
|
||||
"""
|
||||
if not trans.user_is_admin():
|
||||
raise ItemAccessibilityException( 'Only administrators can delete and undelete groups.' )
|
||||
if undelete:
|
||||
group.deleted = False
|
||||
else:
|
||||
group.deleted = True
|
||||
trans.sa_session.add( group )
|
||||
trans.sa_session.flush()
|
||||
return group
|
||||
|
||||
def list( self, trans, deleted=False ):
|
||||
"""
|
||||
Return a list of groups from the DB.
|
||||
|
||||
:returns: query that will emit all groups
|
||||
:rtype: sqlalchemy query
|
||||
"""
|
||||
is_admin = trans.user_is_admin()
|
||||
query = trans.sa_session.query( trans.app.model.Group )
|
||||
if is_admin:
|
||||
if deleted is None:
|
||||
# Flag is not specified, do not filter on it.
|
||||
pass
|
||||
elif deleted:
|
||||
query = query.filter( trans.app.model.Group.table.c.deleted == True )
|
||||
else:
|
||||
query = query.filter( trans.app.model.Group.table.c.deleted == False )
|
||||
else:
|
||||
query = query.filter( trans.app.model.Group.table.c.deleted == False )
|
||||
return query
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Manager and Serializer for TS repositories.
|
||||
"""
|
||||
from galaxy.exceptions import InconsistentDatabase
|
||||
from galaxy.exceptions import RequestParameterInvalidException
|
||||
from galaxy.exceptions import InternalServerError
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class RepoManager( object ):
|
||||
"""
|
||||
Interface/service object for interacting with TS repositories.
|
||||
"""
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
super( RepoManager, self ).__init__( *args, **kwargs )
|
||||
|
||||
def get( self, trans, decoded_repo_id ):
|
||||
"""
|
||||
Get the repo from the DB.
|
||||
|
||||
:param decoded_repo_id: decoded repo id
|
||||
:type decoded_repo_id: int
|
||||
|
||||
:returns: the requested repo
|
||||
:rtype: Repository
|
||||
"""
|
||||
try:
|
||||
repo = trans.sa_session.query( trans.app.model.Repository ).filter( trans.app.model.Repository.table.c.id == decoded_repo_id ).one()
|
||||
except MultipleResultsFound:
|
||||
raise InconsistentDatabase( 'Multiple repositories found with the same id.' )
|
||||
except NoResultFound:
|
||||
raise RequestParameterInvalidException( 'No repository found with the id provided.' )
|
||||
except Exception:
|
||||
raise InternalServerError( 'Error loading from the database.' )
|
||||
return repo
|
||||
|
||||
def list_by_owner( self, trans, user_id ):
|
||||
"""
|
||||
Return a list of of repositories owned by a given TS user from the DB.
|
||||
|
||||
:returns: query that will emit repositories owned by given user
|
||||
:rtype: sqlalchemy query
|
||||
"""
|
||||
query = trans.sa_session.query( trans.app.model.Repository ).filter( trans.app.model.Repository.table.c.user_id == user_id )
|
||||
return query
|
||||
|
||||
def create( self, trans, name, description=''):
|
||||
"""
|
||||
Create a new group.
|
||||
"""
|
||||
|
||||
def update( self, trans, group, name=None, description=None ):
|
||||
"""
|
||||
Update the given group
|
||||
"""
|
||||
|
||||
def delete( self, trans, group, undelete=False ):
|
||||
"""
|
||||
Mark given group deleted/undeleted based on the flag.
|
||||
"""
|
||||
@@ -17,6 +17,7 @@ cat <<EOF
|
||||
'${0##*/} -api (test_path)' for running all the test scripts in the ./test/api directory
|
||||
'${0##*/} -toolshed (test_path)' for running all the test scripts in the ./test/tool_shed/functional directory
|
||||
'${0##*/} -workflow test.xml' for running a workflow test case as defined by supplied workflow xml test file (experimental)
|
||||
'${0##*/} -installed' for running tests of Tool Shed installed tools
|
||||
'${0##*/} -framework' for running through example tool tests testing framework features in test/functional/tools"
|
||||
'${0##*/} -framework -id toolid' for testing one framework tool (in test/functional/tools/) with id 'toolid'
|
||||
'${0##*/} -data_managers -id data_manager_id' for testing one Data Manager with id 'data_manager_id'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-detail-view.js","sources":["../../../src/mvc/groups/group-detail-view.js"],"names":["define","mod_toastr","mod_group_model","GroupDetailView","Backbone","View","extend","el","options","app","initialize","this","_","window","globalTS","groups","collection","model","get","group_id","render","fetchGroup","that","Group","id","fetch","success","console","log","error","response","responseJSON","err_msg","template","templateRow","$el","html","group","$","tooltip","join"],"mappings":"AAAAA,QACI,cACA,0BAEJ,SACIC,EACAC,GAIJ,GAAIC,GAAkBC,SAASC,KAAKC,QAChCC,GAAI,kBACJC,WACAC,IAAK,KAELC,WAAa,SAAUF,GACnBG,KAAKH,QAAUI,EAAEN,OAAQK,KAAKH,QAASA,GACvCG,KAAKF,IAAMI,OAAOC,SAASC,OAEE,OAAxBJ,KAAKF,IAAIO,YACVL,KAAKM,MAAQN,KAAKF,IAAIO,WAAWE,IAAKP,KAAKH,QAAQW,UACnDR,KAAKS,UAELT,KAAKU,cAKbA,WAAY,SAAUb,GAClB,GAAIc,GAAOX,IACXA,MAAKH,QAAUI,EAAEN,OAAQK,KAAKH,QAASA,GACvCG,KAAKM,MAAQ,GAAIf,GAAgBqB,OAASC,GAAIb,KAAKH,QAAQW,WAC3DR,KAAKM,MAAMQ,OACTC,QAAS,SAAUT,GACjBU,QAAQC,IAAK,mBACbD,QAAQC,IAAKX,GACbK,EAAKF,UAEPS,MAAO,SAAUZ,EAAOa,GAElB7B,EAAW4B,MADyB,mBAA1BC,GAASC,aACDD,EAASC,aAAaC,QAEtB,yBAM9BZ,OAAQ,WACJ,GAAIa,GAAWtB,KAAKuB,aACpBvB,MAAKwB,IAAIC,KAAMH,GAAYI,MAAO1B,KAAKM,SACvCqB,EAAE,yBAAyBC,WAG/BL,YAAa,WACT,MAAOtB,GAAEqB,UACD,QACI,oCACA,mCACA,kMAEA,mBACA,6CACI,UACI,gBACA,wBACA,sBACJ,WACA,UACI,wDACA,OACI,OACI,yBACJ,QACA,OACI,gPACJ,QACA,OACI,6BACJ,QACJ,QACA,YACJ,WACJ,WAEA,wBACA,6CACI,UACI,gBACA,uBACA,qBACA,iBACA,mBACA,kBACA,oBACJ,WACA,UACI,2DACA,OACI,OACI,6KACJ,QACA,OACI,0BACJ,QACA,OACI,+BACJ,QACA,OACI,oBACJ,QACA,OACI,2BACJ,QACA,OACI,2BACJ,QACA,OACI,uBACJ,QACJ,QACA,YACJ,WACJ,WACJ,UACNO,KAAK,OAGf,QACIrC,gBAAiBA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-groupdetail-view.js","sources":["../../../src/mvc/groups/group-groupdetail-view.js"],"names":["define","GroupDetailView","Backbone","View","extend","events","initialize","group","this","render","tmpl","templateRow","setElement","$el","show","_","template","join"],"mappings":"AAAAA,UACA,WAGA,GAAIC,GAAkBC,SAASC,KAAKC,QAChCC,UAGAC,WAAa,SAAUC,GACnBC,KAAKC,OAAQF,IAGjBE,OAAQ,SAAUF,GACd,GAAIG,GAAOF,KAAKG,aAGhB,OAFAH,MAAKI,WAAWF,GAAQH,MAAMA,KAC9BC,KAAKK,IAAIC,OACFN,MAGXG,YAAa,WACT,MAAOI,GAAEC,UACD,8BACNC,KAAK,OAKf,QACIhB,gBAAiBA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-grouprow-view.js","sources":["../../../src/mvc/groups/group-grouprow-view.js"],"names":["define","GroupRowView","Backbone","View","extend","events","initialize","group","this","render","tmpl","templateRow","setElement","$el","show","_","template","join"],"mappings":"AAAAA,UACA,WAGA,GAAIC,GAAeC,SAASC,KAAKC,QAC7BC,UAGAC,WAAa,SAAUC,GACnBC,KAAKC,OAAOF,IAGhBE,OAAQ,SAAUF,GACd,GAAIG,GAAOF,KAAKG,aAIhB,OAFAH,MAAKI,WAAWF,GAAQH,MAAMA,KAC9BC,KAAKK,IAAIC,OACFN,MAaXG,YAAa,WACT,MAAOI,GAAEC,UACD,iDACA,iFACA,uBACA,6CACA,2CACA,SACNC,KAAK,OAKf,QACIhB,aAAcA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-list-view.js","sources":["../../../src/mvc/groups/group-list-view.js"],"names":["define","mod_toastr","mod_group_model","mod_group_listrow_view","GroupListView","Backbone","View","extend","el","defaults","initialize","options","this","_","that","window","globalTS","groups","collection","Groups","fetch","success","model","console","log","render","error","response","responseJSON","err_msg","$","hide","template","templateGroupsList","$el","html","length","order","renderRows","models","tooltip","css","groups_to_render","i","group","renderOne","rowView","GroupListRowView","find","append","tmpl_array","push","join"],"mappings":"AAAAA,QACI,cACA,yBACA,iCAED,SACCC,EACAC,EACAC,GAGJ,GAAIC,GAAgBC,SAASC,KAAKC,QAC9BC,GAAI,kBACJC,YAOAC,WAAa,SAAUC,GACnBC,KAAKD,QAAUE,EAAEJ,SAAUG,KAAKD,YAAeC,KAAKH,SAAUE,EAC9D,IAAIG,GAAOF,IACXG,QAAOC,SAASC,OAAOC,WAAa,GAAIhB,GAAgBiB,OACxDJ,OAAOC,SAASC,OAAOC,WAAWE,OAChCC,QAAS,SAAUC,GACjBC,QAAQC,IAAK,mBACbD,QAAQC,IAAKF,GACbR,EAAKW,UAEPC,MAAO,SAAUJ,EAAOK,GAElB1B,EAAWyB,MADyB,mBAA1BC,GAASC,aACDD,EAASC,aAAaC,QAEtB,yBAO9BT,MAAO,aAMPK,OAAQ,SAAWd,GACfC,KAAKD,QAAUE,EAAEN,OAAQK,KAAKD,QAASA,GACvCmB,EAAG,YAAaC,MAChB,IAAIC,GAAWpB,KAAKqB,oBACpBrB,MAAKsB,IAAIC,KAAMH,GAAYI,OAAQ,GAAIC,MAAO,SAC9CzB,KAAK0B,WAAYvB,OAAOC,SAASC,OAAOC,WAAWqB,QACnDT,EAAG,yBAA0BU,UAC7BV,EAAG,WAAYW,IAAK,WAAW,SAOnCH,WAAY,SAAUI,GAClB,IAAM,GAAIC,GAAI,EAAGA,EAAID,EAAiBN,OAAQO,IAAM,CAChD,GAAIC,GAAQF,EAAiBC,EAC7B/B,MAAKiC,WAAaD,MAAOA,MAQjCC,UAAW,SAAUlC,GACjB,GAAImC,GAAU,GAAI3C,GAAuB4C,iBAAkBpC,EAC3DC,MAAKsB,IAAIc,KAAM,oBAAqBC,OAAQH,EAAQtC,KAyCxDyB,mBAAoB,WAuBhB,MAtBAiB,eAEAA,WAAWC,KAAK,qBAChBD,WAAWC,KAAK,UAChBD,WAAWC,KAAK,mDAChBD,WAAWC,KAAK,4BAChBD,WAAWC,KAAK,uCAChBD,WAAWC,KAAK,iBAChBD,WAAWC,KAAK,8CAChBD,WAAWC,KAAK,cAChBD,WAAWC,KAAK,mMAChBD,WAAWC,KAAK,gDAChBD,WAAWC,KAAK,6CAChBD,WAAWC,KAAK,iDAChBD,WAAWC,KAAK,eAChBD,WAAWC,KAAK,mCAEhBD,WAAWC,KAAK,eAChBD,WAAWC,KAAK,YAChBD,WAAWC,KAAK,UAChBD,WAAWC,KAAK,UAETtC,EAAEmB,SAASkB,WAAWE,KAAK,OAK1C,QACIhD,cAAeA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-listrow-view.js","sources":["../../../src/mvc/groups/group-listrow-view.js"],"names":["define","GroupListRowView","Backbone","View","extend","events","initialize","options","this","render","group","tmpl","templateRow","setElement","$el","show","_","template","join"],"mappings":"AAAAA,UACA,WAGA,GAAIC,GAAmBC,SAASC,KAAKC,QACjCC,UAEAC,WAAa,SAAUC,GACnBC,KAAKC,OAAQF,EAAQG,QAGzBD,OAAQ,SAAUC,GACd,GAAIC,GAAOH,KAAKI,aAGhB,OAFAJ,MAAKK,WAAWF,GAAQD,MAAMA,KAC9BF,KAAKM,IAAIC,OACFP,MAGXI,YAAa,WACT,MAAOI,GAAEC,UACD,iDACA,iFACA,uBACA,6CACA,2CACA,SACNC,KAAK,OAKf,QACIjB,iBAAkBA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group-model.js","sources":["../../../src/mvc/groups/group-model.js"],"names":["define","Group","Backbone","Model","extend","urlRoot","Groups","Collection","url","model"],"mappings":"AAAAA,UAAW,WAKP,GAAIC,GAAQC,SAASC,MAAMC,QACzBC,QAAS,gBAGPC,EAASJ,SAASK,WAAWH,QAC/BI,IAAK,cAELC,MAAOR,GAIb,QACIA,MAAOA,EACPK,OAAQA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"group.model.js","sources":["../../../src/mvc/groups/group.model.js"],"names":["define","Group","Backbone","Model","extend","urlRoot","Groups","Collection","url","model"],"mappings":"AAAAA,UAAW,WAKP,GAAIC,GAAQC,SAASC,MAAMC,QACzBC,QAAS,iBAGPC,EAASJ,SAASK,WAAWH,QAC/BI,IAAK,cAELC,MAAOR,GAIb,QACIA,MAAOA,EACPK,OAAQA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"toolshed.groups.js","sources":["../src/toolshed.groups.js"],"names":["define","mod_group_list","mod_group_detail","ToolshedRouter","Backbone","Router","extend","routes",":group_id","ToolshedGroups","View","groupListView","groupDetailView","collection","initialize","window","globalTS","groups","this","ts_router","on","GroupListView","group_id","GroupDetailView","history","start","pushState"],"mappings":"AAIAA,QACQ,6BACA,+BACA,0BAEJ,SACIC,EACAC,GAMR,GAAIC,GAAiBC,SAASC,OAAOC,QACjCC,QACI,GAA4B,SAC5BC,YAA4B,gBAKhCC,EAAiBL,SAASM,KAAKJ,QAC/BK,cAAe,KACfC,gBAAiB,KACjBC,WAAY,KAEZC,WAAa,WACTC,OAAOC,SAASC,OAASC,KAEzBA,KAAKC,UAAY,GAAIhB,GACrBe,KAAKC,UAAUC,GAAI,eAAgB,WAC/BL,OAAOC,SAASC,OAAON,cAAgB,GAAIV,GAAeoB,gBAG9DH,KAAKC,UAAUC,GAAI,mBAAoB,SAAUE,GAC7CP,OAAOC,SAASC,OAAOL,gBAAkB,GAAIV,GAAiBqB,iBAAmBD,SAAUA,MAG/FlB,SAASoB,QAAQC,OAAOC,WAAW,MAI3C,QACIjB,eAAgBA"}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
define(["libs/toastr","mvc/groups/group-model"],function(a,b){var c=Backbone.View.extend({el:"#groups_element",options:{},app:null,initialize:function(a){this.options=_.extend(this.options,a),this.app=window.globalTS.groups,null!==this.app.collection?(this.model=this.app.collection.get(this.options.group_id),this.render()):this.fetchGroup()},fetchGroup:function(c){var d=this;this.options=_.extend(this.options,c),this.model=new b.Group({id:this.options.group_id}),this.model.fetch({success:function(a){console.log("received data: "),console.log(a),d.render()},error:function(b,c){a.error("undefined"!=typeof c.responseJSON?c.responseJSON.err_msg:"An error ocurred.")}})},render:function(){var a=this.templateRow();this.$el.html(a({group:this.model})),$("#center [data-toggle]").tooltip()},templateRow:function(){return _.template(["<div>",'<h3><%= group.get("name") %></h3>','<p class="" style="color:gray;">','A group of <%= group.get("members").length %> members with <%= group.get("repositories").length %> repositories and a total of <%= group.get("total_downloads") %> combined tool downloads.</p>',"<h3>Members</h3>",'<table class="grid table table-condensed">',"<thead>","<th>Name</th>","<th>Repositories</th>","<th>Registered</th>","</thead>","<tbody>",'<% _.each(group.get("members"), function(member) { %>',"<tr>","<td>","<%= member.username %>","</td>","<td>",'<a data-toggle="tooltip" data-placement="top" title="Repositories of <%= member.username %>" href="/repository/browse_repositories_by_user?user_id=<%= member.id %>&use_panels=true" id="<%= member.id %>"><%= member.user_repos_count %></a>',"</td>","<td>","<%= member.time_created %>","</td>","</tr>","<% }); %>","</tbody>","</table>","<h3>Repositories</h3>",'<table class="grid table table-condensed">',"<thead>","<th>Name</th>","<th>Description</th>","<th>Downloads</th>","<th>Owner</th>","<th>Created</th>","<th>Rating</th>","<th>Verified</th>","</thead>","<tbody>",'<% _.each(group.get("repositories"), function(repo) { %>',"<tr>","<td>",'<a data-toggle="tooltip" data-placement="top" title="Details of <%= repo.name %>" href="/view/<%= repo.owner %>/<%= repo.name %>" id="<%= repo.id %>"><%= repo.name %></a>',"</td>","<td>","<%= repo.description %>","</td>","<td>","<%= repo.times_downloaded %>","</td>","<td>","<%= repo.owner %>","</td>","<td>","<%= repo.time_created %>","</td>","<td>","<%= repo.ratings_mean %>","</td>","<td>","<%= repo.approved %>","</td>","</tr>","<% }); %>","</tbody>","</table>","</div>"].join(""))}});return{GroupDetailView:c}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-detail-view.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define([],function(){var a=Backbone.View.extend({events:{},initialize:function(a){this.render(a)},render:function(a){var b=this.templateRow();return this.setElement(b({group:a})),this.$el.show(),this},templateRow:function(){return _.template(["<div>TEST GROUP PAGE</div>"].join(""))}});return{GroupDetailView:a}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-groupdetail-view.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define([],function(){var a=Backbone.View.extend({events:{},initialize:function(a){this.render(a)},render:function(a){var b=this.templateRow();return this.setElement(b({group:a})),this.$el.show(),this},templateRow:function(){return _.template(['<tr class="" data-id="<%- group.get("id") %>">','<td><a href="/groups/<%= group.get("id") %>"><%= group.get("name") %></a></td>',"<td>description</td>",'<td><%= group.get("total_members") %></td>','<td><%= group.get("total_repos") %></td>',"</tr>"].join(""))}});return{GroupRowView:a}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-grouprow-view.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define(["libs/toastr","mvc/groups/group-model","mvc/groups/group-listrow-view"],function(a,b,c){var d=Backbone.View.extend({el:"#groups_element",defaults:{},initialize:function(c){this.options=_.defaults(this.options||{},this.defaults,c);var d=this;window.globalTS.groups.collection=new b.Groups,window.globalTS.groups.collection.fetch({success:function(a){console.log("received data: "),console.log(a),d.render()},error:function(b,c){a.error("undefined"!=typeof c.responseJSON?c.responseJSON.err_msg:"An error ocurred.")}})},fetch:function(){},render:function(a){this.options=_.extend(this.options,a),$(".tooltip").hide();var b=this.templateGroupsList();this.$el.html(b({length:10,order:"asc"})),this.renderRows(window.globalTS.groups.collection.models),$("#center [data-toggle]").tooltip(),$("#center").css("overflow","auto")},renderRows:function(a){for(var b=0;b<a.length;b++){var c=a[b];this.renderOne({group:c})}},renderOne:function(a){var b=new c.GroupListRowView(a);this.$el.find("#group_list_body").append(b.el)},templateGroupsList:function(){return tmpl_array=[],tmpl_array.push('<div id="groups">'),tmpl_array.push("</div>"),tmpl_array.push('<div class="groups_container table-responsive">'),tmpl_array.push("<% if(length === 0) { %>"),tmpl_array.push("<div>There are no groups yet.</div>"),tmpl_array.push("<% } else{ %>"),tmpl_array.push('<table class="grid table table-condensed">'),tmpl_array.push(" <thead>"),tmpl_array.push(' <th style="width:30%;"><a class="sort-groups-link" title="Click to reverse order" href="#">name</a> <span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>'),tmpl_array.push(' <th style="width:22%;">description</th>'),tmpl_array.push(' <th style="width:22%;">members</th> '),tmpl_array.push(' <th style="width:26%;">repositories</th>'),tmpl_array.push(" </thead>"),tmpl_array.push(' <tbody id="group_list_body">'),tmpl_array.push(" </tbody>"),tmpl_array.push("</table>"),tmpl_array.push("<% }%>"),tmpl_array.push("</div>"),_.template(tmpl_array.join(""))}});return{GroupListView:d}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-list-view.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define([],function(){var a=Backbone.View.extend({events:{},initialize:function(a){this.render(a.group)},render:function(a){var b=this.templateRow();return this.setElement(b({group:a})),this.$el.show(),this},templateRow:function(){return _.template(['<tr class="" data-id="<%- group.get("id") %>">','<td><a href="groups#/<%= group.get("id") %>"><%= group.get("name") %></a></td>',"<td>description</td>",'<td><%= group.get("total_members") %></td>','<td><%= group.get("total_repos") %></td>',"</tr>"].join(""))}});return{GroupListRowView:a}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-listrow-view.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define([],function(){var a=Backbone.Model.extend({urlRoot:"/api/groups"}),b=Backbone.Collection.extend({url:"/api/groups",model:a});return{Group:a,Groups:b}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group-model.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define([],function(){var a=Backbone.Model.extend({urlRoot:"/api/groups/"}),b=Backbone.Collection.extend({url:"/api/groups",model:a});return{Group:a,Groups:b}});
|
||||
//# sourceMappingURL=../../../maps/mvc/groups/group.model.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define(["mvc/groups/group-list-view","mvc/groups/group-detail-view","mvc/groups/group-model"],function(a,b){var c=Backbone.Router.extend({routes:{"":"groups",":group_id":"group_page"}}),d=Backbone.View.extend({groupListView:null,groupDetailView:null,collection:null,initialize:function(){window.globalTS.groups=this,this.ts_router=new c,this.ts_router.on("route:groups",function(){window.globalTS.groups.groupListView=new a.GroupListView}),this.ts_router.on("route:group_page",function(a){window.globalTS.groups.groupDetailView=new b.GroupDetailView({group_id:a})}),Backbone.history.start({pushState:!1})}});return{ToolshedGroups:d}});
|
||||
//# sourceMappingURL=../maps/toolshed.groups.js.map
|
||||
@@ -1761,6 +1761,7 @@ body{background:#fff;color:#000;margin:10px}body.full-content{overflow:hidden;ma
|
||||
#left,#left-border,#center,#right-border,#right{position:absolute;top:34px;bottom:0px;overflow:hidden;background:#fff}
|
||||
#left{left:0px;width:250px;z-index:200;border-right:solid #999 1px}
|
||||
#left-border{left:250px}
|
||||
.reports-panel-container{position:absolute;top:30px;bottom:10px;overflow:scroll;width:100%;padding:10px}
|
||||
#center{left:250px;right:250px;overflow:hidden;z-index:1}
|
||||
#right-border{right:250px}
|
||||
#right{width:250px;right:0px;z-index:200;border-left:solid #999 1px}
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../client/toolshed/scripts/
|
||||
@@ -0,0 +1,27 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="title()">Tool Errors</%def>
|
||||
|
||||
<h2>Tool Errors</h2>
|
||||
|
||||
<p>
|
||||
Internal Tool Error log
|
||||
</p>
|
||||
|
||||
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
<tr class="header">
|
||||
<td>Time</td>
|
||||
<td>Phase</td>
|
||||
<td>File</td>
|
||||
<td>Error</td>
|
||||
</tr>
|
||||
%for error in tool_errors:
|
||||
<tr>
|
||||
<td>${error['time']}</td>
|
||||
<td>${error['phase']}</td>
|
||||
<td>${error['file']}</td>
|
||||
<td>${error['error']}</td>
|
||||
</tr>
|
||||
%endfor
|
||||
</table>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
if kwargs.get( 'embedded', False ):
|
||||
# No inheritance - using only embeddable content (self.body)
|
||||
return None
|
||||
|
||||
if context.get('use_panels'):
|
||||
if context.get('webapp'):
|
||||
app_name = context.get('webapp')
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='tool_versions' )}" target="galaxy_main">Tool lineage</a></div>
|
||||
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='reload_tool' )}" target="galaxy_main">Reload a tool's configuration</a></div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="toolSectionPad"></div>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class='unified-panel-header-inner'>Reports</div>
|
||||
</div>
|
||||
<div class="page-container" style="padding: 10px;">
|
||||
<div class="page-container reports-panel-container">
|
||||
<div class="toolMenu">
|
||||
<div class="toolSectionList">
|
||||
<div class="toolSectionPad"></div>
|
||||
|
||||
@@ -80,6 +80,9 @@
|
||||
## Repositories tab.
|
||||
${tab( "repositories", "Repositories", h.url_for( controller='/repository', action='index' ) )}
|
||||
|
||||
## Groups tab.
|
||||
${tab( "groups", "Groups", h.url_for( controller='/groups', action='index' ) )}
|
||||
|
||||
## Admin tab.
|
||||
${tab( "admin", "Admin", h.url_for( controller='/admin', action='index' ), extra_class="admin-only", visible=( trans.user and app.config.is_admin_user( trans.user ) ) )}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<%inherit file="/webapps/tool_shed/base_panels.mako"/>
|
||||
<%namespace file="/message.mako" import="render_msg" />
|
||||
|
||||
<%def name="stylesheets()">
|
||||
## Include "base.css" for styling tool menu and forms (details)
|
||||
${h.css( "base", "autocomplete_tagging", "tool_menu" )}
|
||||
|
||||
## But make sure styles for the layout take precedence
|
||||
${parent.stylesheets()}
|
||||
|
||||
<style type="text/css">
|
||||
body { margin: 0; padding: 0; overflow: hidden; }
|
||||
#left {
|
||||
background: #C1C9E5 url(${h.url_for('/static/style/menu_bg.png')}) top repeat-x;
|
||||
}
|
||||
.unified-panel-body {
|
||||
overflow: auto;
|
||||
}
|
||||
.toolMenu {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="javascripts()">
|
||||
${parent.javascripts()}
|
||||
</%def>
|
||||
|
||||
<%def name="init()">
|
||||
%if trans.app.config.require_login and not trans.user:
|
||||
<script type="text/javascript">
|
||||
if ( window != top ) {
|
||||
top.location.href = location.href;
|
||||
}
|
||||
</script>
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="center_panel()">
|
||||
<script type="text/javascript">
|
||||
window.globalTS = new Object();
|
||||
$( function(){
|
||||
require([ '${config.get( "app" ).get( "jscript" )}' ], function( groups ){
|
||||
app = new groups.ToolshedGroups();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<div id="groups_element" style="width: 95%; margin:auto; margin-top:2em; "></div>
|
||||
</%def>
|
||||
Reference in New Issue
Block a user