sftpgo/templates/webclient/files.html
Nicola Murino daf643596d
WebClient: fix icon for 0 byte files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2023-10-29 08:27:00 +01:00

1440 lines
No EOL
59 KiB
HTML

<!--
Copyright (C) 2019-2023 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 "additionalnavitems"}}
{{if .QuotaUsage.HasQuotaInfo}}
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="quotaDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Quota</span>
{{if .QuotaUsage.IsQuotaLow}}
<i class="fas fa-exclamation-triangle fa-fw"></i>
{{else}}
<i class="fas fa-info fa-fw"></i>
{{end}}
</a>
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
ria-labelledby="alertsDropdown">
<h6 class="dropdown-header">
Quota usage
</h6>
{{ if .QuotaUsage.HasDiskQuota}}
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle {{if .QuotaUsage.IsDiskQuotaLow}}bg-warning{{else}}bg-success{{end}}">
<i class="fas fa-hdd text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">Disk quota</div>
{{$size := .QuotaUsage.GetQuotaSize}}
{{$files := .QuotaUsage.GetQuotaFiles}}
{{if $size}}
{{$percentage := .QuotaUsage.GetQuotaSizePercentage}}
<span class="font-weight-bold {{if .QuotaUsage.IsQuotaSizeLow}}text-warning{{end}}">Size: {{$size}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
{{if $files}}<br>{{end}}
{{end}}
{{if $files}}
{{$percentage := .QuotaUsage.GetQuotaFilesPercentage}}
<span class="font-weight-bold {{if .QuotaUsage.IsQuotaFilesLow}}text-warning{{end}}">Files: {{$files}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
{{end}}
</div>
</a>
{{end}}
{{ if .QuotaUsage.HasTranferQuota}}
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle {{if .QuotaUsage.IsTransferQuotaLow}}bg-warning{{else}}bg-success{{end}}">
<i class="fas fa-exchange-alt text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">Transfer quota</div>
{{$total := .QuotaUsage.GetTotalTransferQuota}}
{{$upload := .QuotaUsage.GetUploadTransferQuota}}
{{$download := .QuotaUsage.GetDownloadTransferQuota}}
{{if $total}}
{{$percentage := .QuotaUsage.GetTotalTransferQuotaPercentage}}
<span class="font-weight-bold {{if .QuotaUsage.IsTotalTransferQuotaLow}}text-warning{{end}}">Total: {{$total}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
{{if or $upload $download}}<br>{{end}}
{{end}}
{{if $download}}
{{$percentage := .QuotaUsage.GetDownloadTransferQuotaPercentage}}
<span class="font-weight-bold {{if .QuotaUsage.IsDownloadTransferQuotaLow}}text-warning{{end}}">Download: {{$download}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
{{if $upload}}<br>{{end}}
{{end}}
{{if $upload}}
{{$percentage := .QuotaUsage.GetUploadTransferQuotaPercentage}}
<span class="font-weight-bold {{if .QuotaUsage.IsUploadTransferQuotaLow}}text-warning{{end}}">Upload: {{$upload}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</span>
{{end}}
</div>
</a>
{{end}}
</div>
</li>
<div class="topbar-divider d-none d-sm-block"></div>
{{end}}
{{end}}
{{define "page_body"}}
<div id="errorMsg" class="alert alert-warning alert-dismissible fade show" style="display: none;" role="alert">
<span id="errorTxt"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</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="alert alert-warning alert-dismissible fade show" role="alert">
{{.Error}}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</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="copyModal" tabindex="-1" role="dialog" aria-labelledby="copyModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="copyModalLabel">
Copy 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="copy_form" action="" method="POST">
<div class="modal-body">
<div class="form-group">
<label for="copy_old_name" class="col-form-label">Source</label>
<input type="text" class="form-control" id="copy_old_name" readonly>
</div>
<div class="form-group">
<label for="copy_new_dir" class="col-form-label">New base dir</label>
<input type="text" class="form-control" id="copy_new_dir" required aria-describedby="copyNewDirHelpBlock">
<small id="copyNewDirHelpBlock" class="form-text text-muted">
Setting a directory other than the current one will copy the item there. This directory will be created if it doesn't exist
</small>
</div>
<div class="form-group">
<label for="copy_new_name" class="col-form-label">Target</label>
<input type="text" class="form-control" id="copy_new_name" required aria-describedby="copyTargetDirHelpBlock">
<small id="copyNewDirHelpBlock" class="form-text text-muted">
If the target ends with "/", for example "adir/", it is treated as a directory and the source will be copied to the target. The target directory will be created if it doesn't exist
</small>
</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="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){
$('#errorMsg').hide();
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();
}
}
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);
}
}
$('#errorMsg').hide();
downloadFileAsBlob().catch(function(error){
notifyBlobDownloadError(error.message);
$('#errorTxt').text(error.message);
$('#errorMsg').show();
});
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);
}
}
$('#errorMsg').hide();
saveBlob().catch(function(error){
$('#spinnerModal').modal('hide');
notifySave("KO", error.message);
$('#errorTxt').text(error.message);
$('#errorMsg').show();
});
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);
}
let 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) {
let 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() {
let table = $('#dataTable').DataTable();
table.button('delete:name').enable(false);
let selectedItems = table.column(0).checkboxes.selected()
let has_errors = false;
let index = 0;
let success = 0;
spinnerDone = false;
$('#deleteModal').modal('hide');
$('#spinnerModal').modal('show');
$('#errorMsg').hide();
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;
}
let selected = selectedItems[index];
let itemType = getTypeFromMeta(selected);
let itemName = getNameFromMeta(selected);
let path;
let reqTimeout = 15000;
if (itemType == "1"){
path = '{{.DirsURL}}';
reqTimeout = 120000
} 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;
let 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) {
let json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
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');
$('#errorMsg').hide();
let dirName = replaceSlash($("#directory_name").val());
let 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) {
let txt = "Unable to create the requested directory";
if ($xhr) {
let json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
}
});
});
$("#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');
$('#errorMsg').hide();
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);
let errorMessage = "Error uploading files";
let response;
try {
let f = files[index].file;
let uploadPath = '{{.FileURL}}?path={{.CurrentDir}}'+encodeURIComponent("/"+f.name);
let 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();
uploadFile();
});
}
uploadFile();
});
$("#rename_form").submit(function (event){
event.preventDefault();
let table = $('#dataTable').DataTable();
table.button('rename:name').enable(false);
let selected = table.column(0).checkboxes.selected()[0];
let itemName = getNameFromMeta(selected);
let targetName = replaceSlash($("#rename_new_name").val());
let targetDir = $("#rename_new_dir").val();
if (targetDir != "/") {
targetDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir;
}
if (targetDir.trim() == ""){
targetDir = "{{.CurrentDir}}";
} else {
targetDir = encodeURIComponent(targetDir);
}
let path = '{{.FileActionsURL}}/move';
path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName)+'&target='+targetDir+encodeURIComponent("/"+targetName);
$('#renameModal').modal('hide');
$('#errorMsg').hide();
$.ajax({
url: path,
type: 'POST',
dataType: 'json',
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
timeout: 15000,
success: function (result) {
location.reload();
},
error: function ($xhr, textStatus, errorThrown) {
let txt = "Error renaming item";
if ($xhr) {
let json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
let selectedItems = table.column(0).checkboxes.selected().length;
table.button('rename:name').enable(selectedItems == 1);
}
});
});
$("#copy_form").submit(function (event){
event.preventDefault();
let table = $('#dataTable').DataTable();
table.button('copy:name').enable(false);
let selected = table.column(0).checkboxes.selected()[0];
let itemName = getNameFromMeta(selected);
let targetName = $("#copy_new_name").val();
let targetDir = $("#copy_new_dir").val();
if (targetDir != "/") {
targetDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir;
}
if (targetDir.trim() == ""){
targetDir = "{{.CurrentDir}}";
} else {
targetDir = encodeURIComponent(targetDir);
}
let path = '{{.FileActionsURL}}/copy';
path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName)+'&target='+targetDir+encodeURIComponent("/"+targetName);
spinnerDone = false;
$('#copyModal').modal('hide');
$('#spinnerModal').modal('show');
$('#errorMsg').hide();
$.ajax({
url: path,
type: 'POST',
dataType: 'json',
headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
timeout: 120000,
success: function (result) {
$('#spinnerModal').modal('hide');
spinnerDone = true;
location.reload();
},
error: function ($xhr, textStatus, errorThrown) {
let txt = "Error copying item";
if ($xhr) {
let json = $xhr.responseJSON;
if (json) {
if (json.message) {
txt = json.message;
}
if (json.error) {
txt += ": " + json.error;
}
}
}
$('#errorTxt').text(txt);
$('#errorMsg').show();
$('#spinnerModal').modal('hide');
spinnerDone = true;
let selectedItems = table.column(0).checkboxes.selected().length;
table.button('copy: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) {
let filesArray = [];
let selected = dt.column(0).checkboxes.selected();
for (i = 0; i < selected.length; i++) {
filesArray.push(getNameFromMeta(selected[i]));
}
let files = encodeURIComponent(JSON.stringify(filesArray));
let downloadURL = '{{.DownloadURL}}';
let currentDir = '{{.CurrentDir}}';
let ts = new Date().getTime().toString();
window.open(`${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) {
let selected = table.column(0).checkboxes.selected()[0];
let 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.copy = {
text: '<i class="fas fa-copy"></i>',
name: 'copy',
titleAttr: "Copy",
action: function (e, dt, node, config) {
let selected = table.column(0).checkboxes.selected()[0];
let itemName = getNameFromMeta(selected);
$("#copy_old_name").val(itemName);
$("#copy_new_dir").val(decodeURIComponent("{{.CurrentDir}}".replace(/\+/g, '%20')));
$("#copy_new_name").val("");
$('#copyModal').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) {
let filesArray = [];
let selected = dt.column(0).checkboxes.selected();
for (i = 0; i < selected.length; i++) {
filesArray.push(getNameFromMeta(selected[i]));
}
let files = encodeURIComponent(JSON.stringify(filesArray));
let shareURL = '{{.ShareURL}}';
let currentDir = '{{.CurrentDir}}';
let ts = new Date().getTime().toString();
window.open(`${shareURL}?path=${currentDir}&files=${files}&_=${ts}`,'_blank');
},
enabled: false
};
let table = $('#dataTable').DataTable({
"ajax": {
"url": "{{.DirsURL}}?path={{.CurrentDir}}",
"dataSrc": "",
"error": function ($xhr, textStatus, errorThrown) {
$(".dataTables_processing").hide();
let txt = "Failed to get directory listing";
if ($xhr) {
let json = $xhr.responseJSON;
if (json) {
if (json.message){
txt += ": " + json.message;
} else {
txt += ": " + json.error;
}
}
}
$('#errorMsg').hide();
$('#errorTxt').text(txt);
$('#errorMsg').show();
}
},
"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') {
let title = "";
let cssClass = "";
let 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>`;
}
let icon = getIconForFile(data);
return `<i class="${icon}"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
}
return data;
}
},
{
"data": "size",
"render": function (data, type, row) {
if (type === 'display') {
if (data || data === 0){
return fileSizeIEC(data);
}
return "";
}
return data;
}
},
{ "data": "last_modified" },
{ "data": "edit_url",
"render": function (data, type, row) {
if (type === 'display') {
let filename = escapeHTML(row["name"]);
let 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":
let 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){
let 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){
let 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) {
let selectedItems = table.column(0).checkboxes.selected().length;
let 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 .CanAddFiles}}
table.button('copy: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 .CanAddFiles}}
table.button().add(0, 'copy');
{{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}}