Merge pull request #389 from abf/rosa-build:388-ability-to-remove-files-from-ui
#388: add ability to remove files from UI
This commit is contained in:
commit
6253c025c6
|
@ -45,4 +45,4 @@ var SoundNotificationsHelper = function() {
|
|||
}
|
||||
}
|
||||
}
|
||||
RosaABF.factory('SoundNotificationsHelper', SoundNotificationsHelper);
|
||||
RosaABF.factory('SoundNotificationsHelper', SoundNotificationsHelper);
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
RosaABF.controller 'ContentsController', ['$scope', '$http', '$location', ($scope, $http, $location) ->
|
||||
|
||||
$scope.platform = $('#platform_name').val()
|
||||
$scope.processing = true
|
||||
$scope.contents = []
|
||||
$scope.folders = []
|
||||
$scope.pages = []
|
||||
|
||||
# Fixes: redirect to page after form submit
|
||||
$("#search_contents_form").on 'submit', ->
|
||||
false
|
||||
|
||||
$scope.refresh = ->
|
||||
$scope.processing = true
|
||||
|
||||
params =
|
||||
platform_id: $scope.platform
|
||||
path: $('#path').val()
|
||||
term: $('#term').val()
|
||||
page: $('#page').val()
|
||||
format: 'json'
|
||||
|
||||
$http.get(Routes.platform_contents_path(params)).success( (data) ->
|
||||
$scope.folders = data.folders
|
||||
$scope.contents = data.contents
|
||||
$scope.pages = data.pages
|
||||
$scope.back = data.back
|
||||
$scope.processing = false
|
||||
).error( ->
|
||||
$scope.contents = []
|
||||
$scope.processing = false
|
||||
)
|
||||
true
|
||||
|
||||
$scope.open = (content) ->
|
||||
return if $scope.processing
|
||||
if $.type(content) == 'string'
|
||||
$location.search('path', content)
|
||||
else if content.is_folder
|
||||
$location.search('path', content.subpath)
|
||||
|
||||
$scope.destroy = (content) ->
|
||||
params =
|
||||
path: content.subpath
|
||||
format: 'json'
|
||||
|
||||
content.processing = true
|
||||
$http.delete(Routes.remove_file_platform_contents_path($scope.platform, params)).success( ->
|
||||
$scope.refresh()
|
||||
).error( ->
|
||||
$scope.refresh()
|
||||
)
|
||||
true
|
||||
|
||||
$scope.search = ->
|
||||
$location.search('term', $('#term').val())
|
||||
|
||||
$scope.$on '$locationChangeSuccess', (event) ->
|
||||
$scope.updateParams()
|
||||
$scope.refresh()
|
||||
|
||||
$scope.updateParams = ->
|
||||
params = $location.search()
|
||||
$('#path').val(params['path'])
|
||||
$('#term').val(params['term'])
|
||||
$('#page').val(params['page'])
|
||||
|
||||
$scope.goToPage = (number) ->
|
||||
$location.search('page', number)
|
||||
|
||||
]
|
|
@ -0,0 +1,11 @@
|
|||
RosaABF = angular.module 'RosaABF'
|
||||
|
||||
RosaABF.directive "ngConfirmClick", ->
|
||||
priority: 100
|
||||
restrict: "A"
|
||||
link: (scope, element, attr) ->
|
||||
msg = attr.ngConfirmClick || "Are you sure?"
|
||||
element.bind 'click', (event) ->
|
||||
unless confirm(msg)
|
||||
event.stopImmediatePropagation()
|
||||
event.preventDefault
|
|
@ -2,6 +2,7 @@
|
|||
@import "design/main";
|
||||
@import "design/git";
|
||||
@import "design/common";
|
||||
@import "design/contents";
|
||||
@import "design/custom";
|
||||
@import "design/build_lists_monitoring";
|
||||
@import "design/profile";
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#contents {
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
.path {
|
||||
a {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.text {
|
||||
padding: 5px 10px;
|
||||
background-color: #dcecfa;
|
||||
float: left;
|
||||
height: 14px;
|
||||
}
|
||||
.arrow-right {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 12px solid transparent;
|
||||
border-left: 12px solid #dcecfa;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2047,29 +2047,6 @@ article .activity .top {
|
|||
margin: 2px 0;
|
||||
}
|
||||
|
||||
#contents {
|
||||
.path {
|
||||
a {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.text {
|
||||
padding: 5px 10px;
|
||||
background-color: #dcecfa;
|
||||
float: left;
|
||||
height: 14px;
|
||||
}
|
||||
.arrow-right {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 12px solid transparent;
|
||||
border-left: 12px solid #dcecfa;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
table tbody {
|
||||
td.build-list-statuses {
|
||||
background: #FFF;
|
||||
|
|
|
@ -2,16 +2,28 @@ class Platforms::ContentsController < Platforms::BaseController
|
|||
include PaginateHelper
|
||||
|
||||
before_filter :authenticate_user!
|
||||
skip_before_filter :authenticate_user! if APP_CONFIG['anonymous_access']
|
||||
skip_before_filter :authenticate_user!, only: :index if APP_CONFIG['anonymous_access']
|
||||
|
||||
load_and_authorize_resource :platform
|
||||
|
||||
def index
|
||||
@path = params[:path].to_s
|
||||
@term = params[:term]
|
||||
@contents = PlatformContent.find_by_platform(@platform, @path, @term)
|
||||
.paginate(paginate_params)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
@path = params[:path].to_s
|
||||
@term = params[:term]
|
||||
@contents = PlatformContent.find_by_platform(@platform, @path, @term)
|
||||
.paginate(paginate_params)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def remove_file
|
||||
authorize!(:remove_file, @platform)
|
||||
PlatformContent.remove_file(@platform, params[:path])
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
module ContentsHelper
|
||||
|
||||
def build_content_paths(platform, path)
|
||||
paths = ['/']
|
||||
paths |= path.split('/').select(&:present?)
|
||||
paths.uniq!
|
||||
|
||||
compound_path = ''
|
||||
paths.map do |p|
|
||||
compound_path << p << '/' if p != '/'
|
||||
link_to(platform_content_path(platform, compound_path), {remote: true}) do
|
||||
content_tag(:span, p, {class: 'text'}) +
|
||||
content_tag(:span, '', {class: 'arrow-right'})
|
||||
end
|
||||
end.join.html_safe
|
||||
end
|
||||
|
||||
def platform_content_path(platform, path, name = nil)
|
||||
full_path = platform_contents_path(platform)
|
||||
full_path << '/' << path if path.present?
|
||||
full_path << ('/' << name) if name.present?
|
||||
full_path
|
||||
end
|
||||
|
||||
end
|
|
@ -114,7 +114,7 @@ class Ability
|
|||
can [:read, :related, :members], Platform, owner_type: 'Group', owner_id: user_group_ids
|
||||
can([:read, :related, :members], Platform, read_relations_for('platforms')) {|platform| local_reader? platform}
|
||||
can [:read, :related], Platform, id: user.repositories.pluck(:platform_id)
|
||||
can([:update, :destroy, :change_visibility], Platform) {|platform| owner?(platform) }
|
||||
can([:update, :destroy, :change_visibility, :remove_file], Platform) {|platform| owner?(platform) }
|
||||
can([:local_admin_manage, :members, :add_member, :remove_member, :remove_members] , Platform) {|platform| owner?(platform) || local_admin?(platform) }
|
||||
|
||||
can([:create, :publish], MassBuild) {|mass_build| owner?(mass_build.save_to_platform) || local_admin?(mass_build.save_to_platform)}
|
||||
|
|
|
@ -49,8 +49,11 @@ class PlatformContent
|
|||
end
|
||||
|
||||
def download_url
|
||||
suffix = @path.gsub(/^#{@platform.path}/, '')
|
||||
"#{APP_CONFIG['downloads_url']}/#{@platform.name}#{suffix}"
|
||||
"#{APP_CONFIG['downloads_url']}/#{@platform.name}#{subpath}"
|
||||
end
|
||||
|
||||
def subpath
|
||||
@subpath ||= @path.gsub(/^#{@platform.path}/, '')
|
||||
end
|
||||
|
||||
def self.find_by_platform(platform, path, term)
|
||||
|
@ -58,14 +61,7 @@ class PlatformContent
|
|||
term = (term || '').strip.gsub(/[\\\/]+/, '')
|
||||
.gsub(/[^\w\-\+\.]/, '_')
|
||||
|
||||
path = path.split(File::SEPARATOR).map(&:strip).select(&:present?)
|
||||
.map{ |p|
|
||||
# Strip out the non-ascii character
|
||||
p.gsub(/[\\\/]+/, '')
|
||||
.gsub(/^[\.]+/, '')
|
||||
.gsub(/[^\w\-\.]/, '_')
|
||||
}
|
||||
.join(File::SEPARATOR)
|
||||
path = sanitize_path(path)
|
||||
results = Dir.glob(File.join(platform.path, path, "*#{term}*"))
|
||||
if term
|
||||
results = results.sort_by(&:length)
|
||||
|
@ -75,4 +71,19 @@ class PlatformContent
|
|||
results.map{ |p| PlatformContent.new(platform, p) }
|
||||
end
|
||||
|
||||
def self.remove_file(platform, path)
|
||||
path = File.join(platform.path, sanitize_path(path))
|
||||
FileUtils.rm_f(path) if File.exist?(path)
|
||||
end
|
||||
|
||||
def self.sanitize_path(path)
|
||||
path.split(File::SEPARATOR).map(&:strip).select(&:present?)
|
||||
.map{ |p|
|
||||
# Strip out the non-ascii character
|
||||
p.gsub(/[\\\/]+/, '')
|
||||
.gsub(/^[\.]+/, '')
|
||||
.gsub(/[^\w\-\.]/, '_')
|
||||
}.join(File::SEPARATOR)
|
||||
end
|
||||
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
#contents
|
||||
|
||||
= form_for @platform, url: platform_content_path(@platform, @path), html: { class: :form, remote: true, method: :get } do |f|
|
||||
= tracker_search_field(:term, @term.present? ? @term : t('layout.platforms.search_contents'))
|
||||
= f.submit t('layout.search.header'), data: {'disable-with' => t('layout.processing')}
|
||||
%br
|
||||
|
||||
.path= build_content_paths(@platform, @path)
|
||||
.both
|
||||
|
||||
%table.tablesorter.project{cellpadding: "0", cellspacing: "0"}
|
||||
%tbody
|
||||
|
||||
- if @path.present?
|
||||
%tr
|
||||
%td= link_to '../', platform_content_path(@platform, @path, '../'), {remote: true}
|
||||
%td
|
||||
%td
|
||||
|
||||
- (@contents.select(&:is_folder?) | @contents).each do |content|
|
||||
%tr
|
||||
%td
|
||||
- options = {class: 'files-see'}
|
||||
- if content.is_folder?
|
||||
- pic = 'folder.png'
|
||||
- path = platform_content_path(@platform, @path, content.name)
|
||||
- options[:remote] = true
|
||||
.pic= image_tag pic || 'code.png'
|
||||
.name= link_to(content.name, path || content.download_url, options)
|
||||
%td= link_to t('activerecord.models.build_list'), content.build_list if content.build_list
|
||||
%td= number_to_human_size(content.size) unless content.is_folder?
|
||||
.both
|
||||
|
||||
= will_paginate @contents, {remote: true}
|
|
@ -6,9 +6,53 @@
|
|||
= t('layout.platforms.contents_of')
|
||||
= @platform.name
|
||||
|
||||
= render 'contents'
|
||||
#contents{ 'ng-controller' => 'ContentsController' }
|
||||
|
||||
:javascript
|
||||
$(function(){
|
||||
$('.pagination a').attr('data-remote', 'true');
|
||||
});
|
||||
= form_for @platform, html: { class: :form, 'ng-submit' => 'search()', id: 'search_contents_form' } do |f|
|
||||
= f.hidden_field :name
|
||||
= hidden_field_tag 'path'
|
||||
= hidden_field_tag 'page'
|
||||
%input#term{ name: 'term', type: 'text', placeholder: t('layout.platforms.search_contents') }
|
||||
= f.submit t('layout.search.header'), data: {'disable-with' => t('layout.processing')}
|
||||
%br
|
||||
|
||||
.path
|
||||
%a{ 'ng-repeat' => 'folder in folders' }
|
||||
%span.text{ 'ng-click' => "open(folder.path)" } {{folder.name}}
|
||||
%span.arrow-right
|
||||
.both
|
||||
|
||||
- can_remove = can? :remove_file, @platform
|
||||
%table.tablesorter.project{cellpadding: 0, cellspacing: 0, 'ng-hide' => 'processing' }
|
||||
%tbody
|
||||
|
||||
%tr{ 'ng-show' => 'back' }
|
||||
%td
|
||||
%a{ 'ng-click' => "open(back)" } ../
|
||||
%td
|
||||
%td
|
||||
- if can_remove
|
||||
%td
|
||||
|
||||
%tr{'ng-repeat' => 'content in contents'}
|
||||
%td{ 'ng-show' => 'content.is_folder' }
|
||||
.pic= image_tag 'folder.png'
|
||||
.name
|
||||
%a.files-see{ 'ng-click' => "open(content)" } {{content.name}}
|
||||
%td{ 'ng-hide' => 'content.is_folder' }
|
||||
.pic= image_tag 'code.png'
|
||||
.name
|
||||
%a.files-see{'ng-href' => '{{content.download_url}}'} {{content.name}}
|
||||
%td
|
||||
%a{'ng-href' => '{{content.build_list.url}}', 'ng-show' => 'content.build_list' }
|
||||
= t('activerecord.models.build_list')
|
||||
%td {{content.size}}
|
||||
- if can_remove
|
||||
%td{ 'ng-show' => 'content.is_folder' }
|
||||
%td.buttons{ 'ng-hide' => 'content.is_folder' }
|
||||
%div{ 'ng-show' => 'content.processing' }
|
||||
= t('layout.processing')
|
||||
%span.delete{ 'ng-click' => "destroy(content)", 'ng-hide' => 'content.processing', 'ng-confirm-click' => t('layout.confirm') }
|
||||
.both
|
||||
|
||||
= render 'shared/angularjs_will_paginate'
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
$('#contents').html("#{escape_javascript(render 'contents')}");
|
||||
$('.pagination a').attr('data-remote', 'true');
|
|
@ -0,0 +1,25 @@
|
|||
json.contents (@contents.select(&:is_folder?) | @contents) do |content|
|
||||
json.(content, :name)
|
||||
json.is_folder content.is_folder?
|
||||
|
||||
json.size number_to_human_size(content.size) unless content.is_folder?
|
||||
|
||||
json.download_url content.download_url unless content.is_folder?
|
||||
json.subpath content.subpath
|
||||
|
||||
json.build_list do
|
||||
json.url build_list_path(content.build_list)
|
||||
end if content.build_list
|
||||
end
|
||||
|
||||
paths = @path.split('/').select(&:present?)
|
||||
compound_path = ''
|
||||
json.folders (['/'] | paths) do |folder|
|
||||
compound_path << '/' << folder if folder != '/'
|
||||
json.path compound_path.dup
|
||||
json.name folder
|
||||
end
|
||||
|
||||
json.back paths.size == 1 ? '/' : paths[0...-1].join('/')
|
||||
|
||||
json.pages angularjs_will_paginate(@contents)
|
|
@ -1,3 +1,6 @@
|
|||
/ Add to controller something like:
|
||||
/ $scope.goToPage = (number) ->
|
||||
/ $location.search('page', number)
|
||||
#will_paginate{'ng-show' => 'pages'}
|
||||
.pagination
|
||||
%div{'ng-class' => "{'disabled': !page.active}", 'ng-repeat' => 'page in pages', 'ng-switch' => 'page.type'}
|
||||
|
|
|
@ -175,8 +175,11 @@ Rosa::Application.routes.draw do
|
|||
get :advisories
|
||||
end
|
||||
|
||||
resources :contents, only: [:index]
|
||||
get '/contents/*path' => 'contents#index', format: false
|
||||
resources :contents, only: %i(index) do
|
||||
collection do
|
||||
delete :remove_file
|
||||
end
|
||||
end
|
||||
|
||||
resources :mass_builds, only: [:create, :new, :index] do
|
||||
member do
|
||||
|
|
|
@ -28,6 +28,34 @@ shared_examples_for 'content platform user with show rights' do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'content platform user without owner rights' do
|
||||
it 'should not be able to perform remove_file action for main platform' do
|
||||
get :remove_file, platform_id: @platform, path: '/test'
|
||||
response.should_not be_success
|
||||
end
|
||||
|
||||
it 'should not be able to perform index remove_file for personal platform' do
|
||||
get :remove_file, platform_id: @personal_platform, path: '/test'
|
||||
response.should_not be_success
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'content platform user with owner rights' do
|
||||
before do
|
||||
allow(PlatformContent).to receive(:remove_file)
|
||||
end
|
||||
|
||||
it 'should be able to perform remove_file action for main platform' do
|
||||
get :remove_file, platform_id: @platform, path: '/test'
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it 'should be able to perform remove_file action for personal platform' do
|
||||
get :remove_file, platform_id: @personal_platform, path: '/test'
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
|
||||
describe Platforms::ContentsController do
|
||||
before do
|
||||
stub_symlink_methods
|
||||
|
@ -52,6 +80,7 @@ describe Platforms::ContentsController do
|
|||
|
||||
it_should_behave_like 'content platform user with show rights' if APP_CONFIG['anonymous_access']
|
||||
it_should_behave_like 'content platform user without show rights for hidden platform'
|
||||
it_should_behave_like 'content platform user without owner rights'
|
||||
end
|
||||
|
||||
context 'for global admin' do
|
||||
|
@ -61,17 +90,20 @@ describe Platforms::ContentsController do
|
|||
|
||||
it_should_behave_like 'content platform user with show rights'
|
||||
it_should_behave_like 'content platform user with show rights for hidden platform'
|
||||
it_should_behave_like 'content platform user with owner rights'
|
||||
end
|
||||
|
||||
context 'for owner user' do
|
||||
before do
|
||||
http_login(@user)
|
||||
@platform.owner = @user; @platform.save
|
||||
allow(Platform).to receive(:find).and_return(@platform)
|
||||
allow(@platform).to receive(:owner).and_return(@user)
|
||||
create_relation(@platform, @user, 'admin')
|
||||
end
|
||||
|
||||
it_should_behave_like 'content platform user with show rights'
|
||||
it_should_behave_like 'content platform user with show rights for hidden platform'
|
||||
it_should_behave_like 'content platform user with owner rights'
|
||||
end
|
||||
|
||||
context 'for member of platform' do
|
||||
|
@ -83,6 +115,7 @@ describe Platforms::ContentsController do
|
|||
|
||||
it_should_behave_like 'content platform user with show rights'
|
||||
it_should_behave_like 'content platform user with show rights for hidden platform'
|
||||
it_should_behave_like 'content platform user without owner rights'
|
||||
end
|
||||
|
||||
context 'for simple user' do
|
||||
|
@ -92,6 +125,7 @@ describe Platforms::ContentsController do
|
|||
|
||||
it_should_behave_like 'content platform user with show rights'
|
||||
it_should_behave_like 'content platform user without show rights for hidden platform'
|
||||
it_should_behave_like 'content platform user without owner rights'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue