sftpgo-mirror/templates/webclient/files.html
Nicola Murino 6ebe7691db
WebClient: add drag and drop upload UI
thanks to @wooneusean for the help

Fixes #951

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-11-19 12:31:03 +01:00

1247 lines
No EOL
50 KiB
HTML

<!--
Copyright (C) 2019-2022 Nicola Murino
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
{{template "base" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/dataTables.checkboxes.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/lightbox2/css/lightbox.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/video-js/video-js.min.css" rel="stylesheet" />
<link href="{{.StaticURL}}/vendor/filepond/filepond.min.css" rel="stylesheet" />
<style>
div.dataTables_wrapper span.selected-info,
div.dataTables_wrapper span.selected-item {
margin-left: 0.5em;
}
</style>
{{end}}
{{define "page_body"}}
<div id="errorMsg" class="card mb-4 border-left-warning" style="display: none;">
<div id="errorTxt" class="card-body text-form-error"></div>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold"><a href="{{.FilesURL}}?path=%2F"><i class="fas fa-home"></i>&nbsp;Home</a>&nbsp;{{range .Paths}}{{if eq .Href ""}}/{{.DirName}}{{else}}<a href="{{.Href}}">/{{.DirName}}</a>{{end}}{{end}}</h6>
</div>
<div class="card-body">
{{if .Error}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.Error}}</div>
</div>
{{end}}
<div id="tableContainer" class="table-responsive">
<table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Name</th>
<th>Size</th>
<th>Last modified</th>
<th></th>
<th></th>
</tr>
</thead>
</table>
</div>
</div>
</div>
{{end}}
{{define "dialog"}}
<div class="modal fade" id="createDirModal" tabindex="-1" role="dialog" aria-labelledby="createDirModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createDirModalLabel">
Create a new directory
</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="create_dir_form" action="" method="POST">
<div class="modal-body">
<div class="form-group">
<label for="directory_name" class="col-form-label">Name</label>
<input type="text" class="form-control" id="directory_name" required>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="uploadFilesModal" tabindex="-1" role="dialog" aria-labelledby="uploadFilesModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="uploadFilesModalLabel">
Upload one or more files
</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="upload_files_form" action="{{.FilesURL}}?path={{.CurrentDir}}" method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div id="uploadErrorMsg" class="card mb-4 border-left-warning" style="display: none;">
<div id="uploadErrorTxt" class="card-body text-form-error"></div>
</div>
<input type="file" id="files_name" name="filenames" required multiple>
</div>
<div class="modal-footer">
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="renameModalLabel">
Rename the selected item
</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="rename_form" action="" method="POST">
<div class="modal-body">
<div class="form-group">
<label for="rename_old_name" class="col-form-label">Old name</label>
<input type="text" class="form-control" id="rename_old_name" readonly>
</div>
<div class="form-group">
<label for="rename_new_dir" class="col-form-label">New base dir</label>
<input type="text" class="form-control" id="rename_new_dir" required aria-describedby="renameNewDirHelpBlock">
<small id="renameNewDirHelpBlock" class="form-text text-muted">
Setting a directory other than the current one will move the item there. This directory must exists
</small>
</div>
<div class="form-group">
<label for="rename_new_name" class="col-form-label">New name</label>
<input type="text" class="form-control" id="rename_new_name" required>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">
Confirmation required
</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">Do you want to delete the selected item/s?</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">
Cancel
</button>
<a class="btn btn-warning" href="#" onclick="deleteAction()">
Delete
</a>
</div>
</div>
</div>
</div>
<div class="modal fade" id="videoModal" tabindex="-1" role="dialog" aria-labelledby="videoModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="videoModalLabel">
<span id="video_title"></span>
</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<video id="video_player" class="video-js vjs-big-play-centered vjs-fluid">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video</p>
</video>
</div>
</div>
</div>
</div>
<div class="modal fade" id="spinnerModal" tabindex="-1" role="dialog" data-keyboard="false" data-backdrop="static">
<div class="modal-dialog modal-dialog-centered justify-content-center" role="document">
<span style="color: #333333;" class="fa fa-spinner fa-spin fa-3x"></span>
<!-- <span id="uploadProgress" style="color: #3A3B3C;" class="mx-3"></span> -->
</div>
</div>
{{end}}
{{define "extra_js"}}
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.checkboxes.min.js"></script>
<script src="{{.StaticURL}}/vendor/lightbox2/js/lightbox.min.js"></script>
<script src="{{.StaticURL}}/vendor/pdfobject/pdfobject.min.js"></script>
<script src="{{.StaticURL}}/vendor/codemirror/codemirror.js"></script>
<script src="{{.StaticURL}}/vendor/codemirror/meta.js"></script>
<script src="{{.StaticURL}}/vendor/video-js/video.min.js"></script>
<script src="{{.StaticURL}}/vendor/filepond/filepond.min.js"></script>
{{if .HasIntegrations}}
<script type="text/javascript">
var childReference = null;
var checkerStarted = false;
const childProps = new Map();
function openExternalURL(url, fileLink, fileName){
if (childReference == null || childReference.closed) {
childProps.set('link', fileLink);
childProps.set('url', url);
childProps.set('file_name', UnicodeDecodeB64(fileName));
childReference = window.open(url, '_blank');
if (!checkerStarted){
keepAlive();
setInterval(checkExternalWindow, 300000);
checkerStarted = true;
}
} else {
$('#errorTxt').text('An external window is already open, please close it before trying to open a new one');
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 8000);
}
}
function notifyBlobDownloadError(message) {
if (childReference == null || childReference.closed) {
console.log("external window null or closed, cannot notify download error");
return;
}
childReference.postMessage({
type: 'blobDownloadError',
message: message
}, childProps.get('url'));
}
function notifySave(status, message) {
if (childReference == null || childReference.closed) {
console.log("external window null or closed, cannot notify save");
return;
}
childReference.postMessage({
type: 'blobSaveResult',
status: status,
message: message
}, childProps.get('url'));
}
window.addEventListener('message', (event) => {
var url = childProps.get('url');
if (!url || !url.startsWith(event.origin)){
console.log("origin: "+event.origin+" does not match the expected one: "+url+" refusing message");
return;
}
if (childReference == null || childReference.closed) {
console.log("external window null or closed, refusing message");
return;
}
switch (event.data.type){
case 'ready':
// the child is ready send some details
childReference.postMessage({
type: 'readyResponse',
user: '{{.LoggedUser.Username}}',
file_name: childProps.get('file_name')
}, childProps.get('url'));
break;
case 'sendBlob':
// we have to download the blob, this could require some time so
// we first send a blobDownloadStart message so the child can
// show a spinner or something similar
var errorMessage = "Error downloading file";
childReference.postMessage({
type: 'blobDownloadStart'
}, childProps.get('url'));
// download the file and send it as blob to the child window
async function downloadFileAsBlob(){
var errorMessage = "Error downloading file";
let response;
try {
response = await fetch(childProps.get('link'),{
headers: {
'X-CSRF-TOKEN': '{{.CSRFToken}}'
},
credentials: 'same-origin',
redirect: 'error'
});
} catch (e){
throw Error(errorMessage+": " +e.message);
}
if (response.status == 200){
let responseBlob;
try {
responseBlob = await response.blob();
} catch (e){
throw Error(errorMessage+" as blob: " +e.message);
}
let fileBlob = new File([responseBlob], childProps.get('file_name'), {type: responseBlob.type, lastModified: ""});
childReference.postMessage({
type: 'blob',
file: fileBlob
}, childProps.get('url'));
} else {
let jsonResponse;
try {
jsonResponse = await response.json();
} catch(e){
throw Error(errorMessage);
}
if (jsonResponse.message) {
errorMessage = jsonResponse.message;
}
if (jsonResponse.error) {
errorMessage += ": " + jsonResponse.error;
}
throw Error(errorMessage);
}
}
downloadFileAsBlob().catch(function(error){
notifyBlobDownloadError(error.message);
$('#errorTxt').text(error.message);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 5000);
});
break;
case 'saveBlob':
spinnerDone = false;
$('#spinnerModal').modal('show');
async function saveBlob() {
var errorMessage = "Error saving external file";
var uploadPath = '{{.FileURL}}?path={{.CurrentDir}}'+encodeURIComponent("/"+unescapeHTML(childProps.get('file_name')));
let response;
try {
response = await fetch(uploadPath, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{.CSRFToken}}'
},
credentials: 'same-origin',
redirect: 'error',
body: event.data.file
});
} catch (e){
throw Error(errorMessage+": " +e.message);
}
if (response.status == 201){
$('#spinnerModal').modal('hide');
notifySave("OK", "");
setTimeout(function () {
location.reload();
}, 2000);
} else {
let jsonResponse;
try {
jsonResponse = await response.json();
} catch(e){
throw Error(errorMessage);
}
if (jsonResponse.message) {
errorMessage = jsonResponse.message;
}
if (jsonResponse.error) {
errorMessage += ": " + jsonResponse.error;
}
throw Error(errorMessage);
}
}
saveBlob().catch(function(error){
$('#spinnerModal').modal('hide');
notifySave("KO", error.message);
$('#errorTxt').text(error.message);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 5000);
});
break;
default:
console.log("Unsupported message: "+JSON.stringify(event.data));
}
});
function checkExternalWindow() {
if (childReference == null || childReference.closed) {
return;
}
keepAlive();
}
</script>
{{end}}
<script type="text/javascript">
var spinnerDone = false;
var player;
var playerKeepAlive;
function shortenData(d, cutoff) {
if ( typeof d !== 'string' ) {
return d;
}
if ( d.length <= cutoff ) {
return escapeHTML(d);
}
var shortened = d.substr(0, cutoff-1);
return escapeHTML(shortened)+'&#8230;';
}
function openVideoPlayer(name, url, videoType){
$("#video_title").text(UnicodeDecodeB64(name));
$('#videoModal').modal('show');
player.src({
type: videoType,
src: url
});
keepAlive();
playerKeepAlive = setInterval(keepAlive, 300000);
}
function getIconForFile(filename) {
var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
switch (extension) {
case "doc":
case "docx":
case "odt":
case "wps":
return "far fa-file-word";
case "ppt":
case "pptx":
return "far fa-file-powerpoint";
case "xls":
case "xlsx":
case "ods":
return "far fa-file-excel";
case "pdf":
return "far fa-file-pdf";
case "webm":
case "mkv":
case "flv":
case "vob":
case "ogv":
case "ogg":
case "avi":
case "ts":
case "mov":
case "wmv":
case "asf":
case "mpeg":
case "mpv":
case "3gp":
case "mp4":
return "far fa-file-video";
case "jpeg":
case "jpg":
case "png":
case "gif":
case "webp":
case "tiff":
case "psd":
case "bmp":
case "svg":
case "jp2":
return "far fa-file-image";
case "go":
case "sh":
case "bat":
case "java":
case "php":
case "cs":
case "asp":
case "aspx":
case "css":
case "html":
case "xhtml":
case "htm":
case "js":
case "jsp":
case "py":
case "rb":
case "cgi":
case "c":
case "cpp":
case "h":
case "hpp":
case "kt":
case "ktm":
case "kts":
case "swift":
case "r":
return "far fa-file-code";
case "zip":
case "zipx":
case "7z":
case "rar":
case "tar":
case "gz":
case "bz2":
case "zstd":
case "zst":
case "sz":
case "lz":
case "lz4":
case "xz":
case "jar":
return "far fa-file-archive";
case "txt":
case "rtf":
case "json":
case "xml":
case "yaml":
case "toml":
case "log":
case "csv":
case "ini":
case "cfg":
return "far fa-file-alt";
default:
return "far fa-file";
}
}
function getNameFromMeta(meta) {
return meta.split('_').slice(1).join('_');
}
function getTypeFromMeta(meta) {
return meta.split('_')[0];
}
function deleteAction() {
var table = $('#dataTable').DataTable();
table.button('delete:name').enable(false);
var selectedItems = table.column(0).checkboxes.selected()
var has_errors = false;
var index = 0;
var success = 0;
spinnerDone = false;
$('#deleteModal').modal('hide');
$('#spinnerModal').modal('show');
function deleteItem() {
if (index >= selectedItems.length || has_errors){
$('#spinnerModal').modal('hide');
spinnerDone = true;
if (!has_errors){
location.reload();
} else {
table.button('delete:name').enable(true);
}
return;
}
var selected = selectedItems[index];
var itemType = getTypeFromMeta(selected);
var itemName = getNameFromMeta(selected);
var path;
var reqTimeout = 15000;
if (itemType == "1"){
path = '{{.DirsURL}}';
reqTimeout = 90000
} else {
path = '{{.FilesURL}}';
}
path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName);
$.ajax({
url: path,
type: 'DELETE',
dataType: 'json',
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
timeout: reqTimeout,
success: function (result) {
index++;
success++;
deleteItem();
},
error: function ($xhr, textStatus, errorThrown) {
index++;
has_errors = true;
var txt = "Unable to delete the selected item/s";
if (success > 0){
txt = "Not all the selected items have been deleted, please reload the page";
}
if ($xhr) {
var json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 10000);
deleteItem();
}
});
}
deleteItem();
}
function keepAlive() {
$.ajax({
url: '{{.ProfileURL}}',
timeout: 15000
});
}
const isDirectoryEntry = item => isEntry(item) && (getAsEntry(item) || {}).isDirectory;
const isEntry = item => 'webkitGetAsEntry' in item;
const getAsEntry = item => item.webkitGetAsEntry();
$(document).ready(function () {
player = videojs('video_player', {
controls: true,
autoplay: false,
preload: 'auto'
});
$('#videoModal').on('hide.bs.modal', function () {
player.pause();
player.reset();
if (playerKeepAlive != null){
clearInterval(playerKeepAlive);
}
});
$('#spinnerModal').on('shown.bs.modal', function () {
if (spinnerDone){
$('#spinnerModal').modal('hide');
}
});
{{if .CanAddFiles}}
FilePond.create(document.getElementById("files_name"),{
allowMultiple: true,
name: 'filenames',
maxFiles: 30,
credits: false,
required: true,
onwarning: function(error){
if (error.code == 0){
$('#uploadErrorTxt').text('You can upload a maximum of 30 files');
$('#uploadErrorMsg').show();
setTimeout(function () {
$('#uploadErrorMsg').hide();
}, 10000);
}
},
beforeAddFile: (fileItem) => new Promise(resolve => {
let num = 0;
FilePond.find(document.getElementById("files_name")).getFiles().forEach(function(val){
if (val.filename == fileItem.filename){
num++;
}
});
resolve(num == 1);
})
});
$('#tableContainer').on("dragover", function(ev){
ev.preventDefault();
$('#tableContainer').css('opacity','0.5');
});
$('#tableContainer').on("dragend dragleave", function(ev){
ev.preventDefault();
$('#tableContainer').css('opacity','1');
});
$('#tableContainer').on("drop", function(ev){
ev.preventDefault();
$('#tableContainer').css('opacity','1');
let filesDropped = false;
if (ev.originalEvent.dataTransfer.items) {
[...ev.originalEvent.dataTransfer.items].forEach((item, i) => {
if (item.kind === 'file') {
// if this is a directory just open the upload dialog
if (!isDirectoryEntry(item)){
FilePond.find(document.getElementById("files_name")).addFile(item.getAsFile());
}
filesDropped = true;
}
});
} else {
[...ev.originalEvent.dataTransfer.files].forEach((file, i) => {
FilePond.find(document.getElementById("files_name")).addFile(file);
filesDropped = true;
});
}
if (filesDropped && !$('#uploadFilesModal').hasClass('show')){
$('#uploadFilesModal').modal('show');
}
});
{{end}}
$("#create_dir_form").submit(function (event) {
event.preventDefault();
$('#createDirModal').modal('hide');
var dirName = replaceSlash($("#directory_name").val());
var path = '{{.DirsURL}}?path={{.CurrentDir}}' + encodeURIComponent("/"+dirName);
$.ajax({
url: path,
type: 'POST',
dataType: 'json',
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
timeout: 15000,
success: function (result) {
location.reload();
},
error: function ($xhr, textStatus, errorThrown) {
var txt = "Unable to create the requested directory";
if ($xhr) {
var json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 5000);
}
});
});
$("#upload_files_form").submit(function (event){
event.preventDefault();
keepAlive();
var keepAliveTimer = setInterval(keepAlive, 300000);
var files = FilePond.find(document.getElementById("files_name")).getFiles();
var has_errors = false;
var index = 0;
var success = 0;
spinnerDone = false;
$('#uploadFilesModal').modal('hide');
$('#spinnerModal').modal('show');
function uploadFile() {
if (index >= files.length || has_errors){
//console.log("upload done, index: "+index+" has errors: "+has_errors+" ok: "+success);
clearInterval(keepAliveTimer);
$('#spinnerModal').modal('hide');
spinnerDone = true;
if (!has_errors){
location.reload();
}
return;
}
async function saveFile() {
//console.log("save file, index: "+index);
var errorMessage = "Error uploading files";
let response;
try {
var f = files[index].file;
var uploadPath = '{{.FileURL}}?path={{.CurrentDir}}'+encodeURIComponent("/"+f.name);
var lastModified;
try {
lastModified = f.lastModified;
} catch (e) {
console.log("unable to get last modified time from file: "+e.message);
lastModified = "";
}
response = await fetch(uploadPath, {
method: 'POST',
headers: {
'X-SFTPGO-MTIME': lastModified,
'X-CSRF-TOKEN': '{{.CSRFToken}}'
},
credentials: 'same-origin',
redirect: 'error',
body: f
});
} catch (e){
throw Error(errorMessage+": " +e.message);
}
if (response.status == 201){
index++;
success++;
uploadFile();
} else {
let jsonResponse;
try {
jsonResponse = await response.json();
} catch(e){
throw Error(errorMessage);
}
if (jsonResponse.message) {
errorMessage = jsonResponse.message;
}
if (jsonResponse.error) {
errorMessage += ": " + jsonResponse.error;
}
throw Error(errorMessage);
}
}
saveFile().catch(function(error){
index++;
has_errors = true;
$('#errorTxt').text(error.message);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 10000);
uploadFile();
});
}
uploadFile();
});
$("#rename_form").submit(function (event){
event.preventDefault();
var table = $('#dataTable').DataTable();
table.button('rename:name').enable(false);
var selected = table.column(0).checkboxes.selected()[0];
var itemType = getTypeFromMeta(selected);
var itemName = getNameFromMeta(selected);
var targetName = replaceSlash($("#rename_new_name").val());
var targetDir = $("#rename_new_dir").val();
if (targetDir != "/") {
targetDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir;
}
if (targetDir.trim() == ""){
targetDir = "{{.CurrentDir}}";
} else {
targetDir = encodeURIComponent(targetDir);
}
var path;
if (itemType == "1"){
path = '{{.DirsURL}}';
} else {
path = '{{.FilesURL}}';
}
path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName)+'&target='+targetDir+encodeURIComponent("/"+targetName);
$('#renameModal').modal('hide');
$.ajax({
url: path,
type: 'PATCH',
dataType: 'json',
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
timeout: 15000,
success: function (result) {
location.reload();
},
error: function ($xhr, textStatus, errorThrown) {
var txt = "Error renaming item";
if ($xhr) {
var json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 8000);
var selectedItems = table.column(0).checkboxes.selected().length;
table.button('rename:name').enable(selectedItems == 1);
}
});
});
$.fn.dataTable.ext.buttons.refresh = {
text: '<i class="fas fa-sync-alt"></i>',
name: 'refresh',
titleAttr: "Refresh",
action: function (e, dt, node, config) {
location.reload();
}
};
$.fn.dataTable.ext.buttons.download = {
text: '<i class="fas fa-download"></i>',
name: 'download',
titleAttr: "Download Zip",
action: function (e, dt, node, config) {
var filesArray = [];
var selected = dt.column(0).checkboxes.selected();
for (i = 0; i < selected.length; i++) {
filesArray.push(getNameFromMeta(selected[i]));
}
var files = encodeURIComponent(JSON.stringify(filesArray));
var downloadURL = '{{.DownloadURL}}';
var currentDir = '{{.CurrentDir}}';
var ts = new Date().getTime().toString();
window.location = `${downloadURL}?path=${currentDir}&files=${files}&_=${ts}`;
},
enabled: false
};
$.fn.dataTable.ext.buttons.addFiles = {
text: '<i class="fas fa-file-upload"></i>',
name: 'addFiles',
titleAttr: "Upload files",
action: function (e, dt, node, config) {
//FilePond.find(document.getElementById("files_name")).removeFiles();
$('#uploadFilesModal').modal('show');
},
enabled: true
};
$.fn.dataTable.ext.buttons.addDirectory = {
text: '<i class="fas fa-folder-plus"></i>',
name: 'addDirectory',
titleAttr: "Add directory",
action: function (e, dt, node, config) {
$("#directory_name").val("");
$('#createDirModal').modal('show');
},
enabled: true
};
$.fn.dataTable.ext.buttons.rename = {
text: '<i class="fas fa-pen"></i>',
name: 'rename',
titleAttr: "Rename",
action: function (e, dt, node, config) {
var selected = table.column(0).checkboxes.selected()[0];
var itemName = getNameFromMeta(selected);
$("#rename_old_name").val(itemName);
$("#rename_new_dir").val(decodeURIComponent("{{.CurrentDir}}".replace(/\+/g, '%20')));
$("#rename_new_name").val("");
$('#renameModal').modal('show');
},
enabled: false
};
$.fn.dataTable.ext.buttons.delete = {
text: '<i class="fas fa-trash"></i>',
name: 'delete',
titleAttr: "Delete",
action: function (e, dt, node, config) {
$('#deleteModal').modal('show');
},
enabled: false
};
$.fn.dataTable.ext.buttons.share = {
text: '<i class="fas fa-share-alt"></i>',
name: 'share',
titleAttr: "Share",
action: function (e, dt, node, config) {
var filesArray = [];
var selected = dt.column(0).checkboxes.selected();
for (i = 0; i < selected.length; i++) {
filesArray.push(getNameFromMeta(selected[i]));
}
var files = encodeURIComponent(JSON.stringify(filesArray));
var shareURL = '{{.ShareURL}}';
var currentDir = '{{.CurrentDir}}';
var ts = new Date().getTime().toString();
window.location = `${shareURL}?path=${currentDir}&files=${files}&_=${ts}`;
},
enabled: false
};
var table = $('#dataTable').DataTable({
"ajax": {
"url": "{{.DirsURL}}?path={{.CurrentDir}}",
"dataSrc": "",
"error": function ($xhr, textStatus, errorThrown) {
$(".dataTables_processing").hide();
var txt = "Failed to get directory listing";
if ($xhr) {
var json = $xhr.responseJSON;
if (json) {
if (json.message){
txt += ": " + json.message;
} else {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
setTimeout(function () {
$('#errorMsg').hide();
}, 10000);
}
},
"deferRender": true,
"processing": true,
"lengthMenu": [ 10, 25, 50, 100, 250, 500 ],
"stateSave": true,
"stateDuration": 0,
"stateSaveParams": function (settings, data) {
data.sftpgo_dir = '{{.CurrentDir}}';
},
"stateLoadParams": function (settings, data) {
if (!data.sftpgo_dir || data.sftpgo_dir != '{{.CurrentDir}}'){
data.start = 0;
data.search.search = "";
}
data.checkboxes = [];
},
"columns": [
{ "data": "meta" },
{ "data": "type" },
{
"data": "name",
"render": function (data, type, row) {
if (type === 'display') {
var title = "";
var cssClass = "";
var shortened = shortenData(data, 70);
data = escapeHTML(data);
if (shortened != data){
title = data;
cssClass = "ellipsis";
}
if (row["type"] == "1") {
return `<i class="fas fa-folder"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
}
if (row["size"] == "") {
return `<i class="fas fa-external-link-alt"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
}
var icon = getIconForFile(data);
return `<i class="${icon}"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
}
return data;
}
},
{ "data": "size" },
{ "data": "last_modified" },
{ "data": "edit_url",
"render": function (data, type, row) {
if (type === 'display') {
var filename = escapeHTML(row["name"]);
var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
if (data){
if (extension == "csv" || extension == "bat" || CodeMirror.findModeByExtension(extension) != null){
{{if .CanAddFiles}}
return `<a href="${data}"><i class="fas fa-edit"></i></a>`;
{{else}}
return `<a href="${data}"><i class="fas fa-eye"></i></a>`;
{{end}}
}
}
if (row["type"] == "2") {
switch (extension) {
case "jpeg":
case "jpg":
case "png":
case "gif":
case "webp":
case "bmp":
case "svg":
case "ico":
var title = escapeHTMLForceSafe(row["name"])
return `<a href="${row['url']}" data-lightbox="image-gallery" data-title="${title}"><i class="fas fa-eye"></i></a>`;
case "mp4":
case "mov":
var name = b64EncodeUnicode(row["name"]);
return `<a href="#" onclick="openVideoPlayer('${name}', '${row['url']}', 'video/mp4');"><i class="fas fa-eye"></i></a>`;
case "webm":
var name = b64EncodeUnicode(row["name"]);
return `<a href="#" onclick="openVideoPlayer('${name}', '${row['url']}', 'video/webm');"><i class="fas fa-eye"></i></a>`;
case "ogv":
case "ogg":
var name = b64EncodeUnicode(row["name"]);
return `<a href="#" onclick="openVideoPlayer('${name}}', '${row['url']}', 'video/ogg');"><i class="fas fa-eye"></i></a>`;
case "pdf":
if (PDFObject.supportsPDFs){
var view_url = row['url'];
view_url = view_url.replace('{{.FilesURL}}','{{.ViewPDFURL}}');
return `<a href="${view_url}" target="_blank"><i class="fas fa-eye"></i></a>`;
}
}
}
}
return "";
}
},
{ "data": "ext_url",
"render": function (data, type, row) {
{{if .HasIntegrations}}
if (type === 'display') {
if (data){
var name = b64EncodeUnicode(escapeHTML(row["name"]));
return `<a href="#" onclick="openExternalURL('${data}', '${row["ext_link"]}', '${name}');"><i class="fas fa-external-link-alt"></i></a>`;
}
}
{{end}}
return "";
}
}
],
"buttons": [],
"lengthChange": true,
"columnDefs": [
{
"targets": [0],
"checkboxes": {
"selectCallback": function (nodes, selected) {
var selectedItems = table.column(0).checkboxes.selected().length;
var selectedText = "";
if (selectedItems == 1) {
selectedText = "1 item selected";
} else if (selectedItems > 1) {
selectedText = `${selectedItems} items selected`;
}
{{if .CanDownload}}
table.button('download:name').enable(selectedItems > 0);
{{end}}
{{if .CanRename}}
table.button('rename:name').enable(selectedItems == 1);
{{end}}
{{if .CanDelete}}
table.button('delete:name').enable(selectedItems > 0);
{{end}}
{{if .CanShare}}
table.button('share:name').enable(selectedItems > 0);
{{end}}
$('#dataTable_info').find('span').remove();
$("#dataTable_info").append('<span class="selected-info"><span class="selected-item">' + selectedText + '</span></span>');
}
},
"orderable": false,
"searchable": false
},
{
"targets": [1],
"visible": false,
"searchable": false
},
{
"targets": [3, 4],
"searchable": false
},
{
"targets": [5, 6],
"orderable": false,
"searchable": false
}
],
"scrollX": false,
"scrollY": false,
"responsive": true,
"language": {
"loadingRecords": "",
"emptyTable": "No files or folders"
},
"initComplete": function (settings, json) {
table.button().add(0, 'refresh');
//table.button().add(0, 'pageLength');
{{if .CanShare}}
table.button().add(0, 'share');
{{end}}
{{if .CanDownload}}
table.button().add(0, 'download');
{{end}}
{{if .CanDelete}}
table.button().add(0, 'delete');
{{end}}
{{if .CanRename}}
table.button().add(0, 'rename');
{{end}}
{{if .CanCreateDirs}}
table.button().add(0, 'addDirectory');
{{end}}
{{if .CanAddFiles}}
table.button().add(0, 'addFiles');
{{end}}
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
},
"orderFixed": [1, 'asc'],
"order": [2, 'asc']
});
new $.fn.dataTable.FixedHeader(table);
$.fn.dataTable.ext.errMode = 'none';
});
</script>
{{end}}