self.response_body = file_enumerator opts
end
- ApiClientAuthorization.where(filters: [['scopes', '=', ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/collections/#{@object.uuid}/"]]])
++ def sharing_scopes
++ ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/keep_services"]
++ end
++
+ def search_scopes
++ ApiClientAuthorization.where(filters: [['scopes', '=', sharing_scopes]])
+ end
+
def show
return super if !@object
- @provenance = []
- @output2job = {}
- @output2colorindex = {}
- @sourcedata = {params[:uuid] => {uuid: params[:uuid]}}
- @protected = {}
- @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
-
- colorindex = -1
- any_hope_left = true
- while any_hope_left
- any_hope_left = false
- Job.where(output: @sourcedata.keys).sort_by { |a| a.finished_at || a.created_at }.reverse.each do |job|
- if !@output2colorindex[job.output]
- any_hope_left = true
- @output2colorindex[job.output] = (colorindex += 1) % 10
- @provenance << {job: job, output: job.output}
- @sourcedata.delete job.output
- @output2job[job.output] = job
- job.dependencies.each do |new_source_data|
- unless @output2colorindex[new_source_data]
- @sourcedata[new_source_data] = {uuid: new_source_data}
- end
- end
- end
- end
- end
-
- Link.where(head_uuid: @sourcedata.keys | @output2job.keys).each do |link|
- if link.link_class == 'resources' and link.name == 'wants'
- @protected[link.head_uuid] = true
- if link.tail_uuid == current_user.uuid
- @is_persistent = true
- end
+ if current_user
+ jobs_with = lambda do |conds|
+ Job.limit(RELATION_LIMIT).where(conds)
+ .results.sort_by { |j| j.finished_at || j.created_at }
end
+ @output_of = jobs_with.call(output: @object.uuid)
+ @log_of = jobs_with.call(log: @object.uuid)
+ folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
+ .where(head_uuid: @object.uuid, link_class: 'name').results
+ folder_hash = Group.where(uuid: folder_links.map(&:tail_uuid)).to_hash
+ @folders = folder_links.map { |link| folder_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
+ @logs = Log.limit(RELATION_LIMIT).order("created_at DESC")
+ .where(object_uuid: @object.uuid).results
+ @is_persistent = Link.limit(1)
+ .where(head_uuid: @object.uuid, tail_uuid: current_user.uuid,
+ link_class: 'resources', name: 'wants')
+ .results.any?
++ @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
end
- Link.where(tail_uuid: @sourcedata.keys).each do |link|
- if link.link_class == 'data_origin'
- @sourcedata[link.tail_uuid][:data_origins] ||= []
- @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_uuid]
- end
- end
- Collection.where(uuid: @sourcedata.keys).each do |collection|
- if @sourcedata[collection.uuid]
- @sourcedata[collection.uuid][:collection] = collection
- end
- end
-
- Collection.where(uuid: @object.uuid).each do |u|
- @prov_svg = ProvenanceHelper::create_provenance_graph(u.provenance, "provenance_svg",
- {:request => request,
- :direction => :bottom_up,
- :combine_jobs => :script_only}) rescue nil
- @used_by_svg = ProvenanceHelper::create_provenance_graph(u.used_by, "used_by_svg",
- {:request => request,
- :direction => :top_down,
- :combine_jobs => :script_only,
- :pdata_only => true}) rescue nil
- end
+ @prov_svg = ProvenanceHelper::create_provenance_graph(@object.provenance, "provenance_svg",
+ {:request => request,
+ :direction => :bottom_up,
+ :combine_jobs => :script_only}) rescue nil
+ @used_by_svg = ProvenanceHelper::create_provenance_graph(@object.used_by, "used_by_svg",
+ {:request => request,
+ :direction => :top_down,
+ :combine_jobs => :script_only,
+ :pdata_only => true}) rescue nil
end
- a = ApiClientAuthorization.create(scopes: ["GET /arvados/v1/collections/#{@object.uuid}", "GET /arvados/v1/collections/#{@object.uuid}/"])
+ def sharing_popup
+ @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
+ respond_to do |format|
+ format.html
+ format.js
+ end
+ end
+
+ def share
++ a = ApiClientAuthorization.create(scopes: sharing_scopes)
+ @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
+ render 'sharing_popup'
+ end
+
+ def unshare
+ @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
+ @search_sharing.each do |s|
+ s.destroy
+ end
+ @search_sharing = search_scopes.select { |s| s.scopes != ['all'] }
+ render 'sharing_popup'
+ end
+
protected
- def find_usable_token
- # Iterate over every token available to make it the current token and
+ def find_usable_token(token_list)
+ # Iterate over every given token to make it the current token and
# yield the given block.
# If the block succeeds, return the token it used.
# Otherwise, render an error response based on the most specific
--- /dev/null
- <%= link_to linktext, sharing_popup_collection_url(id: @object.uuid), {class: "btn-xs #{btnstyle}", :remote => true, 'data-toggle' => "modal", 'data-target' => '#collection-sharing-modal-window'} %>
+<% if @search_sharing.any? %>
+ <% linktext = "Shared" %>
+ <% btnstyle = "btn-success" %>
+<% else %>
+ <% linktext = "Share" %>
+ <% btnstyle = "btn-info" %>
+<% end %>
++<%= link_to linktext, sharing_popup_collection_url(id: @object.uuid), {class: "btn #{btnstyle}", :remote => true, 'data-toggle' => "modal", 'data-target' => '#collection-sharing-modal-window'} %>
--- /dev/null
- <% link = collection_url + "?reader_tokens[]=#{@search_sharing.first.api_token}" %>
+
+<div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">×</button>
+ <h4 class="modal-title">Sharing</h4>
+ </div>
+ <div class="modal-body">
+ <div id="sharing-text" style="text-align: center; word-wrap: break-word">
+ <% if @search_sharing.any? %>
+ Use this link to share this collection:<br>
+ <big>
++ <% link = collections_url + "/download/#{@object.uuid}/#{@search_sharing.first.api_token}" %>
+ <%= link_to link, link %>
+ </big>
+ <% else %>
+ Not shared.
+ <% end %>
+ </div>
+ <div style="text-align: center; padding-top: 1em">
+ <% if @search_sharing and @search_sharing.any? %>
+ <%= link_to "Unshare", unshare_collection_url, {
+ class: 'btn btn-success',
+ remote: true,
+ method: 'post'
+ } %>
+ <% else %>
+ <%= link_to "Share", share_collection_url, {
+ class: 'btn btn-info',
+ remote: true,
+ method: 'post'
+ } %>
+ <% end %>
+ </div>
+ </div>
+ </div>
+</div>
<div class="col-md-6"></div>
<div class="col-md-6">
<div class="pull-right">
- <span id="sharing-button">
- <%= render partial: 'sharing_button' %>
- </span>
- Collection storage status:
+ <span style="padding-left: 1em">Collection storage status:</span>
<%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
+
</div>
</div>
</div>
:class => 'persistent-selection',
:friendly_type => "File",
:friendly_name => "#{@object.uuid}/#{file_path}",
- :href => "#{url_for controller: 'collections', action: 'show', id: @object.uuid }/#{file_path}",
- :title => "Click to add this item to your selection list"
+ :href => url_for(controller: 'collections', action: 'show_file',
+ uuid: @object.uuid, file: file_path),
+ :title => "Include #{file_path} in your selections",
} %>
- </td>
- <td>
- <%= file[0] %>
- </td>
-
- <td>
- <%= link_to (if CollectionsHelper::is_image file[1]
- image_tag "#{url_for @object}/#{file_path}", class: "file-list-inline-image"
- else
- file[1]
- end),
- {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'inline'},
- {title: file_path} %>
- </td>
-
- <td style="text-align:right">
- <%= raw(human_readable_bytes_html(file[2])) %>
- </td>
-
- <td>
- <div style="display:inline-block">
- <%= link_to raw('<i class="glyphicon glyphicon-download-alt"></i>'), {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'attachment'}, {class: 'btn btn-info btn-sm', title: 'Download'} %>
- </div>
- </td>
- </tr>
- <% end; end %>
- </tbody>
- </table>
+ <%= link_to(raw('<i class="fa fa-search"></i>'),
+ link_params.merge(disposition: 'inline'),
+ {title: "View #{file_path}", class: "btn btn-info btn-sm"}) %>
+ <%= link_to(raw('<i class="fa fa-download"></i>'),
+ link_params.merge(disposition: 'attachment'),
+ {title: "Download #{file_path}", class: "btn btn-info btn-sm"}) %>
+ </div>
+ <% if CollectionsHelper::is_image(filename) %>
+ <div class="collection_files_name"><i class="fa fa-fw fa-bar-chart-o"></i> <%= filename %></div>
+ </div>
+ <div class="collection_files_inline">
+ <%= link_to(image_tag("#{url_for @object}/#{file_path}"),
+ link_params.merge(disposition: 'inline'),
+ {title: file_path}) %>
+ </div>
+ <% else %>
+ <div class="collection_files_name"><i class="fa fa-fw fa-file"></i> <%= filename %></div>
+ </div>
+ <% end %>
+ </li>
+ <% end # if file or directory %>
+ <% end # file_tree.each %>
+ <%= raw(dirstack.map { |_| "</ul>" }.join("</li>")) %>
+ <% end # if file_tree %>
+
+<% content_for :footer_html do %>
+<div id="collection-sharing-modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"></div>
+<% end %>
--- /dev/null
+ <div class="row row-fill-height">
+ <div class="col-md-6">
+ <div class="panel panel-info">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <% default_name = "Collection #{@object.uuid}" %>
+ <% name_html = render_editable_attribute @object, 'name', nil, {data: {emptytext: default_name}} %>
+ <%= (/\S/.match(name_html)) ? name_html : default_name %>
+ </h3>
+ </div>
+ <div class="panel-body">
+ <img src="/favicon.ico" class="pull-right" alt="" style="opacity: 0.3"/>
+ <% if not (@output_of.andand.any? or @log_of.andand.any?) %>
+ <p><i>No source information available.</i></p>
+ <% end %>
+
+ <% if @output_of.andand.any? %>
+ <p>Output of jobs:<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 />
+ <% end %>
+ </p>
+ <% end %>
+
+ <% if @log_of.andand.any? %>
+ <p>Log of jobs:<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 />
+ <% end %>
+ </p>
+ <% end %>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ Activity
+ </h3>
+ </div>
+ <div class="panel-body smaller-text">
+ <!--
+ <input type="text" class="form-control" placeholder="Search"/>
+ -->
+ <div style="height:0.5em;"></div>
+ <% if not @logs.andand.any? %>
+ <p>
+ Created: <%= @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 %>
+ </p>
+ <% else %>
+ <%= render_arvados_object_list_start(@logs, 'Show all activity',
+ logs_path(filters: [['object_uuid','=',@object.uuid]].to_json)) do |log| %>
+ <p>
+ <%= time_ago_in_words(log.event_at) %> ago: <%= log.summary %>
+ <% if log.object_uuid %>
+ <%= link_to_if_arvados_object log.object_uuid, link_text: raw('<i class="fa fa-hand-o-right"></i>') %>
+ <% end %>
+ </p>
+ <% end %>
+ <% end %>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ Sharing and permissions
+ </h3>
+ </div>
+ <div class="panel-body">
+ <!--
+ <input type="text" class="form-control" placeholder="Search"/>
+ -->
++
++ <div id="sharing-button" style="text-align: center">
++ <%= render partial: 'sharing_button' %>
++ </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',
+ links_path(filter: [['head_uuid', '=', @object.uuid],
+ ['link_class', '=', 'name']].to_json)) do |folder| %>
+ <%= link_to_if_arvados_object(folder, friendly_name: true) %><br />
+ <% end %>
+ </p>
+ <% end %>
+ <% if @permissions.andand.any? %>
+ <p>Readable by:<br />
+ <%= render_arvados_object_list_start(@permissions, 'Show all permissions',
+ links_path(filter: [['head_uuid', '=', @object.uuid],
+ ['link_class', '=', 'permission']].to_json)) do |link| %>
+ <%= link_to_if_arvados_object(link.tail_uuid, friendly_name: true) %><br />
+ <% end %>
+ </p>
+ <% end %>
++
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <%= render file: 'application/show.html.erb' %>
get 'compare', on: :collection
end
resources :links
- match '/collections/graph' => 'collections#graph'
+ get '/collections/graph' => 'collections#graph'
resources :collections do
post 'set_persistent', on: :member
+ get 'sharing_popup', :on => :member
+ post 'share', :on => :member
+ post 'unshare', :on => :member
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
match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
def ensure_owner_uuid_is_permitted
raise PermissionDeniedError if !current_user
- if self.respond_to? :owner_uuid=
+ if respond_to? :owner_uuid=
self.owner_uuid ||= current_user.uuid
- if self.owner_uuid_changed?
- if current_user.uuid == self.owner_uuid or
- current_user.can? write: self.owner_uuid
- # current_user is, or has :write permission on, the new owner
- else
- logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
- raise PermissionDeniedError
- end
- end
+ end
+ if self.owner_uuid_changed?
- if current_user.uuid == self.owner_uuid or
+ if new_record?
+ return true
- elsif current_user.uuid == self.owner_uuid_was or
- current_user.uuid == self.uuid or
- current_user.can? write: self.owner_uuid_was
- # current user is, or has :write permission on, the previous owner
- return true
++ elsif current_user.uuid == self.owner_uuid or
+ current_user.can? write: self.owner_uuid
+ # current_user is, or has :write permission on, the new owner
else
- logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
+ logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{self.uuid} but does not have permission to write #{self.owner_uuid_was}"
raise PermissionDeniedError
end
end