Skip to content

Commit

Permalink
3944 - add file browser to project show page.
Browse files Browse the repository at this point in the history
  • Loading branch information
euler-room committed Nov 21, 2024
1 parent 4f7a643 commit 4b67711
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 36 deletions.
47 changes: 47 additions & 0 deletions apps/dashboard/app/controllers/concerns/pathable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Pathable
extend ActiveSupport::Concern

def normalized_path(path)
Pathname.new("/#{path.to_s.chomp('/').delete_prefix('/')}")
end

def parse_path(path = nil, filesystem = nil)
normal_path = normalized_path(path || resolved_path)
filesystem ||= resolved_fs
if filesystem == 'fs'
@path = PosixFile.new(normal_path)
@filesystem = 'fs'
elsif ::Configuration.remote_files_enabled? && filesystem != 'fs'
@path = RemoteFile.new(normal_path, filesystem)
@filesystem = filesystem
else
@path = PosixFile.new(normal_path)
@filesystem = filesystem
raise StandardError, I18n.t('dashboard.files_remote_disabled')
end
end

def validate_path!
if posix_file?
AllowlistPolicy.default.validate!(@path)
elsif @path.remote_type.nil?
raise StandardError, "Remote #{@path.remote} does not exist"
elsif ::Configuration.allowlist_paths.present? && (@path.remote_type == 'local' || @path.remote_type == 'alias')
# local and alias remotes would allow bypassing the AllowListPolicy
# TODO: Attempt to evaluate the path of them and validate?
raise StandardError, "Remotes of type #{@path.remote_type} are not allowed due to ALLOWLIST_PATH"
end
end

def posix_file?
@path.is_a?(PosixFile)
end

def resolved_path
raise NoMethodError, "Must implement resolved_path in #{self.class.to_s} to use Pathable concern"
end

def resolved_fs
raise NoMethodError, "Must implement resolved_fs in #{self.class.to_s} to use Pathable concern"
end
end
46 changes: 11 additions & 35 deletions apps/dashboard/app/controllers/files_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# The controller for all the files pages /dashboard/files
class FilesController < ApplicationController
include ActionController::Live
include Pathable

before_action :strip_sendfile_headers, only: [:fs]

Expand Down Expand Up @@ -39,8 +40,8 @@ def fs
end
end

# FIXME: below is a large block that should be moved to a model
# if moved to a model the exceptions can be handled there and
# FIXME: below is a large block that should be moved to a concern (Zipable, perhaps?)
# if moved to a concern the exceptions can be handled there and
# then this code will be simpler to read
# and we can avoid rescuing in a block so we can reintroduce
# the block braces which is the Rails convention with the respond_to formats.
Expand Down Expand Up @@ -192,41 +193,16 @@ def strip_sendfile_headers
request.headers['HTTP_X_ACCEL_MAPPING'] = nil
end

def normalized_path(path = params[:filepath])
Pathname.new("/#{path.to_s.chomp('/').delete_prefix('/')}")
# Required for use with Pathable concern (app/controllers/concerns/pathable.rb)
def resolved_path
params[:filepath]
end

def parse_path(path = params[:filepath], filesystem = params[:fs])
normal_path = normalized_path(path)
if filesystem == 'fs'
@path = PosixFile.new(normal_path)
@filesystem = 'fs'
elsif ::Configuration.remote_files_enabled? && filesystem != 'fs'
@path = RemoteFile.new(normal_path, filesystem)
@filesystem = filesystem
else
@path = PosixFile.new(normal_path)
@filesystem = filesystem
raise StandardError, I18n.t('dashboard.files_remote_disabled')
end
end

def validate_path!
if posix_file?
AllowlistPolicy.default.validate!(@path)
elsif @path.remote_type.nil?
raise StandardError, "Remote #{@path.remote} does not exist"
elsif ::Configuration.allowlist_paths.present? && (@path.remote_type == 'local' || @path.remote_type == 'alias')
# local and alias remotes would allow bypassing the AllowListPolicy
# TODO: Attempt to evaluate the path of them and validate?
raise StandardError, "Remotes of type #{@path.remote_type} are not allowed due to ALLOWLIST_PATH"
end

