targets.fadeToggle(200);
});
+ var ajaxCount = 0;
+
$(document).
on('ajax:send', function(e, xhr) {
- $('.loading').fadeTo('fast', 1);
+ ajaxCount += 1;
+ if (ajaxCount == 1) {
+ $('.loading').fadeTo('fast', 1);
+ }
}).
on('ajax:complete', function(e, status) {
- $('.loading').fadeOut('fast', 0);
+ ajaxCount -= 1;
+ if (ajaxCount == 0) {
+ $('.loading').fadeOut('fast', 0);
+ }
}).
on('click', '.removable-tag a', function(e) {
var tag_span = $(this).parents('[data-tag-link-uuid]').eq(0)
$('.btn').button();
});
+ $(document).
+ on('ready ajax:complete', function() {
+ $('[data-toggle~=tooltip]').tooltip({container:'body'});
+ });
+
HeaderRowFixer = function(selector) {
this.duplicateTheadTr = function() {
$(selector).each(function() {
$(document).
on('ready ajax:complete', function() {
- $('#editable-submit').click(function() {
- console.log($(this));
- });
$('.editable').
+ not('.editable-done-setup').
+ addClass('editable-done-setup').
editable({
success: function(response, newValue) {
// If we just created a new object, stash its UUID
$(this).editable('option', 'url', response.href);
}
return;
+ },
+ error: function(response, newValue) {
+ return response.responseJSON.errors.join();
}
}).
on('hidden', function(e, reason) {
});
}
});
+ }).
+ on('ready ajax:complete', function() {
+ $("[data-toggle~='x-editable']").
+ not('.editable-done-setup').
+ addClass('editable-done-setup').
+ click(function(e) {
+ e.stopPropagation();
+ $($(this).attr('data-toggle-selector')).editable('toggle');
+ });
});
$.fn.editabletypes.text.defaults.tpl = '<input type="text" name="editable-text">'
function run_pipeline_button_state() {
- var a = $('a.editable.required.editable-empty');
+ var a = $('a.editable.required.editable-empty,input.form-control.required[value=]');
if (a.length > 0) {
$(".run-pipeline-button").addClass("disabled");
}
run_pipeline_button_state();
});
- $(document).on('ajax:complete ready', function() {
- var a = $('.arv-log-event-listener');
- if (a.length > 0) {
- $('.arv-log-event-listener').each(function() {
- subscribeToEventLog(this.id);
- });
- }
- });
-
$(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(event, eventData){
- var parsedData = JSON.parse(eventData);
+ var wasatbottom = ($(this).scrollTop() + $(this).height() >=
+ this.scrollHeight);
+ var parsedData = JSON.parse(eventData);
+ var propertyText = undefined;
+ var properties = parsedData.properties;
- var propertyText = undefined
-
- var properties = parsedData.properties;
if (properties !== null) {
- propertyText = properties.text;
+ propertyText = properties.text;
}
-
if (propertyText !== undefined) {
- $(this).append(propertyText + "<br/>");
+ $(this).append(propertyText + "<br/>");
} else {
- $(this).append(parsedData.summary + "<br/>");
+ $(this).append(parsedData.summary + "<br/>");
}
+ if (wasatbottom)
+ this.scrollTop = this.scrollHeight;
+}).on('ready ajax:complete', function(){
+ $('.arv-log-event-handler-append-logs').each(function() {
+ this.scrollTop = this.scrollHeight;
+ });
});
+
+var showhide_compare = function() {
+ var form = $('form#compare')[0];
+ $('input[type=hidden][name="uuids[]"]', form).remove();
+ $('input[type=submit]', form).prop('disabled',true).show();
+ var checked_inputs = $('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]:checked');
+ if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
+ checked_inputs.each(function(){
+ if(this.checked) {
+ $('input[type=submit]', form).prop('disabled',false).show();
+ $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
+ }
+ });
+ }
+};
+$('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]').on('click', showhide_compare);
+showhide_compare();
class ApplicationController < ActionController::Base
include ArvadosApiClientHelper
+ include ApplicationHelper
respond_to :html, :json, :js
protect_from_forgery
self.render_error status: 404
end
- def render_index
- respond_to do |f|
- f.json { render json: @objects }
- f.html {
- if params['tab_pane']
- comparable = self.respond_to? :compare
- render(partial: 'show_' + params['tab_pane'].downcase,
- locals: { comparable: comparable, objects: @objects })
- else
- render
- end
- }
- f.js { render }
- end
- end
-
- def index
+ def find_objects_for_index
@limit ||= 200
if params[:limit]
@limit = params[:limit].to_i
end
@objects ||= model_class
- @objects = @objects.filter(@filters).limit(@limit).offset(@offset).all
+ @objects = @objects.filter(@filters).limit(@limit).offset(@offset)
+ end
+
++ def render_index
++ respond_to do |f|
++ f.json { render json: @objects }
++ f.html {
++ if params['tab_pane']
++ comparable = self.respond_to? :compare
++ render(partial: 'show_' + params['tab_pane'].downcase,
++ locals: { comparable: comparable, objects: @objects })
++ else
++ render
++ end
++ }
++ f.js { render }
++ end
++ end
++
++ def index
++ find_objects_for_index if !@objects
+ render_index
+ end
+
+ helper_method :next_page_offset
+ def next_page_offset
+ if @objects.respond_to?(:result_offset) and
+ @objects.respond_to?(:result_limit) and
+ @objects.respond_to?(:items_available)
+ next_offset = @objects.result_offset + @objects.result_limit
+ if next_offset < @objects.items_available
+ next_offset
+ else
+ nil
+ end
+ end
+ end
+
- def index
- find_objects_for_index if !@objects
- respond_to do |f|
- f.json { render json: @objects }
- f.html { render }
- f.js { render }
- end
- end
-
def show
if !@object
return render_not_found("object not found")
respond_to do |f|
f.json { render json: @object.attributes.merge(href: url_for(@object)) }
f.html {
- if request.method.in? ['GET', 'HEAD']
+ if params['tab_pane']
+ comparable = self.respond_to? :compare
+ render(partial: 'show_' + params['tab_pane'].downcase,
+ locals: { comparable: comparable, objects: @objects })
++ elsif request.method.in? ['GET', 'HEAD']
+ render
else
- if request.method == 'GET'
- render
- else
- redirect_to params[:return_to] || @object
- end
+ redirect_to params[:return_to] || @object
end
}
f.js { render }
end
end
+ def choose
+ params[:limit] ||= 20
+ find_objects_for_index if !@objects
+ respond_to do |f|
+ if params[:partial]
+ f.json {
+ render json: {
+ content: render_to_string(partial: "choose_rows.html",
+ formats: [:html],
+ locals: {
+ multiple: params[:multiple]
+ }),
+ next_page_href: @next_page_href
+ }
+ }
+ end
+ f.js {
+ render partial: 'choose', locals: {multiple: params[:multiple]}
+ }
+ end
+ end
+
def render_content
if !@object
return render_not_found("object not found")
end
def update
- @updates ||= params[@object.class.to_s.underscore.singularize.to_sym]
+ @updates ||= params[@object.resource_param_name.to_sym]
@updates.keys.each do |attr|
if @object.send(attr).is_a? Hash
if @updates[attr].is_a? String
@new_resource_attrs ||= params[model_class.to_s.underscore.singularize]
@new_resource_attrs ||= {}
@new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' }
- @object ||= model_class.new @new_resource_attrs
- @object.save!
- show
+ @object ||= model_class.new @new_resource_attrs, params["options"]
+ if @object.save
+ respond_to do |f|
+ f.json { render json: @object.attributes.merge(href: url_for(@object)) }
+ f.html {
+ redirect_to @object
+ }
+ f.js { render }
+ end
+ else
+ self.render_error status: 422
+ end
end
+ # Clone the given object, merging any attribute values supplied as
+ # with a create action.
+ def copy
+ @new_resource_attrs ||= params[model_class.to_s.underscore.singularize]
+ @new_resource_attrs ||= {}
+ @object = @object.dup
+ @object.update_attributes @new_resource_attrs
+ if not @new_resource_attrs[:name] and @object.respond_to? :name
+ if @object.name and @object.name != ''
+ @object.name = "Copy of #{@object.name}"
+ else
+ @object.name = "Copy of unnamed #{@object.class_for_display.downcase}"
+ end
+ end
+ @object.save!
+ show
+ end
+
def destroy
if @object.destroy
respond_to do |f|
end
def current_user
+ return Thread.current[:user] if Thread.current[:user]
+
if Thread.current[:arvados_api_token]
- Thread.current[:user] ||= User.current
+ if session[:user]
+ if session[:user][:is_active] != true
+ Thread.current[:user] = User.current
+ else
+ Thread.current[:user] = User.new(session[:user])
+ end
+ else
+ Thread.current[:user] = User.current
+ end
else
logger.error "No API token in Thread"
return nil
end
def show_pane_list
- %w(Attributes Metadata JSON API)
+ %w(Attributes Advanced)
end
protected
def redirect_to_login
respond_to do |f|
f.html {
- if request.method == 'GET'
+ if request.method.in? ['GET', 'HEAD']
redirect_to arvados_api_client.arvados_login_url(return_to: request.url)
else
flash[:error] = "Either you are not logged in, or your session has timed out. I can't automatically log you in and re-attempt this request."
if params[:uuid].empty?
@object = nil
else
- @object = model_class.find(params[:uuid])
+ if (model_class != Link and
+ resource_class_for_uuid(params[:uuid]) == Link)
+ @name_link = Link.find(params[:uuid])
+ @object = model_class.find(@name_link.head_uuid)
+ else
+ @object = model_class.find(params[:uuid])
+ end
end
else
@object = model_class.where(uuid: params[:uuid]).first
# call to verify its authenticity.
if verify_api_token
session[:arvados_api_token] = params[:api_token]
- if !request.format.json? and request.method == 'GET'
+ u = User.current
+ session[:user] = {
+ uuid: u.uuid,
+ email: u.email,
+ first_name: u.first_name,
+ last_name: u.last_name,
+ is_active: u.is_active,
+ is_admin: u.is_admin,
+ prefs: u.prefs
+ }
+ if !request.format.json? and request.method.in? ['GET', 'HEAD']
# Repeat this request with api_token in the (new) session
# cookie instead of the query string. This prevents API
# tokens from appearing in (and being inadvisedly copied
end
def thread_with_mandatory_api_token
- thread_with_api_token do
- yield
+ thread_with_api_token(true) do
+ if Thread.current[:arvados_api_token]
+ yield
+ elsif session[:arvados_api_token]
+ # Expired session. Clear it before refreshing login so that,
+ # if this login procedure fails, we end up showing the "please
+ # log in" page instead of getting stuck in a redirect loop.
+ session.delete :arvados_api_token
+ redirect_to_login
+ else
+ render 'users/welcome'
+ end
end
end
end
def check_user_agreements
- if current_user && !current_user.is_active && current_user.is_invited
+ if current_user && !current_user.is_active
+ if not current_user.is_invited
+ return render 'users/inactive'
+ end
signatures = UserAgreement.signatures
@signed_ua_uuids = UserAgreement.signatures.map &:head_uuid
@required_user_agreements = UserAgreement.all.map do |ua|
}
def check_user_notifications
+ return if params['tab_pane']
+
@notification_count = 0
@notifications = []
end
end
- helper_method :my_folders
- def my_folders
- return @my_folders if @my_folders
- @my_folders = []
+ helper_method :all_projects
+ def all_projects
+ @all_projects ||= Group.filter([['group_class','in',['project','folder']]])
+ end
+
+ helper_method :my_projects
+ def my_projects
+ return @my_projects if @my_projects
+ @my_projects = []
root_of = {}
- Group.filter([['group_class','=','folder']]).each do |g|
+ all_projects.each do |g|
root_of[g.uuid] = g.owner_uuid
- @my_folders << g
+ @my_projects << g
end
done = false
while not done
end
end
end
- @my_folders = @my_folders.select do |g|
+ @my_projects = @my_projects.select do |g|
root_of[g.uuid] == current_user.uuid
end
end
+ helper_method :projects_shared_with_me
+ def projects_shared_with_me
+ my_project_uuids = my_projects.collect &:uuid
+ all_projects.reject { |x| x.uuid.in? my_project_uuids }
+ end
+
+ helper_method :recent_jobs_and_pipelines
+ def recent_jobs_and_pipelines
+ in_my_projects = ['owner_uuid','in',my_projects.collect(&:uuid)]
+ (Job.limit(10).filter([in_my_projects]) |
+ PipelineInstance.limit(10).filter([in_my_projects])).
+ sort_by do |x|
+ x.finished_at || x.started_at || x.created_at rescue x.created_at
+ end
+ end
+
+ helper_method :get_object
+ def get_object uuid
+ if @get_object.nil? and @objects
+ @get_object = @objects.each_with_object({}) do |object, h|
+ h[object.uuid] = object
+ end
+ end
+ @get_object ||= {}
+ @get_object[uuid]
+ end
+
+ helper_method :project_breadcrumbs
+ def project_breadcrumbs
+ crumbs = []
+ current = @name_link || @object
+ while current
+ if current.is_a?(Group) and current.group_class.in?(['project','folder'])
+ crumbs.prepend current
+ end
+ if current.is_a? Link
+ current = Group.find?(current.tail_uuid)
+ else
+ current = Group.find?(current.owner_uuid)
+ end
+ end
+ crumbs
+ end
+
+ helper_method :current_project_uuid
+ def current_project_uuid
+ if @object.is_a? Group and @object.group_class.in?(['project','folder'])
+ @object.uuid
+ elsif @name_link.andand.tail_uuid
+ @name_link.tail_uuid
+ elsif @object and resource_class_for_uuid(@object.owner_uuid) == Group
+ @object.owner_uuid
+ else
+ nil
+ end
+ end
++
+ # helper method to get links for given object or uuid
+ helper_method :links_for_object
+ def links_for_object object_or_uuid
+ raise ArgumentError, 'No input argument' unless object_or_uuid
+ preload_links_for_objects([object_or_uuid])
+ uuid = object_or_uuid.is_a?(String) ? object_or_uuid : object_or_uuid.uuid
+ @all_links_for[uuid] ||= []
+ end
+
+ # helper method to preload links for given objects and uuids
+ helper_method :preload_links_for_objects
+ def preload_links_for_objects objects_and_uuids
+ @all_links_for ||= {}
+
+ raise ArgumentError, 'Argument is not an array' unless objects_and_uuids.is_a? Array
+ return @all_links_for if objects_and_uuids.empty?
+
+ uuids = objects_and_uuids.collect { |x| x.is_a?(String) ? x : x.uuid }
+
+ # if already preloaded for all of these uuids, return
+ if not uuids.select { |x| @all_links_for[x].nil? }.any?
+ return @all_links_for
+ end
+
+ uuids.each do |x|
+ @all_links_for[x] = []
+ end
+
+ # TODO: make sure we get every page of results from API server
+ Link.filter([['head_uuid', 'in', uuids]]).each do |link|
+ @all_links_for[link.head_uuid] << link
+ end
+ @all_links_for
+ end
+
+ # helper method to get a certain number of objects of a specific type
+ # this can be used to replace any uses of: "dataclass.limit(n)"
+ helper_method :get_n_objects_of_class
+ def get_n_objects_of_class dataclass, size
+ @objects_map_for ||= {}
+
+ raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class
+ raise ArgumentError, 'Argument is not a valid limit size' unless (size && size>0)
+
+ # if the objects_map_for has a value for this dataclass, and the
+ # size used to retrieve those objects is equal, return it
+ size_key = "#{dataclass.name}_size"
+ if @objects_map_for[dataclass.name] && @objects_map_for[size_key] &&
+ (@objects_map_for[size_key] == size)
+ return @objects_map_for[dataclass.name]
+ end
+
+ @objects_map_for[size_key] = size
+ @objects_map_for[dataclass.name] = dataclass.limit(size)
+ end
+
+ # helper method to get collections for the given uuid
+ helper_method :collections_for_object
+ def collections_for_object uuid
+ raise ArgumentError, 'No input argument' unless uuid
+ preload_collections_for_objects([uuid])
+ @all_collections_for[uuid] ||= []
+ end
+
+ # helper method to preload collections for the given uuids
+ helper_method :preload_collections_for_objects
+ def preload_collections_for_objects uuids
+ @all_collections_for ||= {}
+
+ raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array
+ return @all_collections_for if uuids.empty?
+
+ # if already preloaded for all of these uuids, return
+ if not uuids.select { |x| @all_collections_for[x].nil? }.any?
+ return @all_collections_for
+ end
+
+ uuids.each do |x|
+ @all_collections_for[x] = []
+ end
+
+ # TODO: make sure we get every page of results from API server
+ Collection.where(uuid: uuids).each do |collection|
+ @all_collections_for[collection.uuid] << collection
+ end
+ @all_collections_for
+ end
+
+ # helper method to get log collections for the given log
+ helper_method :log_collections_for_object
+ def log_collections_for_object log
+ raise ArgumentError, 'No input argument' unless log
+
+ preload_log_collections_for_objects([log])
+
+ uuid = log
+ fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log)
+ if fixup && fixup.size>1
+ uuid = fixup[1]
+ end
+
+ @all_log_collections_for[uuid] ||= []
+ end
+
+ # helper method to preload collections for the given uuids
+ helper_method :preload_log_collections_for_objects
+ def preload_log_collections_for_objects logs
+ @all_log_collections_for ||= {}
+
+ raise ArgumentError, 'Argument is not an array' unless logs.is_a? Array
+ return @all_log_collections_for if logs.empty?
+
+ uuids = []
+ logs.each do |log|
+ fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log)
+ if fixup && fixup.size>1
+ uuids << fixup[1]
+ else
+ uuids << log
+ end
+ end
+
+ # if already preloaded for all of these uuids, return
+ if not uuids.select { |x| @all_log_collections_for[x].nil? }.any?
+ return @all_log_collections_for
+ end
+
+ uuids.each do |x|
+ @all_log_collections_for[x] = []
+ end
+
+ # TODO: make sure we get every page of results from API server
+ Collection.where(uuid: uuids).each do |collection|
+ @all_log_collections_for[collection.uuid] << collection
+ end
+ @all_log_collections_for
+ end
+
+ # helper method to get object of a given dataclass and uuid
+ helper_method :object_for_dataclass
+ def object_for_dataclass dataclass, uuid
+ raise ArgumentError, 'No input argument dataclass' unless (dataclass && uuid)
+ preload_objects_for_dataclass(dataclass, [uuid])
+ @objects_for[uuid]
+ end
+
+ # helper method to preload objects for given dataclass and uuids
+ helper_method :preload_objects_for_dataclass
+ def preload_objects_for_dataclass dataclass, uuids
+ @objects_for ||= {}
+
+ raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class
+ raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array
+
+ return @objects_for if uuids.empty?
+
+ # if already preloaded for all of these uuids, return
+ if not uuids.select { |x| @objects_for[x].nil? }.any?
+ return @objects_for
+ end
+
+ dataclass.where(uuid: uuids).each do |obj|
+ @objects_for[obj.uuid] = obj
+ end
+ @objects_for
+ end
+
end
only: [:show_file, :show_file_links])
skip_before_filter(:find_object_by_uuid,
only: [:provenance, :show_file, :show_file_links])
+ # We depend on show_file to display the user agreement:
+ skip_before_filter :check_user_agreements, only: [:show_file]
RELATION_LIMIT = 5
def show_pane_list
- %w(Files Attributes Metadata Provenance_graph Used_by JSON API)
+ %w(Files Provenance_graph Used_by Advanced)
end
def set_persistent
end
end
+ def choose
+ params[:limit] ||= 20
+ @objects = Link.
+ filter([['link_class','=','name'],
+ ['head_uuid','is_a','arvados#collection']])
+ find_objects_for_index
+ @next_page_href = (next_page_offset and
+ url_for(offset: next_page_offset, partial: true))
+ @name_links = @objects
+ @objects = Collection.
+ filter([['uuid','in',@name_links.collect(&:head_uuid)]])
+ super
+ end
+
def index
if params[:search].andand.length.andand > 0
tags = Link.where(any: ['contains', params[:search]])
info[:links] << link
end
@request_url = request.url
+
+ render_index
end
def show_file_links
end
@output_of = jobs_with.call(output: @object.uuid)
@log_of = jobs_with.call(log: @object.uuid)
- project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
- @folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
++ @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
.where(head_uuid: @object.uuid, link_class: 'name').results
- project_hash = Group.where(uuid: project_links.map(&:tail_uuid)).to_hash
- @projects = project_links.map { |link| project_hash[link.tail_uuid] }
- folder_hash = Group.where(uuid: @folder_links.map(&:tail_uuid)).to_hash
- @folders = @folder_links.map { |link| folder_hash[link.tail_uuid] }
++ project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash
++ @projects = @project_links.map { |link| project_hash[link.tail_uuid] }
@permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
.where(head_uuid: @object.uuid, link_class: 'permission',
name: 'can_read').results
:direction => :top_down,
:combine_jobs => :script_only,
:pdata_only => true}) rescue nil
+
+ super
end
def sharing_popup
class GroupsController < ApplicationController
def index
- @groups = Group.filter [['group_class', 'not in', ['folder']]]
+ @groups = Group.filter [['group_class', 'not in', ['folder', 'project']]]
@group_uuids = @groups.collect &:uuid
@links_from = Link.where link_class: 'permission', tail_uuid: @group_uuids
@links_to = Link.where link_class: 'permission', head_uuid: @group_uuids
+ render_index
end
def show
- return redirect_to(folder_path(@object)) if @object.group_class == 'folder'
- super
+ if @object.group_class.in?(['project','folder'])
+ redirect_to(project_path(@object))
+ else
+ super
+ end
end
end
class JobsController < ApplicationController
def generate_provenance(jobs)
+ return if params['tab_pane'] != "Provenance"
+
nodes = []
collections = []
jobs.each do |j|
@svg = ProvenanceHelper::create_provenance_graph nodes, "provenance_svg", {
:request => request,
- :all_script_parameters => true,
+ :all_script_parameters => true,
:script_version_nodes => true}
end
if params[:uuid]
@objects = Job.where(uuid: params[:uuid])
generate_provenance(@objects)
+ render_index
else
@limit = 20
super
end
end
+ def cancel
+ @object.cancel
+ redirect_to @object
+ end
+
def show
generate_provenance([@object])
+ super
end
def index_pane_list
end
def show_pane_list
- %w(Details Provenance Advanced)
- %w(Status Attributes Provenance Metadata JSON API)
++ %w(Status Details Provenance Advanced)
end
end
before_filter :find_objects_by_uuid, only: :compare
include PipelineInstancesHelper
+ def copy
+ @object = @object.dup
+ @object.components.each do |cname, component|
+ component.delete :job
+ end
+ @object.state = 'New'
+ super
+ end
+
+ def update
+ @updates ||= params[@object.class.to_s.underscore.singularize.to_sym]
+ if (components = @updates[:components])
+ components.each do |cname, component|
+ if component[:script_parameters]
+ component[:script_parameters].each do |param, value_info|
+ if value_info.is_a? Hash
+ if resource_class_for_uuid(value_info[:value]) == Link
+ # Use the link target, not the link itself, as script
+ # parameter; but keep the link info around as well.
+ link = Link.find value_info[:value]
+ value_info[:value] = link.head_uuid
+ value_info[:link_uuid] = link.uuid
+ value_info[:link_name] = link.name
+ else
+ # Delete stale link_uuid and link_name data.
+ value_info[:link_uuid] = nil
+ value_info[:link_name] = nil
+ end
+ end
+ end
+ end
+ end
+ end
+ super
+ end
+
def graph(pipelines)
- count = {}
+ return nil, nil if params['tab_pane'] != "Graph"
+
+ count = {}
provenance = {}
pips = {}
n = 1
pips[uuid] = 0 unless pips[uuid] != nil
pips[uuid] |= n
end
-
+
n = n << 1
end
end
provenance, pips = graph(@pipelines)
+ if provenance
+ @prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
+ :request => request,
+ :all_script_parameters => true,
+ :combine_jobs => :script_and_version,
+ :script_version_nodes => true,
+ :pips => pips }
+ end
- @prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
- :request => request,
- :all_script_parameters => true,
- :combine_jobs => :script_and_version,
- :script_version_nodes => true,
- :pips => pips }
super
end
@prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
:request => request,
- :all_script_parameters => true,
+ :all_script_parameters => true,
:combine_jobs => :script_and_version,
:script_version_nodes => true,
:pips => pips }
+ @object = @objects.first
end
def show_pane_list
- panes = %w(Components Graph Attributes Metadata JSON API)
+ panes = %w(Components Graph Advanced)
if @object and @object.state.in? ['New', 'Ready']
panes = %w(Inputs) + panes
end
+ if not @object.components.values.collect { |x| x[:job] }.compact.any?
+ panes -= ['Graph']
+ end
panes
end
- def compare_pane_list
+ def compare_pane_list
%w(Compare Graph)
- end
+ end
def index
@limit = 20
#
def link_to_if_arvados_object(attrvalue, opts={}, style_opts={})
if (resource_class = resource_class_for_uuid(attrvalue, opts))
- link_uuid = attrvalue.is_a?(ArvadosBase) ? attrvalue.uuid : attrvalue
+ if attrvalue.is_a? ArvadosBase
+ object = attrvalue
+ link_uuid = attrvalue.uuid
+ else
+ object = nil
+ link_uuid = attrvalue
+ end
link_name = opts[:link_text]
if !link_name
- link_name = link_uuid
+ link_name = object.andand.default_name || resource_class.default_name
if opts[:friendly_name]
if attrvalue.respond_to? :friendly_link_name
link_name = attrvalue.friendly_link_name
else
begin
- link_name = resource_class.find(link_uuid).friendly_link_name
+ if resource_class.name == 'Collection'
+ link_name = collections_for_object(link_uuid).andand.first.andand.friendly_link_name
+ else
+ link_name = object_for_dataclass(resource_class, link_uuid).andand.friendly_link_name
+ end
rescue RuntimeError
# If that lookup failed, the link will too. So don't make one.
return attrvalue
link_name = "#{resource_class.to_s}: #{link_name}"
end
if !opts[:no_tags] and resource_class == Collection
- Link.where(head_uuid: link_uuid, link_class: ["tag", "identifier"]).each do |tag|
- link_name += ' <span class="label label-info">' + html_escape(tag.name) + '</span>'
+ links_for_object(link_uuid).each do |tag|
+ if tag.link_class.in? ["tag", "identifier"]
+ link_name += ' <span class="label label-info">' + html_escape(tag.name) + '</span>'
+ end
end
end
if opts[:thumbnail] and resource_class == Collection
# add an image thumbnail if the collection consists of a single image file.
- Collection.where(uuid: link_uuid).each do |c|
+ collections_for_object(link_uuid).each do |c|
if c.files.length == 1 and CollectionsHelper::is_image c.files.first[1]
link_name += " "
link_name += image_tag "#{url_for c}/#{CollectionsHelper::file_path c.files.first}", style: "height: 4em; width: auto"
if opts[:no_link]
raw(link_name)
else
- link_to raw(link_name), { controller: resource_class.to_s.tableize, action: 'show', id: link_uuid }, style_opts
+ link_to raw(link_name), { controller: resource_class.to_s.tableize, action: 'show', id: ((opts[:name_link].andand.uuid) || link_uuid) }, style_opts
end
else
# just return attrvalue if it is not recognizable as an Arvados object or uuid.
attrvalue = object.send(attr) if attrvalue.nil?
if !object.attribute_editable?(attr, :ever) or
(!object.editable? and
- !object.owner_uuid.in?(my_folders.collect(&:uuid)))
- return attrvalue
+ !object.owner_uuid.in?(my_projects.collect(&:uuid)))
+ return ((attrvalue && attrvalue.length > 0 && attrvalue) ||
+ (attr == 'name' and object.andand.default_name) ||
+ '(none)')
end
input_type = 'text'
ajax_options['data-pk'][:defaults] = object.attributes
end
ajax_options['data-pk'] = ajax_options['data-pk'].to_json
+ @unique_id ||= (Time.now.to_f*1000000).to_i
+ span_id = object.uuid.to_s + '-' + attr.to_s + '-' + (@unique_id += 1).to_s
- content_tag 'span', attrvalue.to_s, {
- "data-emptytext" => "none",
+ span_tag = content_tag 'span', attrvalue.to_s, {
+ "data-emptytext" => (object.andand.default_name || 'none'),
"data-placement" => "bottom",
"data-type" => input_type,
- "data-title" => "Update #{attr.gsub '_', ' '}",
+ "data-title" => "Edit #{attr.gsub '_', ' '}",
"data-name" => attr,
"data-object-uuid" => object.uuid,
+ "data-toggle" => "manual",
+ "id" => span_id,
:class => "editable"
}.merge(htmloptions).merge(ajax_options)
+ edit_button = raw('<a href="#" class="btn btn-xs btn-default btn-nodecorate" data-toggle="x-editable tooltip" data-toggle-selector="#' + span_id + '" data-placement="top" title="' + (htmloptions[:tiptitle] || 'edit') + '"><i class="fa fa-fw fa-pencil"></i></a>')
+ if htmloptions[:btnplacement] == :left
+ edit_button + ' ' + span_tag
+ else
+ span_tag + ' ' + edit_button
+ end
end
def render_pipeline_component_attribute(object, attr, subattr, value_info, htmloptions={})
if !object or
!object.attribute_editable?(attr, :ever) or
(!object.editable? and
- !object.owner_uuid.in?(my_folders.collect(&:uuid)))
+ !object.owner_uuid.in?(my_projects.collect(&:uuid)))
return link_to_if_arvados_object attrvalue
end
dataclass = ArvadosBase.resource_class_for_uuid(attrvalue)
end
+ id = "#{object.uuid}-#{subattr.join('-')}"
+ dn = "[#{attr}]"
+ subattr.each do |a|
+ dn += "[#{a}]"
+ end
+ if value_info.is_a? Hash
+ dn += '[value]'
+ end
+
+ if dataclass == Collection
+ selection_param = object.class.to_s.underscore + dn
+ display_value = attrvalue
+ if value_info.is_a?(Hash)
+ if (link = Link.find? value_info[:link_uuid])
+ display_value = link.name
+ elsif value_info[:link_name]
+ display_value = value_info[:link_name]
+ end
+ end
+ modal_path = choose_collections_path \
+ ({ title: 'Choose a dataset:',
+ filters: [['tail_uuid', '=', object.owner_uuid]].to_json,
+ action_name: 'OK',
+ action_href: pipeline_instance_path(id: object.uuid),
+ action_method: 'patch',
+ action_data: {
+ merge: true,
+ selection_param: selection_param,
+ success: 'page-refresh'
+ }.to_json,
+ })
+ return content_tag('div', :class => 'input-group') do
+ html = text_field_tag(dn, display_value,
+ :class =>
+ "form-control #{'required' if required}")
+ html + content_tag('span', :class => 'input-group-btn') do
+ link_to('Choose',
+ modal_path,
+ { :class => "btn btn-primary",
+ :remote => true,
+ :method => 'get',
+ })
+ end
+ end
+ end
+
if dataclass.andand.is_a?(Class)
datatype = 'select'
elsif dataclass == 'number'
datatype = 'text'
end
- id = "#{object.uuid}-#{subattr.join('-')}"
- dn = "[#{attr}]"
- subattr.each do |a|
- dn += "[#{a}]"
- end
- if value_info.is_a? Hash
- dn += '[value]'
- end
-
+ # preload data
+ preload_uuids = []
+ items = []
selectables = []
+
attrtext = attrvalue
if dataclass and dataclass.is_a? Class
+ objects = get_n_objects_of_class dataclass, 10
+ objects.each do |item|
+ items << item
+ preload_uuids << item.uuid
+ end
if attrvalue and !attrvalue.empty?
- Link.where(head_uuid: attrvalue, link_class: ["tag", "identifier"]).each do |tag|
- attrtext += " [#{tag.name}]"
+ preload_uuids << attrvalue
+ end
+ preload_links_for_objects preload_uuids
+
+ if attrvalue and !attrvalue.empty?
+ links_for_object(attrvalue).each do |link|
+ if link.link_class.in? ["tag", "identifier"]
+ attrtext += " [#{link.name}]"
+ end
end
selectables.append({name: attrtext, uuid: attrvalue, type: dataclass.to_s})
end
- #dataclass.where(uuid: attrvalue).each do |item|
- # selectables.append({name: item.uuid, uuid: item.uuid, type: dataclass.to_s})
- #end
itemuuids = []
- dataclass.limit(10).each do |item|
+ items.each do |item|
itemuuids << item.uuid
selectables.append({name: item.uuid, uuid: item.uuid, type: dataclass.to_s})
end
- Link.where(head_uuid: itemuuids, link_class: ["tag", "identifier"]).each do |tag|
- selectables.each do |selectable|
- if selectable['uuid'] == tag.head_uuid
- selectable['name'] += ' [' + tag.name + ']'
+
+ itemuuids.each do |itemuuid|
+ links_for_object(itemuuid).each do |link|
+ if link.link_class.in? ["tag", "identifier"]
+ selectables.each do |selectable|
+ if selectable['uuid'] == link.head_uuid
+ selectable['name'] += ' [' + link.name + ']'
+ end
+ end
end
end
end
"data-title" => "Set value for #{subattr[-1].to_s}",
"data-name" => dn,
"data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
- "data-showbuttons" => "false",
"data-value" => attrvalue,
+ # "clear" button interferes with form-control's up/down arrows
+ "data-clear" => false,
:class => "editable #{'required' if required} form-control",
:id => id
}.merge(htmloptions)
button_href, params, *rest)
end
end
+
+ def render_controller_partial partial, opts
+ cname = opts.delete :controller_name
+ begin
+ render opts.merge(partial: "#{cname}/#{partial}")
+ rescue ActionView::MissingTemplate
+ render opts.merge(partial: "application/#{partial}")
+ end
+ end
+
+ def fa_icon_class_for_object object
+ case object.class.to_s.to_sym
+ when :User
+ 'fa-user'
+ when :Group
+ object.group_class ? 'fa-folder' : 'fa-users'
+ when :Job, :PipelineInstance, :PipelineTemplate
+ 'fa-gears'
+ when :Collection
+ 'fa-archive'
+ when :Specimen
+ 'fa-flask'
+ when :Trait
+ 'fa-clipboard'
+ when :Human
+ 'fa-male'
+ when :VirtualMachine
+ 'fa-terminal'
+ when :Repository
+ 'fa-code-fork'
+ when :Link
+ 'fa-arrows-h'
+ when :User
+ 'fa-user'
+ when :Node
+ 'fa-cloud'
+ when :KeepService
+ 'fa-exchange'
+ when :KeepDisk
+ 'fa-hdd-o'
+ else
+ 'fa-cube'
+ end
+ end
end
class ArvadosBase < ActiveRecord::Base
self.abstract_class = true
attr_accessor :attribute_sortkey
+ attr_accessor :create_params
def self.arvados_api_client
ArvadosApiClient.new_or_current
end
end
- def initialize raw_params={}
+ def initialize raw_params={}, create_params={}
super self.class.permit_attribute_params(raw_params)
+ @create_params = create_params
@attribute_sortkey ||= {
'id' => nil,
'name' => '000',
new.private_reload(hash)
end
+ def self.find?(*args)
+ find(*args) rescue nil
+ end
+
def self.order(*args)
ArvadosResourceList.new(self).order(*args)
end
ActionController::Parameters.new(raw_params).permit!
end
- def self.create raw_params={}
- super(permit_attribute_params(raw_params))
+ def self.create raw_params={}, create_params={}
+ x = super(permit_attribute_params(raw_params))
+ x.create_params = create_params
+ x
end
def update_attributes raw_params={}
obdata.delete :uuid
resp = arvados_api_client.api(self.class, '/' + uuid, postdata)
else
+ postdata.merge!(@create_params) if @create_params
resp = arvados_api_client.api(self.class, '', postdata)
end
return false if !resp[:etag] || !resp[:uuid]
uuid
end
- def dup
- super.forget_uuid!
+ def initialize_copy orig
+ super
+ forget_uuid!
end
def attributes_for_display
end
def class_for_display
- self.class.to_s
+ self.class.to_s.underscore.humanize
end
def self.creatable?
current_user
end
- def self.goes_in_folders?
+ def self.goes_in_projects?
false
end
resource_class
end
+ def resource_param_name
+ self.class.to_s.underscore
+ end
+
def friendly_link_name
- (name if self.respond_to? :name) || uuid
+ (name if self.respond_to? :name) || default_name
end
def content_summary
friendly_link_name
end
+ def self.default_name
+ self.to_s.underscore.humanize
+ end
+
+ def controller
+ (self.class.to_s.pluralize + 'Controller').constantize
+ end
+
+ def controller_name
+ self.class.to_s.tableize
+ end
+
+ # Placeholder for name when name is missing or empty
+ def default_name
+ if self.respond_to? :name
+ "New #{class_for_display.downcase}"
+ else
+ uuid
+ end
+ end
+
def owner
ArvadosBase.find(owner_uuid) rescue nil
end
class Job < ArvadosBase
- def self.goes_in_folders?
+ def self.goes_in_projects?
true
end
+ def content_summary
+ "#{script} job"
+ end
+
def attribute_editable? attr, *args
false
end
false
end
+ def default_name
+ if script
+ x = "\"#{script}\" job"
+ else
+ x = super
+ end
+ if finished_at
+ x += " finished #{finished_at.strftime('%b %-d')}"
+ elsif started_at
+ x += " started #{started_at.strftime('%b %-d')}"
+ elsif created_at
+ x += " submitted #{created_at.strftime('%b %-d')}"
+ end
+ end
++
+ def cancel
+ arvados_api_client.api "jobs/#{self.uuid}/", "cancel", {}
+ end
end
+<% content_for :content_top do %>
+ <% if @object and not @object.is_a?(Group) and @object.class.goes_in_projects? and @object.owner_uuid == current_user.uuid %>
+ <div class="pull-right" style="width: 40%">
+ <div class="alert alert-warning alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+ <strong>Hey.</strong> This <%= @object.class_for_display.downcase %> belongs to your account, but it's not in any of your projects. If you want it to be easy to find in the future, you should move it to a project.<br />
+ <%= button_to(choose_projects_path(
+ title: 'Move to...',
+ editable: true,
+ action_name: 'Move',
+ action_href: url_for(action: :update),
+ action_method: 'patch',
+ action_data: {selection_param: @object.resource_param_name+'[owner_uuid]', success: 'page-refresh'}.to_json),
+ { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
+ <i class="fa fa-fw fa-folder"></i> Choose a project...
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+<% end %>
+
+ <% content_for :js do %>
+ tab_pane_valid_state = {};
+
+ function ajaxRefreshTabPane(pane) {
+ if (!tab_pane_valid_state[pane]) {
+ tab_pane_valid_state[pane] = true;
+ $(document).trigger('ajax:send');
+ $.ajax('<%=j url_for @object %>?tab_pane='+pane, {dataType: 'html', type: 'GET'}).
+ done(function(data, status, jqxhr) {
+ $('#' + pane + ' > div > div').html(data);
+ $(document).trigger('ajax:complete');
+ ajaxRefreshTabPane(pane);
+ });
+ }
+ }
+
+ $(window).on('load', smart_scroll_fixup);
+ $(document).on('shown.bs.tab', 'ul.nav-tabs > li > a', smart_scroll_fixup);
+
+ $(document).on('shown.bs.tab', function(e) {
+ ajaxRefreshTabPane(e.target.id.slice(0, -4));
+ });
+
+ $(document).on('arv-log-event', function() {
+ <% pane_list.each do |pane| %>
+ tab_pane_valid_state['<%=j pane %>'] = false;
+ <% end %>
+ ajaxRefreshTabPane($('.tab-pane.active')[0].id);
+ });
+ <% end %>
+
<% content_for :tab_panes do %>
<% comparable = controller.respond_to? :compare %>
- <% pane_list ||= %w(recent) %>
- <% panes = Hash[pane_list.map { |pane|
- [pane, render(partial: 'show_' + pane.downcase,
- locals: { comparable: comparable, objects: @objects })]
- }.compact] %>
<ul class="nav nav-tabs">
- <% panes.each_with_index do |(pane, content), i| %>
+ <% pane_list.each_with_index do |pane, i| %>
<li class="<%= 'active' if i==0 %>"><a href="#<%= pane %>" data-toggle="tab" id="<%= pane %>-tab"> <%= pane.gsub('_', ' ') %></a></li>
<% end %>
</ul>
<div class="tab-content">
- <% panes.each_with_index do |(pane, content), i| %>
- <div id="<%= pane %>" class="tab-pane fade <%= 'in active' if i==0 %>">
+ <% pane_list.each_with_index do |pane, i| %>
+ <div id="<%= pane %>"
+ class="tab-pane fade <%= 'in active' if i==0 %> arv-log-event-listener"
+ <% if controller.action_name == "index" %>
+ data-object-kind="arvados#<%= ArvadosApiClient.class_kind controller.model_class %>"
+ <% else %>
+ data-object-uuid="<%= @object.uuid %>"
+ <% end %>
+ >
+
+ <% content_for :js do %>
+ <% if i == 0 %>
+ tab_pane_valid_state['<%=j pane %>'] = true;
+ <% else %>
+ tab_pane_valid_state['<%=j pane %>'] = false;
+ $(document).on('ready', function() {
+ ajaxRefreshTabPane('<%=j pane %>');
+ });
+ <% end %>
+ <% end %>
+
- <div class="smart-scroll" style="margin-top:0.5em;">
+ <div id="<%= pane %>-scroll" class="<%= 'smart-scroll' if pane.match(/graph/) %>" style="margin-top:0.5em;">
- <%= content %>
+ <div class="pane-content">
+ <% if i == 0 %>
+ <%= render(partial: 'show_' + pane.downcase,
+ locals: { comparable: comparable, objects: @objects }) %>
+ <% else %>
+ <%= image_tag 'ajax-loader.gif' %>
+ <% end %>
+ </div>
</div>
</div>
<% end %>
</div>
<% end %>
-
- <% content_for :js do %>
- $(window).on('load', smart_scroll_fixup);
- $(document).on('shown.bs.tab', 'ul.nav-tabs > li > a', smart_scroll_fixup);
- <% end %>
--- /dev/null
+<% outgoing = Link.where(tail_uuid: @object.uuid) %>
+<% incoming = Link.where(head_uuid: @object.uuid) %>
+
++<%
++ preload_uuids = []
++ preload_head_uuids = []
++ outgoing.results.each do |link|
++ preload_uuids << link.uuid
++ preload_uuids << link.head_uuid
++ preload_head_uuids << link.head_uuid
++ end
++ preload_collections_for_objects preload_uuids
++ preload_links_for_objects preload_head_uuids
++%>
++
+<% if (outgoing | incoming).any? %>
+<table class="table topalign">
+ <colgroup>
+ <col width="20%" />
+ <col width="10%" />
+ <col width="10%" />
+ <col width="20%" />
+ <col width="20%" />
+ <col width="20%" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th></th>
+ <th>link_class</th>
+ <th>name</th>
+ <th>tail</th>
+ <th>head</th>
+ <th>properties</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% (outgoing | incoming).each do |link| %>
+ <tr>
+ <td>
+ <%= render partial: 'show_object_button', locals: { object: link, size: 'xs' } %>
+ <span class="arvados-uuid"><%= link.uuid %></span>
+ </td>
+ <td><%= link.link_class %></td>
+ <td><%= link.name %></td>
+ <td><%= link.tail_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "tail_uuid", attrvalue: link.tail_uuid, editable: false }) %></td>
+ <td><%= link.head_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "head_uuid", attrvalue: link.head_uuid, editable: false }) %></td>
+ <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties, editable: false } %></td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+<% else %>
+<span class="deemphasize">
+ (No metadata links found)
+</span>
+<% end %>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">
- <% "Collection #{@object.uuid}" %>
+ <% i = 0 %>
+ <% @folder_links.each do |l| %>
+ <%= if i > 0 then ', ' end %>
+ <% i += 1 %>
+ <%= l.name %>
+ <% end %>
</h3>
</div>
<div class="panel-body">
<% end %>
<% if @output_of.andand.any? %>
- <p>Output of jobs:<br />
+ <p>This collection was the output of:<br />
<%= render_arvados_object_list_start(@output_of, 'Show all jobs',
jobs_path(filter: [['output', '=', @object.uuid]].to_json)) do |job| %>
- <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+ <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
<% if @log_of.andand.any? %>
- <p>Log of jobs:<br />
+ <p>This collection contains log messages from:<br />
<%= render_arvados_object_list_start(@log_of, 'Show all jobs',
jobs_path(filter: [['log', '=', @object.uuid]].to_json)) do |job| %>
- <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
+ <%= link_to_if_arvados_object(job, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
<input type="text" class="form-control" placeholder="Search"/>
-->
<div style="height:0.5em;"></div>
- <% if not @logs.andand.any? %>
+ <% name_or_object = @name_link.andand.uuid ? @name_link : @object %>
+ <% if name_or_object.created_at and not @logs.andand.any? %>
<p>
- Created: <%= @object.created_at.to_s(:long) %>
+ Created: <%= name_or_object.created_at.to_s(:long) %>
</p>
<p>
- Last modified: <%= @object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object @object.modified_by_user_uuid, friendly_name: true %>
+ Last modified: <%= name_or_object.modified_at.to_s(:long) %> by <%= link_to_if_arvados_object name_or_object.modified_by_user_uuid, friendly_name: true %>
</p>
<% else %>
<%= render_arvados_object_list_start(@logs, 'Show all activity',
- logs_path(filters: [['object_uuid','=',@object.uuid]].to_json)) do |log| %>
+ logs_path(filters: [['object_uuid','=',name_or_object.uuid]].to_json)) do |log| %>
<p>
<%= time_ago_in_words(log.event_at) rescue 'unknown time' %> ago: <%= log.summary %>
<% if log.object_uuid %>
</div>
<div style="height:0.5em;"></div>
- <% if @folders.andand.any? %>
- <p>Included in folders:<br />
- <%= render_arvados_object_list_start(@folders, 'Show all folders',
+ <% if @projects.andand.any? %>
+ <p>Included in projects:<br />
+ <%= render_arvados_object_list_start(@projects, 'Show all projects',
links_path(filter: [['head_uuid', '=', @object.uuid],
- ['link_class', '=', 'name']].to_json)) do |folder| %>
- <%= link_to_if_arvados_object(folder, friendly_name: true) %><br />
+ ['link_class', '=', 'name']].to_json)) do |project| %>
+ <%= link_to_if_arvados_object(project, friendly_name: true) %><br />
<% end %>
</p>
<% end %>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<meta name="description" content="">
<meta name="author" content="">
- <% if current_user %>
+ <% if current_user and $arvados_api_client.discovery[:websocketUrl] %>
<meta name="arv-websocket-url" content="<%=$arvados_api_client.discovery[:websocketUrl]%>?api_token=<%=Thread.current[:arvados_api_token]%>">
<% end %>
<meta name="robots" content="NOINDEX, NOFOLLOW">
height: 100%;
}
- body > div.container-fluid {
- padding-top: 70px; /* 70px to make the container go all the way to the bottom of the navbar */
- }
-
@media (max-width: 979px) { body { padding-top: 0; } }
- .navbar .nav li.nav-separator > span.glyphicon.glyphicon-arrow-right {
- padding-top: 1.25em;
- }
-
@media (max-width: 767px) {
.breadcrumbs {
display: none;
}
}
</style>
- <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
</head>
<body>
- <div id="wrapper">
+ <div id="wrapper" class="container-fluid">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" href="/"><%= Rails.configuration.site_name rescue Rails.application.class.parent_name %></a>
+ <a class="navbar-brand" href="/"><%= Rails.configuration.site_name.downcase rescue Rails.application.class.parent_name %></a>
</div>
<div class="collapse navbar-collapse">
- <% if current_user.andand.is_active %>
- <ul class="nav navbar-nav side-nav">
-
- <li class="<%= 'arvados-nav-active' if params[:action] == 'home' %>">
- <a href="/"><i class="fa fa-lg fa-dashboard fa-fw"></i> Dashboard</a>
- </li>
-
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-lg fa-hand-o-up fa-fw"></i> Help <b class="caret"></b></a>
- <ul class="dropdown-menu">
- <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
- <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
- <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
- </ul>
- </li>
-
- <li class="dropdown">
- <a href="/folders">
- <i class="fa fa-lg fa-folder-o fa-fw"></i> Folders
- </a></li>
- <li><a href="/collections">
- <i class="fa fa-lg fa-briefcase fa-fw"></i> Collections (data files)
- </a></li>
- <li><a href="/jobs">
- <i class="fa fa-lg fa-tasks fa-fw"></i> Jobs
- </a></li>
- <li><a href="/pipeline_instances">
- <i class="fa fa-lg fa-tasks fa-fw"></i> Pipeline instances
- </a></li>
- <li><a href="/pipeline_templates">
- <i class="fa fa-lg fa-gears fa-fw"></i> Pipeline templates
- </a></li>
- <li> </li>
- <li><a href="/repositories">
- <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
- </a></li>
- <li><a href="/virtual_machines">
- <i class="fa fa-lg fa-terminal fa-fw"></i> Virtual machines
- </a></li>
- <li><a href="/humans">
- <i class="fa fa-lg fa-male fa-fw"></i> Humans
- </a></li>
- <li><a href="/specimens">
- <i class="fa fa-lg fa-flask fa-fw"></i> Specimens
- </a></li>
- <li><a href="/traits">
- <i class="fa fa-lg fa-clipboard fa-fw"></i> Traits
- </a></li>
- <li><a href="/links">
- <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
- </a></li>
- <% if current_user.andand.is_admin %>
- <li><a href="/users">
- <i class="fa fa-lg fa-user fa-fw"></i> Users
- </a></li>
- <% end %>
- <li><a href="/groups">
- <i class="fa fa-lg fa-users fa-fw"></i> Groups
- </a></li>
- <li><a href="/nodes">
- <i class="fa fa-lg fa-cloud fa-fw"></i> Compute nodes
- </a></li>
- <li><a href="/keep_services">
- <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
- </a></li>
- <li><a href="/keep_disks">
- <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
- </a></li>
- </ul>
- <% end %>
-
- <ul class="nav navbar-nav navbar-left breadcrumbs">
- <% if current_user %>
- <% if content_for?(:breadcrumbs) %>
- <%= yield(:breadcrumbs) %>
- <% else %>
- <li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
- <li>
- <%= link_to(
- controller.controller_name.humanize.downcase,
- url_for({controller: params[:controller]})) %>
- </li>
- <% if params[:action] != 'index' %>
- <li class="nav-separator">
- <span class="glyphicon glyphicon-arrow-right"></span>
- </li>
- <li>
- <%= link_to_if_arvados_object @object, {friendly_name: true}, {data: {object_uuid: @object.andand.uuid, name: 'name'}} %>
- </li>
- <li style="padding: 14px 0 14px">
- <%= form_tag do |f| %>
- <%= render :partial => "selection_checkbox", :locals => {:object => @object} %>
- <% end %>
- </li>
- <% end %>
- <% end %>
- <% end %>
- </ul>
-
<ul class="nav navbar-nav navbar-right">
<li>
</li>
-->
- <li class="dropdown notification-menu">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
- <span class="glyphicon glyphicon-paperclip"></span>
- <span class="badge" id="persistent-selection-count"></span>
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
- <%= form_tag '/actions' do %>
- <%= hidden_field_tag 'uuid', @object.andand.uuid %>
- <div id="selection-form-content"></div>
- <% end %>
- </ul>
- </li>
-
- <% if current_user.is_active %>
+ <% if current_user %>
<li class="dropdown notification-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
- <span class="glyphicon glyphicon-envelope"></span>
<span class="badge badge-alert notification-count"><%= @notification_count %></span>
- <span class="caret"></span>
+ <%= current_user.email %>
</a>
<ul class="dropdown-menu" role="menu">
- <% if (@notifications || []).length > 0 %>
+ <% if current_user.is_active %>
+ <li role="presentation"><a href="/authorized_keys" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage ssh keys</a></li>
+ <li role="presentation"><a href="/api_client_authorizations" role="menuitem"><i class="fa fa-ticket fa-fw"></i> Manage API tokens</a></li>
+ <li role="presentation" class="divider"></li>
+ <% end %>
+ <li role="presentation"><a href="<%= logout_path %>" role="menuitem"><i class="fa fa-sign-out fa-fw"></i> Log out</a></li>
+ <% if current_user.is_active and
+ (@notifications || []).length > 0 %>
+ <li role="presentation" class="divider"></li>
<% @notifications.each_with_index do |n, i| %>
<% if i > 0 %><li class="divider"></li><% end %>
<li class="notification"><%= n.call(self) %></li>
<% end %>
- <% else %>
- <li class="notification empty">No notifications.</li>
<% end %>
</ul>
</li>
<% end %>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="user-menu">
- <span class="glyphicon glyphicon-user"></span><span class="caret"></span>
+ <li class="dropdown notification-menu">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
+ <span class="fa fa-lg fa-paperclip"></span>
+ <span class="badge" id="persistent-selection-count"></span>
</a>
- <ul class="dropdown-menu" role="menu">
- <li role="presentation" class="dropdown-header"><%= current_user.email %></li>
- <% if current_user.is_active %>
- <li role="presentation" class="divider"></li>
- <li role="presentation"><a href="/authorized_keys" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage ssh keys</a></li>
- <li role="presentation"><a href="/api_client_authorizations" role="menuitem"><i class="fa fa-ticket fa-fw"></i> Manage API tokens</a></li>
- <li role="presentation" class="divider"></li>
+ <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
+ <%= form_tag '/actions' do %>
+ <%= hidden_field_tag 'uuid', @object.andand.uuid %>
+ <div id="selection-form-content"></div>
<% end %>
- <li role="presentation"><a href="<%= logout_path %>" role="menuitem"><i class="fa fa-sign-out fa-fw"></i> Log out</a></li>
</ul>
</li>
+
+ <% if current_user.is_active %>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="system-menu">
+ <span class="fa fa-lg fa-gear"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li role="presentation" class="dropdown-header">
+ System tools
+ </li>
+ <li role="presentation"><a href="/repositories">
+ <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
+ </a></li>
+ <li role="presentation"><a href="/virtual_machines">
+ <i class="fa fa-lg fa-terminal fa-fw"></i> Virtual machines
+ </a></li>
+ <li role="presentation"><a href="/links">
+ <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
+ </a></li>
+ <% if current_user.andand.is_admin %>
+ <li role="presentation"><a href="/users">
+ <i class="fa fa-lg fa-user fa-fw"></i> Users
+ </a></li>
+ <% end %>
+ <li role="presentation"><a href="/groups">
+ <i class="fa fa-lg fa-users fa-fw"></i> Groups
+ </a></li>
+ <li role="presentation"><a href="/nodes">
+ <i class="fa fa-lg fa-cloud fa-fw"></i> Compute nodes
+ </a></li>
+ <li role="presentation"><a href="/keep_services">
+ <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
+ </a></li>
+ <li role="presentation"><a href="/keep_disks">
+ <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
+ </a></li>
+ </ul>
+ </li>
+ <% end %>
<% else %>
<li><a href="<%= arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
<% end %>
</div><!-- /.navbar-collapse -->
</nav>
+ <% if current_user.andand.is_active %>
+ <nav class="navbar navbar-default breadcrumbs" role="navigation">
+ <ul class="nav navbar-nav navbar-left">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="projects-menu">
+ <i class="fa fa-lg fa-fw fa-home"></i>
+ Projects
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li role="presentation" class="dropdown-header">
+ <%= link_to projects_path('project[owner_uuid]' => current_project_uuid), method: 'post', class: 'btn btn-xs btn-default pull-right' do %>
+ <i class="fa fa-plus"></i> New project
+ <% end %>
+ My projects
+ </li>
+ <% my_projects.each do |p| %>
+ <li>
+ <%= link_to(p.name, project_path(p.uuid)) %>
+ </li>
+ <% end %>
+ <li class="divider">
+ <li role="presentation" class="dropdown-header">
+ Projects shared with me
+ </li>
+ <% projects_shared_with_me.each do |p| %>
+ <li>
+ <%= link_to project_path(p.uuid) do %>
+ <i class="fa fa-fw fa-share-alt"></i> <%= p.name %>
+ <% end %>
+ </li>
+ <% end %>
+ </ul>
+ </li>
+ <% project_breadcrumbs.each do |p| %>
+ <li class="nav-separator">
+ <i class="fa fa-lg fa-angle-double-right"></i>
+ </li>
+ <li>
+ <%= link_to(p.name, project_path(p.uuid), data: {object_uuid: p.uuid, name: 'name'}) %>
+ </li>
+ <% end %>
+ <% if current_project_uuid.andand != @object.andand.uuid %>
+ <li class="nav-separator">
+ <i class="fa fa-lg fa-angle-double-right"></i>
+ </li>
+ <% end %>
+ </ul>
+ </nav>
+ <% end %>
+
<div id="page-wrapper">
<%= yield %>
</div>
</div>
- </div>
-
<%= yield :footer_html %>
<%= piwik_tracking_tag %>
<%= javascript_tag do %>
resources :virtual_machines
resources :authorized_keys
resources :job_tasks
- resources :jobs
+ resources :jobs do
+ post 'cancel', :on => :member
+ end
match '/logout' => 'sessions#destroy', via: [:get, :post]
get '/logged_out' => 'sessions#index'
resources :users do
resources :uploaded_datasets
resources :groups
resources :specimens
- resources :pipeline_templates
+ resources :pipeline_templates do
+ get 'choose', on: :collection
+ end
resources :pipeline_instances do
get 'compare', on: :collection
+ post 'copy', on: :member
end
resources :links
get '/collections/graph' => 'collections#graph'
get 'sharing_popup', :on => :member
post 'share', :on => :member
post 'unshare', :on => :member
+ get 'choose', on: :collection
end
get('/collections/download/:uuid/:reader_token/*file' => 'collections#show_file',
format: false)
get '/collections/download/:uuid/:reader_token' => 'collections#show_file_links'
get '/collections/:uuid/*file' => 'collections#show_file', :format => false
- resources :folders do
+ resources :projects do
match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
+ match 'remove_items', on: :member, via: :delete, action: :remove_items
get 'choose', on: :collection
end
post 'actions' => 'actions#post'
get 'websockets' => 'websocket#index'
- root :to => 'users#welcome'
+ root :to => 'projects#index'
# Send unroutable requests to an arbitrary controller
# (ends up at ApplicationController#render_not_found)
visit page_with_token('admin_trustedclient')
# go to Users list page
+ find('#system-menu').click
click_link 'Users'
# check active user attributes in the list page
end
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
assert page.has_text? 'Attributes'
- assert page.has_text? 'Metadata'
+ assert page.has_text? 'Advanced'
assert page.has_text? 'Admin'
# go to the Attributes tab
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
assert page.has_text? 'zzzzz-tpzed-d9tiejq69daie8f'
# verify that the new user showed up in the users page and find
# the new user's UUID
- new_user_uuid =
- find('tr[data-object-uuid]', text: 'foo@example.com').
- find('td', text: '-tpzed-').
- text
+ new_user_uuid =
+ find('tr[data-object-uuid]', text: 'foo@example.com')['data-object-uuid']
assert new_user_uuid, "Expected new user uuid not found"
# go to the new user's page
find('tr', text: new_user_uuid).
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
assert page.has_text? 'modified_by_user_uuid'
assert_equal "false", text, "Expected new user's is_active to be false"
end
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: test_repo)'
- assert !(page.has_text? '(VirtualMachine:)')
+ assert page.has_text? 'Repository: test_repo'
+ assert !(page.has_text? 'VirtualMachine:')
headless.stop
end
Capybara.current_driver = :selenium
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
# click on active user
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
# Setup user
assert has_text? 'Virtual Machine'
fill_in "repo_name", :with => "test_repo"
click_button "Submit"
- wait_for_ajax
end
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: test_repo)'
- assert !(page.has_text? '(VirtualMachine:)')
+ assert page.has_text? 'Repository: test_repo'
+ assert !(page.has_text? 'VirtualMachine:')
# Click on Setup button again and this time also choose a VM
click_link 'Admin'
fill_in "repo_name", :with => "second_test_repo"
select("testvm.shell", :from => 'vm_uuid')
click_button "Submit"
- wait_for_ajax
end
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: second_test_repo)'
- assert page.has_text? '(VirtualMachine: testvm.shell)'
+ assert page.has_text? 'Repository: second_test_repo'
+ assert page.has_text? 'VirtualMachine: testvm.shell'
headless.stop
end
visit page_with_token('admin_trustedclient')
+ find('#system-menu').click
click_link 'Users'
# click on active user
find('tr', text: 'zzzzz-tpzed-xurymjxw79nv3jz').
- find('a,button', text: 'Show').
+ find('a', text: 'Show').
click
# Verify that is_active is set
assert_equal "false", text, "Expected user's is_active to be false after unsetup"
end
+ click_link 'Advanced'
click_link 'Metadata'
- assert !(page.has_text? '(Repository: test_repo)')
- assert !(page.has_text? '(Repository: second_test_repo)')
- assert !(page.has_text? '(VirtualMachine: testvm.shell)')
+ assert !(page.has_text? 'Repository: test_repo')
+ assert !(page.has_text? 'Repository: second_test_repo')
+ assert !(page.has_text? 'VirtualMachine: testvm.shell')
# setup user again and verify links present
click_link 'Admin'
fill_in "repo_name", :with => "second_test_repo"
select("testvm.shell", :from => 'vm_uuid')
click_button "Submit"
- wait_for_ajax
end
assert page.has_text? 'modified_by_client_uuid'
+ click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? '(Repository: second_test_repo)'
- assert page.has_text? '(VirtualMachine: testvm.shell)'
+ assert page.has_text? 'Repository: second_test_repo'
+ assert page.has_text? 'VirtualMachine: testvm.shell'
headless.stop
end