mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
1104 lines
36 KiB
HTML
1104 lines
36 KiB
HTML
{% extends ../base.html %}
|
|
|
|
{% block meta %}
|
|
{% end %}
|
|
|
|
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="content-wrapper">
|
|
|
|
<!-- Page Title Header Starts-->
|
|
<div class="row page-title-header">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<h4 class="page-title">
|
|
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
|
|
data['server_stats']['server_id']['server_name'] }}
|
|
<br />
|
|
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- Page Title Header Ends-->
|
|
|
|
{% include "parts/details_stats.html" %}
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-sm-12 grid-margin">
|
|
<div class="card">
|
|
<div class="card-body pt-0">
|
|
{% include "parts/server_controls_list.html" %}
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 col-sm-12">
|
|
<noscript>
|
|
{{ translate('serverFiles', 'noscript', data['lang']) }}
|
|
</noscript>
|
|
<div id="files-tree-nav" class="overlay">
|
|
|
|
<!-- Button to close the overlay navigation -->
|
|
|
|
<!-- Overlay content -->
|
|
<div id="files-tree-nav-content" class="overlay-content">
|
|
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">{{
|
|
translate('serverFiles', 'createFile', data['lang']) }}</a>
|
|
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">{{
|
|
translate('serverFiles', 'createDir', data['lang']) }}</a>
|
|
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">{{
|
|
translate('serverFiles', 'rename', data['lang']) }}</a>
|
|
<a onclick="uploadFilesE(event)" href="javascript:void(0)" id="upload" href="#">{{
|
|
translate('serverFiles', 'upload', data['lang']) }}</a>
|
|
<a onclick="unzipFilesE(event)" href="javascript:void(0)" id="unzip" href="#">{{
|
|
translate('serverFiles', 'unzip', data['lang']) }}</a>
|
|
<a onclick="downloadFileE(event)" href="javascript:void(0)" id="downloadFile" href="#">{{
|
|
translate('serverFiles', 'download', data['lang']) }}</a>
|
|
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
|
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
|
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{
|
|
translate('serverFiles', 'delete', data['lang']) }}</a>
|
|
<a href="javascript:void(0)" class="closebtn" style="color: #8862e0;"
|
|
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
|
translate('serverFiles', 'close', data['lang']) }}</a>
|
|
</div>
|
|
|
|
</div>
|
|
<style>
|
|
/* The Overlay (background) */
|
|
.overlay {
|
|
display: none;
|
|
flex-direction: column;
|
|
background-color: #fff;
|
|
border-radius: 10px;
|
|
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
|
|
padding: 10px 0;
|
|
z-index: 10000;
|
|
overflow: scroll;
|
|
|
|
}
|
|
|
|
.overlay::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
/* Hide scrollbar for IE, Edge and Firefox */
|
|
.overlay {
|
|
-ms-overflow-style: none;
|
|
/* IE and Edge */
|
|
scrollbar-width: none;
|
|
/* Firefox */
|
|
}
|
|
|
|
/* Position the content inside the overlay */
|
|
.overlay-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: #fff;
|
|
border-radius: 10px;
|
|
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
|
|
padding: 10px 0;
|
|
}
|
|
|
|
/* The navigation links inside the overlay */
|
|
.overlay a {
|
|
font: inherit;
|
|
border: 0;
|
|
padding: 10px 30px 10px 15px;
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
text-decoration: unset;
|
|
color: #000;
|
|
font-weight: 500;
|
|
transition: 0.5s linear;
|
|
-webkit-transition: 0.5s linear;
|
|
-moz-transition: 0.5s linear;
|
|
-ms-transition: 0.5s linear;
|
|
-o-transition: 0.5s linear;
|
|
}
|
|
|
|
/* When you mouse over the navigation links, change their color */
|
|
.overlay a:hover,
|
|
.overlay a:focus {
|
|
background: #f1f3f7;
|
|
color: #4b00ff;
|
|
}
|
|
|
|
/* Position the close button (top right corner) */
|
|
.overlay .closebtn .closebtn:hover {
|
|
background-color: red;
|
|
color: red;
|
|
z-index: 10000;
|
|
}
|
|
|
|
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
|
|
@media screen and (max-height: 450px) {
|
|
.overlay a {
|
|
font-size: 20px
|
|
}
|
|
|
|
.overlay .closebtn {
|
|
font-size: 40px;
|
|
top: 15px;
|
|
right: 35px;
|
|
}
|
|
}
|
|
</style>
|
|
<ul class="tree-view">
|
|
<li>
|
|
<div id="root_dir" class="tree-ctx-item" data-path="{{ data['server_stats']['server_id']['path'] }}">
|
|
<span id="{{ data['server_stats']['server_id']['path'] }}span"
|
|
class="files-tree-title tree-caret-down root-dir"
|
|
data-path="{{ data['server_stats']['server_id']['path'] }}" onclick="getToggleMain(event)">
|
|
<i class="far fa-folder"></i>
|
|
<i class="far fa-folder-open"></i>
|
|
{{ translate('serverFiles', 'files', data['lang']) }}
|
|
</span>
|
|
</div>
|
|
<ul class="tree-nested d-block" id="files-tree">
|
|
<li><i class="fa fa-spin fa-spinner"></i>{{ translate('serverFiles', 'loadingRecords', data['lang'])
|
|
}}</li>
|
|
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<style>
|
|
/* Remove default bullets */
|
|
.tree-view,
|
|
.tree-nested {
|
|
list-style-type: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
/* Style the items */
|
|
.tree-item,
|
|
.files-tree-title {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
/* Prevent text selection */
|
|
}
|
|
|
|
/* Create the caret/arrow with a unicode, and style it */
|
|
.tree-caret .fa-folder {
|
|
display: inline-block;
|
|
}
|
|
|
|
.tree-caret .fa-folder-open {
|
|
display: none;
|
|
}
|
|
|
|
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
|
.tree-caret-down .fa-folder {
|
|
display: none;
|
|
}
|
|
|
|
.tree-caret-down .fa-folder-open {
|
|
display: inline-block;
|
|
}
|
|
|
|
/* Hide the nested list */
|
|
.tree-nested {
|
|
display: none;
|
|
}
|
|
</style>
|
|
<div class="col-md-6 col-sm-12">
|
|
<h2 id="fileError"></h2>
|
|
<div id="editorParent">
|
|
{{ translate('serverFiles', 'editingFile', data['lang']) }} <span id="editingFile"></span>
|
|
<div id="editor" onresize="editor.resize()" style="resize: both;width: 100%;">file_contents</div>
|
|
<br />
|
|
</div>
|
|
{{ translate('serverFiles', 'keybindings', data['lang']) }}:
|
|
<div class="btn-group" role="group">
|
|
<button onclick="setKeyboard(event.target)" class="btn btn-primary" data-handler-name="null">{{
|
|
translate('serverFiles', 'default', data['lang']) }}</button>
|
|
<button onclick="setKeyboard(event.target)" class="btn btn-secondary"
|
|
data-handler-name="ace/keyboard/vim">Vim</button>
|
|
<button onclick="setKeyboard(event.target)" class="btn btn-secondary"
|
|
data-handler-name="ace/keyboard/emacs">Emacs</button>
|
|
<button onclick="setKeyboard(event.target)" class="btn btn-secondary"
|
|
data-handler-name="ace/keyboard/sublime">Sublime</button>
|
|
</div>
|
|
<h3 id="file_warn"></h3>
|
|
<button class="btn btn-success" onclick="save()"><i class="fas fa-save"></i> {{ translate('serverFiles',
|
|
'save', data['lang']) }}</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
<!-- content-wrapper ends -->
|
|
|
|
{% end %}
|
|
|
|
{% block js %}
|
|
<script src="/static/assets/vendors/ace-builds/src-min/ace.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<script>
|
|
|
|
const serverId = new URLSearchParams(document.location.search).get('id')
|
|
|
|
|
|
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
|
|
function getCookie(name) {
|
|
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
|
return r ? r[1] : undefined;
|
|
}
|
|
|
|
|
|
let editor = ace.edit('editor');
|
|
editor.setTheme('ace/theme/dracula');
|
|
editor.session.setUseSoftTabs(true);
|
|
|
|
// mouseup = css resize end
|
|
document.addEventListener("mouseup", function (e) {
|
|
editor.resize();
|
|
});
|
|
|
|
let extensionChanges = [
|
|
{
|
|
regex: /^js$/,
|
|
replaceWith: 'ace/mode/javascript'
|
|
},
|
|
{
|
|
regex: /^py$/,
|
|
replaceWith: 'ace/mode/python'
|
|
},
|
|
{
|
|
regex: /^html$/,
|
|
replaceWith: 'ace/mode/html'
|
|
},
|
|
{
|
|
regex: /^yml$/,
|
|
replaceWith: 'ace/mode/yaml'
|
|
},
|
|
{
|
|
regex: /^yaml$/,
|
|
replaceWith: 'ace/mode/yaml'
|
|
},
|
|
{
|
|
regex: /^txt$/,
|
|
replaceWith: 'ace/mode/text'
|
|
},
|
|
{
|
|
regex: /^json$/,
|
|
replaceWith: 'ace/mode/json'
|
|
},
|
|
{
|
|
regex: /^java$/,
|
|
replaceWith: 'ace/mode/java'
|
|
},
|
|
{
|
|
regex: /^cpp$/,
|
|
replaceWith: 'ace/mode/c_cpp'
|
|
},
|
|
{
|
|
regex: /^c$/,
|
|
replaceWith: 'ace/mode/c_cpp'
|
|
},
|
|
{
|
|
regex: /^css$/,
|
|
replaceWith: 'ace/mode/css'
|
|
},
|
|
{
|
|
regex: /^scss$/,
|
|
replaceWith: 'ace/mode/scss'
|
|
},
|
|
{
|
|
regex: /^sass$/,
|
|
replaceWith: 'ace/mode/sass'
|
|
},
|
|
{
|
|
regex: /^lua$/,
|
|
replaceWith: 'ace/mode/lua'
|
|
},
|
|
{
|
|
regex: /^php$/,
|
|
replaceWith: 'ace/mode/php'
|
|
},
|
|
{
|
|
regex: /^ps1$/,
|
|
replaceWith: 'ace/mode/powershell'
|
|
},
|
|
{
|
|
regex: /^svg$/,
|
|
replaceWith: 'ace/mode/svg'
|
|
},
|
|
{
|
|
regex: /^sh$/,
|
|
replaceWith: 'ace/mode/sh'
|
|
},
|
|
{
|
|
regex: /^xml$/,
|
|
replaceWith: 'ace/mode/xml'
|
|
},
|
|
{
|
|
regex: /^ts$/,
|
|
replaceWith: 'ace/mode/typescript'
|
|
},
|
|
{
|
|
regex: /^properties$/,
|
|
replaceWith: 'ace/mode/properties'
|
|
},
|
|
{
|
|
regex: /^log$/,
|
|
replaceWith: 'ace/mode/txt'
|
|
},
|
|
];
|
|
|
|
var filePath = '';
|
|
|
|
function clickOnFile(event) {
|
|
filePath = event.target.getAttribute('data-path');
|
|
$.ajax({
|
|
type: 'GET',
|
|
url: "/files/get_file?id=" + serverId + "&file_path=" + encodeURIComponent(filePath),
|
|
dataType: 'text',
|
|
success: function (data) {
|
|
console.log('Got File Contents From Server');
|
|
json = JSON.parse(data)
|
|
if (json.error) {
|
|
$('#editorParent').toggle(false) // hide
|
|
$('#fileError').toggle(true) // show
|
|
$('#fileError').text("{{ translate('serverFiles', 'fileReadError', data['lang']) }}: " + json.error) // show error
|
|
editor.blur()
|
|
} else {
|
|
$('#editorParent').toggle(true) // show
|
|
$('#fileError').toggle(false) // hide
|
|
setFileName(event.target.innerText);
|
|
editor.session.setValue(json.content);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
function setFileName(name) {
|
|
let fileName = name || 'default.txt';
|
|
document.getElementById('editingFile').innerText = fileName;
|
|
|
|
if (fileName.match('.')) {
|
|
|
|
// The pop method removes and returns the last element.
|
|
setMode(fileName
|
|
.split('.')
|
|
.pop()
|
|
.replace('ace/mode/', ''));
|
|
|
|
} else {
|
|
setMode('txt');
|
|
document
|
|
.querySelector('#file_warn')
|
|
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage', data['lang']) %}";
|
|
}
|
|
}
|
|
|
|
var onlongtouch;
|
|
var timer;
|
|
var touchduration = 500; //length of time we want the user to touch before we do something
|
|
const longtouch = new Event('longtouch');
|
|
|
|
function touchstart(event) {
|
|
if (!timer) {
|
|
timer = setTimeout(onlongtouch, touchduration, event);
|
|
}
|
|
}
|
|
|
|
function touchend(event) {
|
|
//stops short touches from firing the event
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
console.log('Timer: ' + timer)
|
|
timer = null;
|
|
}
|
|
}
|
|
|
|
onlongtouch = function (e) {
|
|
console.log('iOS long touch detected!');
|
|
if ([
|
|
'iPad Simulator',
|
|
'iPhone Simulator',
|
|
'iPod Simulator',
|
|
'iPad',
|
|
'iPhone',
|
|
'iPod'
|
|
].includes(navigator.platform)
|
|
// iPad on iOS 13 detection
|
|
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
var ctxmenuPath = e.target.getAttribute('data-path');
|
|
var ctxmenuName = e.target.getAttribute('data-name');
|
|
if (!ctxmenuPath) {
|
|
console.log({ 'event.target': e.target, ctxmenuPath });
|
|
return;
|
|
}
|
|
$('#renameItem').show();
|
|
|
|
var isDir = e.target.classList.contains('files-tree-title');
|
|
$('#createFile').toggle(isDir);
|
|
$('#createDir').toggle(isDir);
|
|
$('#deleteDir').toggle(isDir);
|
|
$('#upload').toggle(isDir);
|
|
|
|
var isFile = e.target.classList.contains('tree-file');
|
|
$('#deleteFile').toggle(isFile);
|
|
$('#downloadFile').toggle(isFile);
|
|
console.log({ 'event.target': e.target, isDir, isFile });
|
|
|
|
if (e.target.classList.contains('root-dir')) {
|
|
$('#createFile').show();
|
|
$('#createDir').show();
|
|
$('#renameItem').hide();
|
|
$('#deleteDir').hide();
|
|
$('#deleteFile').hide();
|
|
$('#downloadFile').hide();
|
|
$('#upload').show();
|
|
}
|
|
if (e.target.textContent.endsWith('.zip')) {
|
|
$('#unzip').show();
|
|
console.log(e.target.textContent)
|
|
} else {
|
|
$('#unzip').hide();
|
|
}
|
|
|
|
var clientX = e.layerX + 2;
|
|
var clientY = e.layerY + 5;
|
|
|
|
|
|
|
|
document.getElementById('files-tree-nav-content')
|
|
.setAttribute('data-path', ctxmenuPath);
|
|
document.getElementById('files-tree-nav-content')
|
|
.setAttribute('data-name', ctxmenuName);
|
|
document.getElementById("files-tree-nav").style.display = "flex";
|
|
document.getElementById("files-tree-nav").style.position = "fixed";
|
|
domRect = document.getElementById("files-tree-nav").getBoundingClientRect();
|
|
sum = (clientY + domRect['height']) - window.innerHeight
|
|
if (domRect['height'] + clientY > window.innerHeight) {
|
|
clientY = clientY - sum;
|
|
}
|
|
document.getElementById("files-tree-nav").style.top = clientY + 'px';
|
|
document.getElementById("files-tree-nav").style.left = clientX + 'px';
|
|
console.log(window.innerHeight)
|
|
timer = null;
|
|
};
|
|
}
|
|
|
|
|
|
setFileName();
|
|
$('#editorParent').toggle(false) // show
|
|
$('#fileError').toggle(false) // hide
|
|
editor.blur()
|
|
|
|
function setMode(extension) {
|
|
// if the extension matches with the RegEx it will return the replaceWith
|
|
// property. else it will return the one it has. defaults to the extension.
|
|
// this runs for each element in extensionChanges.
|
|
let aceMode = extensionChanges.reduce((output, element) => {
|
|
return extension.match(element.regex)
|
|
? element.replaceWith
|
|
: output;
|
|
}, extension);
|
|
|
|
if (!aceMode.startsWith('ace/mode/')) {
|
|
document
|
|
.querySelector('#file_warn')
|
|
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage', data['lang']) %}";
|
|
} else {
|
|
document
|
|
.querySelector('#file_warn')
|
|
.innerText = '';
|
|
|
|
console.log(aceMode || 'ace/mode/text');
|
|
editor.session.setMode(aceMode || 'ace/mode/text');
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function save() {
|
|
let text = editor.session.getValue();
|
|
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "PUT",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/save_file?id=" + serverId,
|
|
data: {
|
|
file_contents: text,
|
|
file_path: filePath
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
//TODO add better feedback for file saves
|
|
console.log(data);
|
|
},
|
|
});
|
|
}
|
|
|
|
function createFile(parent, name, callback) {
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "POST",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/create_file?id=" + serverId,
|
|
data: {
|
|
file_parent: parent,
|
|
file_name: name
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function createDir(parent, name, callback) {
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "POST",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/create_dir?id=" + serverId,
|
|
data: {
|
|
dir_parent: parent,
|
|
dir_name: name
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function renameItem(path, name, callback) {
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "PATCH",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/rename_file?id=" + serverId,
|
|
data: {
|
|
item_path: path,
|
|
new_item_name: name
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function deleteFile(path, callback) {
|
|
console.log('Deleting: ' + path)
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "DELETE",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/del_file?id=" + serverId,
|
|
data: {
|
|
file_path: path
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function deleteDir(path, callback) {
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "DELETE",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/del_dir?id=" + serverId,
|
|
data: {
|
|
dir_path: path
|
|
},
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function unZip(path, callback) {
|
|
var token = getCookie("_xsrf")
|
|
$.ajax({
|
|
type: "POST",
|
|
headers: { 'X-XSRFToken': token },
|
|
url: "/files/unzip_file?id=" + serverId,
|
|
data: {
|
|
path: path
|
|
},
|
|
});
|
|
window.location.href = "/panel/server_detail?id=" + serverId + "&subpage=files"
|
|
}
|
|
|
|
function sendFile(file, path, serverId, left, onProgress) {
|
|
let xmlHttpRequest = new XMLHttpRequest();
|
|
let token = getCookie("_xsrf")
|
|
let fileName = file.name
|
|
let target = '/upload?server_id=' + serverId
|
|
let mimeType = file.type
|
|
let size = file.size
|
|
|
|
xmlHttpRequest.open('POST', target, true);
|
|
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
|
|
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
|
|
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
|
|
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
|
|
xmlHttpRequest.setRequestHeader('X-Path', path);
|
|
xmlHttpRequest.setRequestHeader('X-Files-Left', left);
|
|
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
|
xmlHttpRequest.setRequestHeader('X-ServerId', serverId);
|
|
xmlHttpRequest.upload.addEventListener('progress', (event) =>
|
|
onProgress(Math.floor(event.loaded / event.total * 100)), false);
|
|
xmlHttpRequest.addEventListener('load', (event) => {
|
|
if (event.target.responseText == 'success') {
|
|
console.log('Upload for file', file.name, 'was successful!')
|
|
}
|
|
else {
|
|
alert('Upload failed with response: ' + event.target.responseText);
|
|
doUpload = false;
|
|
}
|
|
}, false);
|
|
xmlHttpRequest.addEventListener('error', (e) => {
|
|
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
|
var dialog = bootbox.dialog({
|
|
title: 'Error on Upload',
|
|
message: "Fix size exceeded the max body size. Contact your system administrator.",
|
|
size: 'large',
|
|
buttons: {
|
|
ok: {
|
|
label: "Ok",
|
|
className: 'btn-info',
|
|
callback: function () {
|
|
document.location.reload();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}, false);
|
|
xmlHttpRequest.send(file);
|
|
}
|
|
|
|
let uploadWaitDialog;
|
|
let doUpload = true;
|
|
|
|
function uploadFilesE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
console.log("PATH: " + path);
|
|
$(function () {
|
|
var uploadHtml = "<div>" +
|
|
'<form id="upload_file" enctype="multipart/form-data">' + "<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
|
|
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
|
|
"<i class='fa fa-cloud-upload fa-3x'></i>" +
|
|
"<br />" +
|
|
"{{translate('serverFiles', 'clickUpload', data['lang'])}}" +
|
|
"</label></form>" +
|
|
"<br />" +
|
|
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
|
|
"</div><div class='clearfix'></div>";
|
|
bootbox.dialog({
|
|
message: uploadHtml,
|
|
title: "{{ translate('serverFiles', 'uploadTitle', data['lang'])}}" + path,
|
|
buttons: {
|
|
success: {
|
|
label: "{{ translate('serverFiles', 'upload', data['lang']) }}",
|
|
className: "btn-default",
|
|
callback: async function () {
|
|
var height = files.files.length * 50;
|
|
|
|
var waitMessage = '<p class="text-center mb-0">' +
|
|
'<i class="fa fa-spin fa-cog"></i>' +
|
|
"{{ translate('serverFiles', 'waitUpload', data['lang']) }}" + '<br>' +
|
|
'<strong>' + "{{ translate('serverFiles', 'stayHere', data['lang']) }}" + '</strong>' +
|
|
'</p>' +
|
|
'<div class="progress" id="upload-progress-bar-parent" style="height:' + height + 'px; width:100%; display: block;">' +
|
|
'</div>'
|
|
files = document.getElementById("files");
|
|
uploadWaitDialog = bootbox.dialog({
|
|
message: waitMessage,
|
|
closeButton: false
|
|
});
|
|
let nFiles = files.files.length;
|
|
for (i = 0; i < files.files.length; i++) {
|
|
if (!doUpload) {
|
|
doUpload = true;
|
|
hideUploadBox();
|
|
break;
|
|
}
|
|
console.log(files.files[i].name);
|
|
const progressHtml = `
|
|
<div style="width: 100%; min-width: 100%;">
|
|
${files.files[i].name}:
|
|
<br><div
|
|
id="upload-progress-bar-${i + 1}"
|
|
class="progress-bar progress-bar-striped progress-bar-animated"
|
|
role="progressbar"
|
|
style="width: 100%; height: 10px;"
|
|
aria-valuenow="0"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100"
|
|
></div>
|
|
</div><br>
|
|
`;
|
|
$('#upload-progress-bar-parent').append(progressHtml);
|
|
console.log(files.files.length)
|
|
sendFile(files.files[i], path, serverId, files.files.length - i - 1, (progress) => {
|
|
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
|
|
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%')
|
|
});
|
|
}
|
|
hideUploadBox();
|
|
//$('#upload_file').submit(); //.trigger('submit');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var fileList = document.getElementById("files");
|
|
fileList.addEventListener("change", function (e) {
|
|
var list = "";
|
|
for (var i = 0; i < this.files.length; i++) {
|
|
list += "<li class='col-xs-12 file-list'>" + this.files[i].name + "</li>"
|
|
}
|
|
|
|
document.getElementById("fileList").innerHTML = list;
|
|
}, false);
|
|
});
|
|
}
|
|
function getTreeView(event) {
|
|
|
|
const path = $('#root_dir').data('path');;
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: "/files/get_tree?id=" + serverId + "&path=" + path,
|
|
dataType: 'text',
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
console.log(data);
|
|
|
|
dataArr = data.split('\n');
|
|
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
text = dataArr.join('\n');
|
|
|
|
try {
|
|
document.getElementById(path).innerHTML += text;
|
|
event.target.parentElement.classList.add("clicked");
|
|
} catch {
|
|
document.getElementById('files-tree').innerHTML = text;
|
|
}
|
|
|
|
|
|
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
|
|
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
|
|
|
|
setTimeout(function () { setTreeViewContext() }, 1000);
|
|
},
|
|
});
|
|
}
|
|
|
|
function getToggleMain(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
document.getElementById("files-tree").classList.toggle("d-block");
|
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
document.getElementById(path + "span").classList.toggle("tree-caret");
|
|
}
|
|
|
|
function getDirView(event) {
|
|
let path = event.target.parentElement.getAttribute('data-path');
|
|
|
|
if (document.getElementById(path).classList.contains('clicked')) {
|
|
|
|
var toggler = document.getElementById(path + "span");
|
|
|
|
if (toggler.classList.contains('files-tree-title')) {
|
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
}
|
|
return;
|
|
} else {
|
|
$.ajax({
|
|
type: "GET",
|
|
url: "/files/get_dir?id=" + serverId + "&path=" + path,
|
|
dataType: 'text',
|
|
success: function (data) {
|
|
console.log("got response:");
|
|
|
|
dataArr = data.split('\n');
|
|
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
|
text = dataArr.join('\n');
|
|
|
|
try {
|
|
document.getElementById(path + "span").classList.add('tree-caret-down');
|
|
document.getElementById(path).innerHTML += text;
|
|
document.getElementById(path).classList.add("clicked");
|
|
} catch {
|
|
console.log("Bad")
|
|
}
|
|
|
|
setTimeout(function () { setTreeViewContext() }, 1000);
|
|
|
|
var toggler = document.getElementById(path);
|
|
|
|
if (toggler.classList.contains('files-tree-title')) {
|
|
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
|
document.getElementById(path + "ul").classList.toggle("d-block");
|
|
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
|
});
|
|
}
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
function setTreeViewContext() {
|
|
var treeItems = document.getElementsByClassName('tree-ctx-item');
|
|
|
|
for (var i = 0; i < treeItems.length; i++) {
|
|
var treeItem = treeItems[i];
|
|
if ([
|
|
'iPad Simulator',
|
|
'iPhone Simulator',
|
|
'iPod Simulator',
|
|
'iPad',
|
|
'iPhone',
|
|
'iPod'
|
|
].includes(navigator.platform)
|
|
// iPad on iOS 13 detection
|
|
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)) {
|
|
treeItem.addEventListener("touchstart", touchstart, false);
|
|
treeItem.addEventListener("touchend", touchend, false);
|
|
}
|
|
treeItem.addEventListener('contextmenu', function contextListener(event) {
|
|
event.preventDefault();
|
|
var ctxmenuPath = event.target.getAttribute('data-path');
|
|
var ctxmenuName = event.target.getAttribute('data-name');
|
|
if (!ctxmenuPath) {
|
|
console.log({ 'event.target': event.target, ctxmenuPath });
|
|
return;
|
|
}
|
|
$('#renameItem').show();
|
|
|
|
var isDir = event.target.classList.contains('files-tree-title');
|
|
$('#createFile').toggle(isDir);
|
|
$('#createDir').toggle(isDir);
|
|
$('#deleteDir').toggle(isDir);
|
|
$('#upload').toggle(isDir);
|
|
|
|
var isFile = event.target.classList.contains('tree-file');
|
|
$('#deleteFile').toggle(isFile);
|
|
$('#downloadFile').toggle(isFile);
|
|
console.log({ 'event.target': event.target, isDir, isFile });
|
|
|
|
if (event.target.classList.contains('root-dir')) {
|
|
$('#createFile').show();
|
|
$('#createDir').show();
|
|
$('#renameItem').hide();
|
|
$('#deleteDir').hide();
|
|
$('#deleteFile').hide();
|
|
$('#downloadFile').hide();
|
|
$('#upload').show();
|
|
}
|
|
if (event.target.textContent.endsWith('.zip')) {
|
|
$('#unzip').show();
|
|
console.log(event.target.textContent)
|
|
} else {
|
|
$('#unzip').hide();
|
|
}
|
|
|
|
var clientX = event.clientX;
|
|
var clientY = event.clientY;
|
|
|
|
|
|
|
|
document.getElementById('files-tree-nav-content')
|
|
.setAttribute('data-path', ctxmenuPath);
|
|
document.getElementById('files-tree-nav-content')
|
|
.setAttribute('data-name', ctxmenuName);
|
|
document.getElementById("files-tree-nav").style.display = "flex";
|
|
document.getElementById("files-tree-nav").style.position = "fixed";
|
|
domRect = document.getElementById("files-tree-nav").getBoundingClientRect();
|
|
sum = (clientY + domRect['height']) - window.innerHeight
|
|
if (domRect['height'] + clientY > window.innerHeight) {
|
|
clientY = clientY - sum
|
|
}
|
|
document.getElementById("files-tree-nav").style.top = clientY + 'px';
|
|
document.getElementById("files-tree-nav").style.left = clientX + 'px';
|
|
console.log(domRect)
|
|
console.log(window.innerHeight)
|
|
})
|
|
}
|
|
}
|
|
|
|
document.addEventListener('click', function (e) {
|
|
let inside = (e.target.closest('#files-tree-nav'));
|
|
let contextMenu = document.getElementById('files-tree-nav');
|
|
if (!inside) {
|
|
contextMenu.setAttribute('style', 'display:none');
|
|
} else {
|
|
contextMenu.setAttribute('style', 'display:none');
|
|
}
|
|
});
|
|
|
|
if (webSocket) {
|
|
webSocket.on('close_upload_box', function (close_upload_box) {
|
|
hideUploadBox();
|
|
});
|
|
}
|
|
function hideUploadBox() {
|
|
if (!uploadWaitDialog) return;
|
|
uploadWaitDialog.modal('hide');
|
|
getTreeView();
|
|
}
|
|
|
|
function createFileE(event) {
|
|
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion', data['lang']) %}", function (result) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
if (!result) return;
|
|
|
|
createFile(path, result, function () {
|
|
getTreeView()
|
|
document.getElementById('files-tree-nav').style.display = 'none';
|
|
});
|
|
})
|
|
}
|
|
|
|
function createDirE(event) {
|
|
bootbox.prompt("{% raw translate('serverFiles', 'createDirQuestion', data['lang']) %}", function (result) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
if (!result) return;
|
|
|
|
createDir(path, result, function () {
|
|
getTreeView()
|
|
document.getElementById('files-tree-nav').style.display = 'none';
|
|
});
|
|
})
|
|
}
|
|
function downloadFileE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
window.location.href = `/panel/download_file?id=${serverId}&path=${path}&name=${name}`;
|
|
}
|
|
|
|
function renameItemE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
bootbox.prompt({
|
|
title: "{% raw translate('serverFiles', 'renameItemQuestion', data['lang']) %}",
|
|
value: name,
|
|
callback: function (result) {
|
|
if (!result) return;
|
|
renameItem(path, result, function () {
|
|
getTreeView()
|
|
document.getElementById('files-tree-nav').style.display = 'none';
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function unzipFilesE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
unZip(path)
|
|
}
|
|
|
|
function deleteFileE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
bootbox.confirm({
|
|
size: "",
|
|
title: "{% raw translate('serverFiles', 'deleteItemQuestion', data['lang']) %}",
|
|
closeButton: false,
|
|
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage', data['lang']) %}",
|
|
buttons: {
|
|
confirm: {
|
|
label: "{{ translate('serverFiles', 'yesDelete', data['lang']) }}",
|
|
className: 'btn-danger'
|
|
},
|
|
cancel: {
|
|
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
|
|
className: 'btn-link'
|
|
}
|
|
},
|
|
callback: function (result) {
|
|
if (!result) return;
|
|
deleteFile(path, function () {
|
|
getTreeView()
|
|
document.getElementById('files-tree-nav').style.display = 'none';
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteDirE(event) {
|
|
path = event.target.parentElement.getAttribute('data-path');
|
|
name = event.target.parentElement.getAttribute('data-name');
|
|
bootbox.confirm({
|
|
size: "",
|
|
title: "{% raw translate('serverFiles', 'deleteItemQuestion', data['lang']) %}",
|
|
closeButton: false,
|
|
message: "{% raw translate('serverFiles', 'deleteItemQuestionMessage', data['lang']) %}",
|
|
buttons: {
|
|
confirm: {
|
|
label: "{{ translate('serverFiles', 'yesDelete', data['lang']) }}",
|
|
className: 'btn-danger'
|
|
},
|
|
cancel: {
|
|
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
|
|
className: 'btn-link'
|
|
}
|
|
},
|
|
callback: function (result) {
|
|
if (!result) return;
|
|
deleteDir(path, function () {
|
|
getTreeView()
|
|
document.getElementById('files-tree-nav').style.display = 'none';
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
getTreeView();
|
|
setTreeViewContext();
|
|
|
|
function setKeyboard(target) {
|
|
var handlerName = target.getAttribute('data-handler-name');
|
|
if (handlerName == 'null') handlerName = null;
|
|
editor.setKeyboardHandler(handlerName);
|
|
|
|
var nodes = target.parentNode.querySelectorAll("[data-handler-name]");
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
nodes[i].classList.remove('btn-primary');
|
|
nodes[i].classList.add('btn-secondary');
|
|
}
|
|
|
|
target.classList.remove('btn-secondary');
|
|
target.classList.add('btn-primary');
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
{% end %} |