# Required for use with Pathable concern (app/controllers/concerns/pathable.rb)
def resolved_fs
params[:fs]
end

def posix_file?
@path.is_a?(PosixFile)
end


def download?
params[:download]
end
Expand Down
34 changes: 34 additions & 0 deletions apps/dashboard/app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

# The controller for project pages /dashboard/projects.
class ProjectsController < ApplicationController
include Pathable

# GET /projects/:id
def show
project_id = show_project_params[:id]
@project = Project.find(project_id)

parse_path
validate_path!
@files = @path.ls

if @project.nil?
respond_to do |format|
message = I18n.t('dashboard.jobs_project_not_found', project_id: project_id)
Expand All @@ -27,6 +34,19 @@ def show
end
end
end

# GET /projects/:project_id/files/*filepath
def files
@project = Project.find(files_params[:project_id])
parse_path(files_params[:filepath])
validate_path!
Rails.logger.debug("\n\n\n==============================================================")
Rails.logger.debug("ProjectsController#files: request: #{request.methods.sort}")
Rails.logger.debug("==============================================================\n\n\n")
@files = @path.ls

render(partial: 'projects/directory', locals: { project_id: @project.id, path: @path, files: @files })
end

# GET /projects
def index
Expand Down Expand Up @@ -162,6 +182,16 @@ def stop_job

private

# Required for use with Pathable concern (app/controllers/concerns/pathable.rb)
def resolved_path
@project&.directory.to_s
end

# Required for use with Pathable concern (app/controllers/concerns/pathable.rb)
def resolved_fs
'fs'
end

def templates
Project.templates.map do |project|
label = project.title
Expand All @@ -179,6 +209,10 @@ def project_params
.permit(:name, :directory, :description, :icon, :id, :template)
end

def files_params
params.permit(:project_id, :filepath)
end

def show_project_params
params.permit(:id)
end
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/app/javascript/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'datatables.net';
import 'datatables.net-bs4/js/dataTables.bootstrap4';
import 'datatables.net-select/js/dataTables.select';
import 'datatables.net-plugins/api/processing().mjs';
import "@hotwired/turbo-rails"

import Rails from '@rails/ujs';

Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<%= return "turbo_rails/frame" if turbo_frame_request? %>
<!DOCTYPE html>
<html lang="<%= Configuration.locale %>">
<head>
Expand Down
21 changes: 21 additions & 0 deletions apps/dashboard/app/views/projects/_directory.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<%= turbo_frame_tag "project_files" do %>
<table class="table table-striped table-condensed w-100">
<thead>
<tr>
<th>
<input type="checkbox" id="select_all" title="Select All"></input>
</th>
<th>Type<span aria-hidden="true"></span></th>
<th>Name<span aria-hidden="true"></span></th>
<th><span class="sr-only">Actions</span></th>
<th>Size<span aria-hidden="true"></span></th>
<th>Modified at<span aria-hidden="true"></span></th>
<th>Owner<span aria-hidden="true"></span></th>
<th>Mode<span aria-hidden="true"></span></th>
</tr>
</thead>
<tbody>
<%= render partial: "files", locals: { project_id: project.id, path: path, files: files } %>
</tbody>
</table>
<% end %>
54 changes: 54 additions & 0 deletions apps/dashboard/app/views/projects/_files.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<tr>
<td>
<input type="checkbox" class="file-checkbox disabled" title="Select"></input>
</td>
<td>
</td>
<td>
<%= link_to("..", project_files_path(project_id: project_id, filepath: sanitize("#{path}/..")), data: { turbo_frame: "project_files" }) %>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<% files.each do |file| %>
<tr>
<td>
<input type="checkbox" class="file-checkbox" data-file-id="<%= file[:id] %>" title="Select"></input>
</td>
<td>
<%= file[:type] %>
</td>
<td>
<%= link_to(file[:name], project_files_path(project_id: project_id, filepath: sanitize("#{path}/#{file[:name]}")), data: { turbo_frame: "project_files" }) %>
</td>
<td>
<span class="sr-only">Actions</span>
<div class="btn-group">
<button type="button" class="btn btn-outline-dark btn-sm" title="View file" data-file-id="<%= file[:id] %>">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-outline-dark btn-sm" title="Download file" data-file-id="<%= file[:id] %>">
<i class="fa fa-download" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-outline-dark btn-sm" title="Delete file" data-file-id="<%= file[:id] %>">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</td>
<td>
<%= file[:size] %>
</td>
<td>
<%= file[:modified_at] %>
</td>
<td>
<%= file[:owner] %>
</td>
<td>
<%= file[:mode] %>
</td>
</tr>
<%- end -%>
6 changes: 5 additions & 1 deletion apps/dashboard/app/views/projects/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
</div>
</div>

