Add multiple file/directory deletion in the filemanager (#544)
* Add deletion of multiple selected files * Adjust success/failure text to properly represent multiple files * Actually update the minimized versions with the new code * Use let instead of var and seperate items into seperate code tags * Deleting the selected items now supports the new endpoint * Replaced the select buttons with checkboxes * Selections is now handled by find all the selected checkboxes * Add a warning if no files/folders are selected when pressing delete * Add a select all files/folders checkbox * Move mass delete button into a mass actions dropdown * Move style to css file * Actually update the minimized files (again) * Mass actions button is now disabled by default * Clicking on a row selects the checkbox and enables the actions button * Fix clicking anything else but the row or checkbox triggering selection
This commit is contained in:
parent
0def41740a
commit
e64eb4901e
7 changed files with 203 additions and 13 deletions
|
@ -295,3 +295,25 @@ input.form-autocomplete-stop[readonly] {
|
||||||
background: white;
|
background: white;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-massactions {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-files {
|
||||||
|
position: relative;
|
||||||
|
bottom: 1px;
|
||||||
|
margin-right: 7px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-file {
|
||||||
|
position: relative;
|
||||||
|
bottom: 1px;
|
||||||
|
margin-right: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-folder {
|
||||||
|
position: relative;
|
||||||
|
bottom: 1px;
|
||||||
|
margin-right: 5px !important;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -292,12 +292,17 @@ class ActionsClass {
|
||||||
showLoaderOnConfirm: true
|
showLoaderOnConfirm: true
|
||||||
}, () => {
|
}, () => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'POST',
|
||||||
url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/f/${delPath}${delName}`,
|
|
||||||
headers: {
|
headers: {
|
||||||
'X-Access-Token': Pterodactyl.server.daemonSecret,
|
'X-Access-Token': Pterodactyl.server.daemonSecret,
|
||||||
'X-Access-Server': Pterodactyl.server.uuid,
|
'X-Access-Server': Pterodactyl.server.uuid,
|
||||||
}
|
},
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/delete`,
|
||||||
|
timeout: 10000,
|
||||||
|
data: JSON.stringify({
|
||||||
|
items: [`${delPath}${delName}`]
|
||||||
|
}),
|
||||||
}).done(data => {
|
}).done(data => {
|
||||||
nameBlock.parent().addClass('warning').delay(200).fadeOut();
|
nameBlock.parent().addClass('warning').delay(200).fadeOut();
|
||||||
swal({
|
swal({
|
||||||
|
@ -316,6 +321,125 @@ class ActionsClass {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleMassActions() {
|
||||||
|
if ($('#file_listing input[type="checkbox"]:checked').length) {
|
||||||
|
$('#mass_actions').removeClass('disabled');
|
||||||
|
} else {
|
||||||
|
$('#mass_actions').addClass('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleHighlight(event) {
|
||||||
|
const parent = $(event.currentTarget);
|
||||||
|
const item = $(event.currentTarget).find('input');
|
||||||
|
|
||||||
|
if($(item).is(':checked')) {
|
||||||
|
$(item).prop('checked', false);
|
||||||
|
parent.removeClass('warning').delay(200);
|
||||||
|
} else {
|
||||||
|
$(item).prop('checked', true);
|
||||||
|
parent.addClass('warning').delay(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightAll(event) {
|
||||||
|
let parent;
|
||||||
|
const item = $(event.currentTarget).find('input');
|
||||||
|
|
||||||
|
if($(item).is(':checked')) {
|
||||||
|
$('#file_listing input[type=checkbox]').prop('checked', false);
|
||||||
|
$('#file_listing input[data-action="addSelection"]').each(function() {
|
||||||
|
parent = $(this).closest('tr');
|
||||||
|
parent.removeClass('warning').delay(200);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$('#file_listing input[type=checkbox]').prop('checked', true);
|
||||||
|
$('#file_listing input[data-action="addSelection"]').each(function() {
|
||||||
|
parent = $(this).closest('tr');
|
||||||
|
parent.addClass('warning').delay(200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelected() {
|
||||||
|
let selectedItems = [];
|
||||||
|
let selectedItemsElements = [];
|
||||||
|
let parent;
|
||||||
|
let nameBlock;
|
||||||
|
let delLocation;
|
||||||
|
|
||||||
|
$('#file_listing input[data-action="addSelection"]:checked').each(function() {
|
||||||
|
parent = $(this).closest('tr');
|
||||||
|
nameBlock = $(parent).find('td[data-identifier="name"]');
|
||||||
|
delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));
|
||||||
|
|
||||||
|
selectedItems.push(delLocation);
|
||||||
|
selectedItemsElements.push(parent);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedItems.length != 0)
|
||||||
|
{
|
||||||
|
let formattedItems = "";
|
||||||
|
$.each(selectedItems, function(key, value) {
|
||||||
|
formattedItems += ("<code>" + value + "</code>, ");
|
||||||
|
})
|
||||||
|
|
||||||
|
formattedItems = formattedItems.slice(0, -2);
|
||||||
|
|
||||||
|
swal({
|
||||||
|
type: 'warning',
|
||||||
|
title: '',
|
||||||
|
text: 'Are you sure you want to delete:' + formattedItems + '? There is <strong>no</strong> reversing this action.',
|
||||||
|
html: true,
|
||||||
|
showCancelButton: true,
|
||||||
|
showConfirmButton: true,
|
||||||
|
closeOnConfirm: false,
|
||||||
|
showLoaderOnConfirm: true
|
||||||
|
}, () => {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Access-Token': Pterodactyl.server.daemonSecret,
|
||||||
|
'X-Access-Server': Pterodactyl.server.uuid,
|
||||||
|
},
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/delete`,
|
||||||
|
timeout: 10000,
|
||||||
|
data: JSON.stringify({
|
||||||
|
items: selectedItems
|
||||||
|
}),
|
||||||
|
}).done(data => {
|
||||||
|
$('#file_listing input:checked').each(function() {
|
||||||
|
$(this).prop('checked', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(selectedItemsElements, function() {
|
||||||
|
$(this).addClass('warning').delay(200).fadeOut();
|
||||||
|
})
|
||||||
|
|
||||||
|
swal({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Files Deleted'
|
||||||
|
});
|
||||||
|
}).fail(jqXHR => {
|
||||||
|
console.error(jqXHR);
|
||||||
|
swal({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Whoops!',
|
||||||
|
html: true,
|
||||||
|
text: 'An error occured while attempting to delete these files. Please try again.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
swal({
|
||||||
|
type: 'warning',
|
||||||
|
title: '',
|
||||||
|
text: 'Please select files/folders to delete.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
decompress() {
|
decompress() {
|
||||||
const nameBlock = $(this.element).find('td[data-identifier="name"]');
|
const nameBlock = $(this.element).find('td[data-identifier="name"]');
|
||||||
const compPath = decodeURIComponent(nameBlock.data('path'));
|
const compPath = decodeURIComponent(nameBlock.data('path'));
|
||||||
|
|
|
@ -45,6 +45,10 @@ class FileManager {
|
||||||
ContextMenu.run();
|
ContextMenu.run();
|
||||||
this.reloadFilesButton();
|
this.reloadFilesButton();
|
||||||
this.addFolderButton();
|
this.addFolderButton();
|
||||||
|
this.selectItem();
|
||||||
|
this.selectAll();
|
||||||
|
this.selectiveDeletion();
|
||||||
|
this.selectRow();
|
||||||
if (_.isFunction(next)) {
|
if (_.isFunction(next)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -83,12 +87,42 @@ class FileManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectItem() {
|
||||||
|
$('[data-action="addSelection"]').on('click', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll() {
|
||||||
|
$('[data-action="selectAll"]').on('click', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectiveDeletion() {
|
||||||
|
$('[data-action="selective-deletion"]').on('mousedown', event => {
|
||||||
|
new ActionsClass().deleteSelected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addFolderButton() {
|
addFolderButton() {
|
||||||
$('[data-action="add-folder"]').unbind().on('click', () => {
|
$('[data-action="add-folder"]').unbind().on('click', () => {
|
||||||
new ActionsClass().folder($('#file_listing').data('current-dir') || '/');
|
new ActionsClass().folder($('#file_listing').data('current-dir') || '/');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectRow() {
|
||||||
|
$('#file_listing tr').on('mousedown', event => {
|
||||||
|
if($(event.target).is('th') || $(event.target).is('input[data-action="selectAll"]')) {
|
||||||
|
new ActionsClass().highlightAll(event);
|
||||||
|
} else if($(event.target).is('td') || $(event.target).is('input[data-action="addSelection"]')) {
|
||||||
|
new ActionsClass().toggleHighlight(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
new ActionsClass().toggleMassActions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
decodeHash() {
|
decodeHash() {
|
||||||
return decodeURIComponent(window.location.hash.substring(1));
|
return decodeURIComponent(window.location.hash.substring(1));
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,8 @@ return [
|
||||||
'last_modified' => 'Last Modified',
|
'last_modified' => 'Last Modified',
|
||||||
'add_new' => 'Add New File',
|
'add_new' => 'Add New File',
|
||||||
'add_folder' => 'Add New Folder',
|
'add_folder' => 'Add New Folder',
|
||||||
|
'mass_actions' => 'Mass actions',
|
||||||
|
'delete' => 'Delete',
|
||||||
'edit' => [
|
'edit' => [
|
||||||
'header' => 'Edit File',
|
'header' => 'Edit File',
|
||||||
'header_sub' => 'Make modifications to a file from the web.',
|
'header_sub' => 'Make modifications to a file from the web.',
|
||||||
|
|
|
@ -21,6 +21,14 @@
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">/home/container{{ $directory['header'] }}</h3>
|
<h3 class="box-title">/home/container{{ $directory['header'] }}</h3>
|
||||||
<div class="box-tools pull-right">
|
<div class="box-tools pull-right">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" id="mass_actions" class="btn btn-sm btn-info dropdown-toggle disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@lang('server.files.mass_actions') <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-massactions">
|
||||||
|
<li><a href="#" id="selective-deletion" data-action="selective-deletion">@lang('server.files.delete') <i class="fa fa-fw fa-trash-o"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<button class="btn btn-sm btn-success btn-icon" data-action="add-folder">
|
<button class="btn btn-sm btn-success btn-icon" data-action="add-folder">
|
||||||
<i class="fa fa-fw fa-folder-open-o"></i>
|
<i class="fa fa-fw fa-folder-open-o"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -38,13 +46,13 @@
|
||||||
<table class="table table-hover" id="file_listing" data-current-dir="{{ $directory['header'] }}">
|
<table class="table table-hover" id="file_listing" data-current-dir="{{ $directory['header'] }}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:2%;" class="middle text-center">
|
<th style="width:4%;" class="middle">
|
||||||
<i class="fa fa-refresh muted muted-hover use-pointer" data-action="reload-files" style="font-size:14px;"></i>
|
<input type="checkbox" class="select-all-files" data-action="selectAll"><i class="fa fa-refresh muted muted-hover use-pointer" data-action="reload-files" style="font-size:14px;"></i>
|
||||||
</th>
|
</th>
|
||||||
<th style="width:55%">@lang('server.files.file_name')</th>
|
<th style="width:55%">@lang('server.files.file_name')</th>
|
||||||
<th style="width:15%" class="hidden-xs">@lang('server.files.size')</th>
|
<th style="width:15%" class="hidden-xs">@lang('server.files.size')</th>
|
||||||
<th style="width:20%" class="hidden-xs">@lang('server.files.last_modified')</th>
|
<th style="width:20%" class="hidden-xs">@lang('server.files.last_modified')</th>
|
||||||
<th style="width:8%"></th>
|
<th style="width:6%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="append_files_to">
|
<tbody id="append_files_to">
|
||||||
|
@ -70,7 +78,7 @@
|
||||||
@endif
|
@endif
|
||||||
@foreach ($folders as $folder)
|
@foreach ($folders as $folder)
|
||||||
<tr data-type="folder">
|
<tr data-type="folder">
|
||||||
<td data-identifier="type" class="middle"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
|
<td data-identifier="type" class="middle"><input type="checkbox" class="select-folder" data-action="addSelection"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
|
||||||
<td data-identifier="name" data-name="{{ rawurlencode($folder['entry']) }}" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}@endif/">
|
<td data-identifier="name" data-name="{{ rawurlencode($folder['entry']) }}" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}@endif/">
|
||||||
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a>
|
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -85,12 +93,12 @@
|
||||||
{{ $carbon->diffForHumans() }}
|
{{ $carbon->diffForHumans() }}
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></button></td>
|
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
@foreach ($files as $file)
|
@foreach ($files as $file)
|
||||||
<tr data-type="file" data-mime="{{ $file['mime'] }}">
|
<tr data-type="file" data-mime="{{ $file['mime'] }}">
|
||||||
<td data-identifier="type" class="middle">
|
<td data-identifier="type" class="middle"><input type="checkbox" class="select-file" data-action="addSelection">
|
||||||
{{-- oh boy --}}
|
{{-- oh boy --}}
|
||||||
@if(in_array($file['mime'], [
|
@if(in_array($file['mime'], [
|
||||||
'application/x-7z-compressed',
|
'application/x-7z-compressed',
|
||||||
|
@ -162,7 +170,7 @@
|
||||||
{{ $carbon->diffForHumans() }}
|
{{ $carbon->diffForHumans() }}
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></button></td>
|
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
Loading…
Reference in a new issue