<div class="col-md-8">
<div class="col-md-4">
<div class="row">
<h2 class="h3 d-flex justify-content-center">Active Jobs</h2>
<%= render(partial: 'job_details', collection: @project.active_jobs, as: :job, locals: { project: @project }) %>
Expand All @@ -77,6 +77,10 @@
</div>
</div>
</div>
<div class="col-md-6">
<h2 class="h3 d-flex justify-content-center"><%= "#{@project.name} (Project ID: #{@project.id})" %></h2>
<%= render partial: 'directory', locals: { project: @project, path: @path, files: @files } %>
</div>
</div>

<% unless @project.readme_path.nil? %>
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
post 'save', on: :member
end
end
if Configuration.can_access_files?
get 'projects/:project_id/files/*filepath' => 'projects#files', as: 'project_files'
end
end

# in production, if the user doesn't have access to the files app directory, we hide the routes
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "openondemand-dashboard",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@hotwired/turbo-rails": "^8.0.12",
"@popperjs/core": "^2.11.8",
"@rails/ujs": "^7.0.1",
"@uppy/core": "^4.0",
Expand Down
18 changes: 18 additions & 0 deletions apps/dashboard/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,29 @@
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5"
integrity sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==

"@hotwired/turbo-rails@^8.0.12":
version "8.0.12"
resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-8.0.12.tgz#6f1a2661122c0a2bf717f3bc68b5106638798c89"
integrity sha512-ZXwu9ez+Gd4RQNeHIitqOQgi/LyqY8J4JqsUN0nnYiZDBRq7IreeFdMbz29VdJpIsmYqwooE4cFzPU7QvJkQkA==
dependencies:
"@hotwired/turbo" "^8.0.12"
"@rails/actioncable" "^7.0"

"@hotwired/turbo@^8.0.12":
version "8.0.12"
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.12.tgz#50aa8345d7f62402680c6d2d9814660761837001"
integrity sha512-l3BiQRkD7qrnQv6ms6sqPLczvwbQpXt5iAVwjDvX0iumrz6yEonQkNAzNjeDX25/OJMFDTxpHjkJZHGpM9ikWw==

"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==

"@rails/actioncable@^7.0":
version "7.2.200"
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.2.200.tgz#7f56b3313762dbb85b64490aa33c5f431419aafd"
integrity sha512-gVmi3MabEa+Bkatvw0/k1Y3WTidcIf3qNbb9L8qc+AmT2UmkVqUZhJpSLvKmH10twCYIGzn7yySW/lOpg81Duw==

"@rails/ujs@^7.0.1":
version "7.1.500"
resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.1.500.tgz#84bb037b6a823ec7fb7782a2ac03452deb505128"
Expand Down

0 comments on commit 4b67711

Please sign in to comment.