2021-01-15 12:13:06 +00:00
{% extends ../base.html %}
{% block meta %}
{% end %}
2021-09-29 02:25:47 +00:00
{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %}
2021-01-15 12:13:06 +00:00
{% block content %}
< div class = "content-wrapper" >
2022-04-02 17:06:20 +00:00
<!-- Page Title Header Starts -->
2021-01-15 12:13:06 +00:00
< div class = "row page-title-header" >
< div class = "col-12" >
< div class = "page-header" >
< h4 class = "page-title" >
2022-04-02 17:06:20 +00:00
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
< br / >
2023-11-01 17:44:56 +00:00
< small > UUID: {{ data['server_stats']['server_id']['server_id'] }}< / small >
2021-01-15 12:13:06 +00:00
< / h4 >
< / div >
< / div >
< / div >
<!-- Page Title Header Ends -->
2022-04-02 17:06:20 +00:00
{% include "parts/details_stats.html" %}
2021-01-15 12:13:06 +00:00
< div class = "row" >
< div class = "col-sm-12 grid-margin" >
< div class = "card" >
< div class = "card-body pt-0" >
2022-06-21 19:59:34 +00:00
< span class = "d-none d-sm-block" >
{% include "parts/server_controls_list.html %}
< / span >
< span class = "d-block d-sm-none" >
{% include "parts/m_server_controls_list.html %}
< / span >
2022-04-02 17:06:20 +00:00
< div class = "row" >
< div class = "col-md-6 col-sm-12" >
< noscript >
{{ translate('serverFiles', 'noscript', data['lang']) }}
< / noscript >
2022-05-15 17:38:48 +00:00
< div id = "files-tree-nav" class = "overlay" style = "background-color: #9f9daf !important;" >
2022-04-02 17:06:20 +00:00
<!-- Button to close the overlay navigation -->
<!-- Overlay content -->
2022-05-15 17:38:48 +00:00
< div id = "files-tree-nav-content" class = "overlay-content" style = "background-color: #9f9daf;" >
< h4 id = "context-title" style = "color:#4b00ff; text-align: center; padding: 3px;" >
< / h4 >
< p style = "width: 90%; border-bottom: 2px solid black; margin:0 auto;" > < / p >
2022-04-02 17:06:20 +00:00
< 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 >
2024-08-06 19:45:00 +00:00
< a onclick = "deleteFileE(event)" href = "javascript:void(0)" id = "deleteDir" href = "#"
style="color: red">{{
2022-04-02 17:06:20 +00:00
translate('serverFiles', 'delete', data['lang']) }}< / a >
2022-10-04 17:31:37 +00:00
< a href = "javascript:void(0)" class = "closebtn" style = "color: var(--info);"
2022-04-02 17:06:20 +00:00
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
translate('serverFiles', 'close', data['lang']) }}< / a >
2021-01-17 17:20:28 +00:00
< / div >
2022-04-02 17:06:20 +00:00
< / div >
< style >
/* The Overlay (background) */
.overlay {
2021-08-20 17:46:01 +00:00
display: none;
flex-direction: column;
2022-05-15 17:38:48 +00:00
background-color: #9f9daf;
2021-08-20 17:46:01 +00:00
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
z-index: 10000;
overflow: scroll;
2022-04-02 17:06:20 +00:00
}
2021-01-17 17:20:28 +00:00
2022-04-02 17:06:20 +00:00
.overlay::-webkit-scrollbar {
display: none;
2021-01-15 12:13:06 +00:00
}
2022-04-02 17:06:20 +00:00
/* Hide scrollbar for IE, Edge and Firefox */
.overlay {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
2021-01-15 12:13:06 +00:00
}
2022-04-02 17:06:20 +00:00
/* Position the content inside the overlay */
.overlay-content {
display: flex;
flex-direction: column;
2022-05-15 17:38:48 +00:00
background-color: #9f9daf;
2022-04-02 17:06:20 +00:00
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
2021-01-22 22:46:33 +00:00
}
2022-04-02 17:06:20 +00:00
/* 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;
2021-01-15 12:13:06 +00:00
}
2022-04-02 17:06:20 +00:00
/* When you mouse over the navigation links, change their color */
.overlay a:hover,
.overlay a:focus {
2022-05-15 17:38:48 +00:00
background: grey;
2022-04-02 17:06:20 +00:00
color: #4b00ff;
2021-01-22 22:46:33 +00:00
}
2022-04-02 17:06:20 +00:00
/* Position the close button (top right corner) */
.overlay .closebtn .closebtn:hover {
background-color: red;
color: red;
z-index: 10000;
2021-01-15 12:13:06 +00:00
}
2022-04-02 17:06:20 +00:00
/* 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;
}
2021-01-15 12:13:06 +00:00
}
2024-08-06 19:45:00 +00:00
.tree-file:hover {
2023-09-05 03:38:09 +00:00
cursor: pointer;
}
2021-01-15 12:13:06 +00:00
< / style >
2022-04-02 17:06:20 +00:00
< 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;
}
2022-06-13 22:35:43 +00:00
2022-06-21 20:41:06 +00:00
html,
body,
body>.container-scroller {
2022-06-13 22:35:43 +00:00
overflow: initial;
}
.editorManager {
top: 63px;
position: sticky;
}
2022-04-02 17:06:20 +00:00
< / style >
2022-06-21 20:41:06 +00:00
< div id = "editor_container" class = "col-md-6 col-sm-12" >
< br >
< br >
2022-06-13 22:35:43 +00:00
< div class = "editorManager" >
< 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 >
2022-06-21 20:55:42 +00:00
< span class = "d-none d-md-block " > < button class = "btn btn-info" id = "screen-size" > {{
translate('serverFiles',
'size', data['lang']) }}< / button > < / span >
2022-06-13 22:35:43 +00:00
< / div >
< h3 id = "file_warn" > < / h3 >
< button class = "btn btn-success" onclick = "save()" > < i class = "fas fa-save" > < / i > {{ translate('serverFiles',
2022-06-21 20:44:30 +00:00
'save', data['lang']) }}< / button >
< span style = "color: #2fb689; margin-left: 10px;" id = "save_status" > < / span >
2022-04-02 17:06:20 +00:00
< / div >
2021-01-15 12:13:06 +00:00
< / div >
2022-04-02 17:06:20 +00:00
< / div >
2021-01-15 12:13:06 +00:00
< / 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 >
2022-02-27 17:26:54 +00:00
const serverId = new URLSearchParams(document.location.search).get('id')
2021-01-15 19:59:58 +00:00
2022-02-27 17:26:54 +00:00
//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;
}
2021-01-15 19:59:58 +00:00
2022-02-27 17:26:54 +00:00
let editor = ace.edit('editor');
editor.setTheme('ace/theme/dracula');
editor.session.setUseSoftTabs(true);
2022-06-13 23:45:17 +00:00
editor.commands.addCommand({
name: 'saveFile',
bindKey: {
win: 'Ctrl-S',
mac: 'Command-S',
sender: 'editor|cli'
},
2022-06-21 20:41:06 +00:00
exec: function (env, args, request) {
2022-06-13 23:45:17 +00:00
save()
}
});
2021-01-15 12:13:06 +00:00
2022-02-27 17:26:54 +00:00
// mouseup = css resize end
2022-04-02 17:06:20 +00:00
document.addEventListener("mouseup", function (e) {
editor.resize();
});
2021-01-20 21:10:25 +00:00
2022-02-27 17:26:54 +00:00
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'
},
];
2023-09-03 20:15:38 +00:00
let path = '', serverFileContent = '';
async function clickOnFile(event) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf");
2023-09-03 20:15:38 +00:00
path = event.target.getAttribute('data-path');
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
2023-09-04 23:22:11 +00:00
method: 'POST',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "page": "files", "path": path }),
});
let responseData = await res.json();
console.log(responseData)
if (responseData.status === "ok") {
console.log('Got File Contents From Server');
$('#editorParent').toggle(true) // show
$('#fileError').toggle(false) // hide
setFileName(event.target.innerText);
editor.session.setValue(responseData.data);
serverFileContent = responseData.data;
setSaveStatus(true);
}
else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-15 19:59:58 +00:00
2022-02-27 17:26:54 +00:00
function setFileName(name) {
let fileName = name || 'default.txt';
document.getElementById('editingFile').innerText = fileName;
2021-01-15 12:13:06 +00:00
2022-02-27 17:26:54 +00:00
if (fileName.match('.')) {
2021-01-15 12:13:06 +00:00
2022-02-27 17:26:54 +00:00
// The pop method removes and returns the last element.
setMode(fileName
.split('.')
.pop()
.replace('ace/mode/', ''));
2021-01-15 12:13:06 +00:00
2022-02-27 17:26:54 +00:00
} else {
setMode('txt');
document
.querySelector('#file_warn')
.innerText = "{% raw translate('serverFiles', 'unsupportedLanguage', data['lang']) %}";
2021-01-15 12:13:06 +00:00
}
2022-02-27 17:26:54 +00:00
}
2021-01-15 12:13:06 +00:00
2022-04-02 17:06:20 +00:00
var onlongtouch;
2022-03-22 14:24:15 +00:00
var timer;
var touchduration = 500; //length of time we want the user to touch before we do something
const longtouch = new Event('longtouch');
2022-04-02 17:06:20 +00:00
2022-03-22 14:24:15 +00:00
function touchstart(event) {
2022-04-02 17:06:20 +00:00
if (!timer) {
timer = setTimeout(onlongtouch, touchduration, event);
}
2022-03-22 14:24:15 +00:00
}
2022-04-02 17:06:20 +00:00
2022-03-22 14:24:15 +00:00
function touchend(event) {
2022-04-02 17:06:20 +00:00
//stops short touches from firing the event
if (timer) {
clearTimeout(timer);
console.log('Timer: ' + timer)
timer = null;
}
2022-03-22 14:24:15 +00:00
}
2022-04-02 17:06:20 +00:00
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');
2022-05-15 17:38:48 +00:00
document.getElementById('context-title').innerHTML = ctxmenuName;
2022-04-02 17:06:20 +00:00
if (!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);
if (e.target.classList.contains('root-dir')) {
$('#createFile').show();
$('#createDir').show();
$('#renameItem').hide();
$('#deleteDir').hide();
$('#deleteFile').hide();
$('#downloadFile').hide();
$('#upload').show();
2022-03-22 14:24:15 +00:00
}
2022-04-02 17:06:20 +00:00
if (e.target.textContent.endsWith('.zip')) {
$('#unzip').show();
} 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';
timer = null;
};
}
2022-06-14 00:07:26 +00:00
/**
* @param {boolean} saved
*/
const setSaveStatus = (saved) => {
2023-02-13 18:28:21 +00:00
document.getElementById('save_status').innerHTML = `< i class = "${saved ? " fa-solid fa-file-circle-check " : " fa-regular fa-file " } " > < / i > `;
2022-06-14 00:07:26 +00:00
document.getElementById('save_status').style.color = saved ? '#2fb689' : 'gray';
}
['change', 'undo', 'redo'].forEach(event => editor.on(event, (event) => setSaveStatus(serverFileContent === editor.session.getValue())))
2022-06-21 20:41:06 +00:00
$('#screen-size').on('click', function (e) {
$('#editor_container').toggleClass(`col-md-6`)
2022-04-02 19:57:11 +00:00
2022-06-21 20:41:06 +00:00
});
2022-02-27 17:26:54 +00:00
setFileName();
$('#editorParent').toggle(false) // show
$('#fileError').toggle(false) // hide
editor.blur()
2022-04-02 17:06:20 +00:00
function setMode(extension) {
2022-02-27 17:26:54 +00:00
// 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');
2021-01-15 12:13:06 +00:00
}
2022-02-27 17:26:54 +00:00
}
2021-01-15 12:13:06 +00:00
2023-09-03 20:15:38 +00:00
async function save() {
2022-02-27 17:26:54 +00:00
let text = editor.session.getValue();
2021-01-15 19:59:58 +00:00
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf")
2023-09-03 20:15:38 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
2023-09-04 23:22:11 +00:00
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "path": path, "contents": text }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
serverFileContent = text;
setSaveStatus(true)
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
} else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-15 12:13:06 +00:00
2023-09-03 20:15:38 +00:00
async function createFile(parent, name, callback) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf")
2023-09-03 20:15:38 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
2023-09-04 23:22:11 +00:00
method: 'PUT',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "parent": parent, "name": name, "directory": false }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-17 17:20:28 +00:00
2023-09-03 20:15:38 +00:00
async function createDir(parent, name, callback) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf")
2023-09-03 20:15:38 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
2023-09-04 23:22:11 +00:00
method: 'PUT',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "parent": parent, "name": name, "directory": true }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-17 17:20:28 +00:00
2023-09-03 20:15:38 +00:00
async function renameItem(path, name, callback) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf")
2023-09-03 20:15:38 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
2023-09-04 23:22:11 +00:00
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "path": path, "new_name": name }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-17 17:20:28 +00:00
2023-06-05 22:20:45 +00:00
async function deleteItem(path, el, callback) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf");
2023-06-05 22:20:45 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
2023-09-04 23:22:11 +00:00
method: 'DELETE',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "filename": path }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
el = document.getElementById(path + "li");
$(el).remove();
document.getElementById('files-tree-nav').style.display = 'none';
} else {
2021-01-17 17:20:28 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-01-17 17:20:28 +00:00
2023-09-03 20:15:38 +00:00
async function unZip(path, callback) {
2023-09-04 23:22:11 +00:00
const token = getCookie("_xsrf")
2023-09-03 20:15:38 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files/zip/`, {
2023-09-04 23:22:11 +00:00
method: 'POST',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "folder": path }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
2023-09-03 20:15:38 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2022-02-27 17:26:54 +00:00
}
2021-08-22 20:31:49 +00:00
2022-04-02 17:06:20 +00:00
let uploadWaitDialog;
2021-08-22 20:31:49 +00:00
2023-01-29 00:10:27 +00:00
async function uploadFilesE(event) {
2021-08-19 20:50:18 +00:00
path = event.target.parentElement.getAttribute('data-path');
2021-08-22 20:31:49 +00:00
$(function () {
var uploadHtml = "< div > " +
2022-04-12 19:27:03 +00:00
'< form id = "upload_file" enctype = "multipart/form-data" > ' + "< label class = 'upload-area' style = 'width:100%;text-align:center;' for = 'files' > " +
2022-04-02 17:06:20 +00:00
"< i class = 'fa fa-cloud-upload fa-3x' > < / i > " +
"< br / > " +
"{{translate('serverFiles', 'clickUpload', data['lang'])}}" +
2022-04-12 19:27:03 +00:00
"< input style = 'margin-left: 21%;' id = 'files' name = 'files' type = 'file' multiple = 'true' > " +
2021-08-22 20:31:49 +00:00
"< / label > < / form > " +
"< br / > " +
"< ul style = 'margin-left:5px !important;' id = 'fileList' > < / ul > " +
2022-04-02 17:06:20 +00:00
"< / div > < div class = 'clearfix' > < / div > ";
2022-02-27 17:26:54 +00:00
bootbox.dialog({
2021-08-22 20:31:49 +00:00
message: uploadHtml,
2022-04-02 17:06:20 +00:00
title: "{{ translate('serverFiles', 'uploadTitle', data['lang'])}}" + path,
2021-08-22 20:31:49 +00:00
buttons: {
2022-02-27 17:26:54 +00:00
success: {
label: "{{ translate('serverFiles', 'upload', data['lang']) }}",
className: "btn-default",
callback: async function () {
2024-08-06 19:45:00 +00:00
if ($("#files").get(0).files.length === 0) {
return hideUploadBox();
}
2022-04-02 17:06:20 +00:00
var height = files.files.length * 50;
var waitMessage = '< p class = "text-center mb-0" > ' +
2023-02-01 22:34:07 +00:00
'< i class = "fa fa-spin fa-cog" > < / i > ' +
2022-04-02 17:06:20 +00:00
"{{ 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
2022-02-27 17:26:54 +00:00
});
2022-06-03 18:49:51 +00:00
2022-02-27 17:26:54 +00:00
let nFiles = files.files.length;
2024-08-06 19:45:00 +00:00
const uploadPromises = [];
2022-06-03 18:49:51 +00:00
2024-08-06 19:45:00 +00:00
for (let i = 0; i < nFiles ; i + + ) {
const file = files.files[i];
2022-02-27 17:26:54 +00:00
const progressHtml = `
2024-08-06 19:45:00 +00:00
< div style = "width: 100%; min-width: 100%;" >
${file.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 >
`;
2022-02-27 17:26:54 +00:00
$('#upload-progress-bar-parent').append(progressHtml);
2022-06-03 18:49:51 +00:00
2024-08-06 19:45:00 +00:00
const uploadPromise = uploadFile("server_upload", file, path, i, (progress) => {
2022-02-27 17:26:54 +00:00
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
2023-02-01 20:31:43 +00:00
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
2022-02-27 17:26:54 +00:00
});
2024-08-06 19:45:00 +00:00
uploadPromises.push(uploadPromise);
2024-05-27 03:08:43 +00:00
}
2024-08-06 19:45:00 +00:00
await Promise.all(uploadPromises);
setTimeout(() => {
hideUploadBox();
}, 2000);
2021-08-22 20:31:49 +00:00
}
2022-02-27 17:26:54 +00:00
}
2021-08-22 20:31:49 +00:00
}
2022-02-27 17:26:54 +00:00
});
2024-08-06 19:45:00 +00:00
});
}
2022-02-27 17:26:54 +00:00
2024-08-06 19:45:00 +00:00
async function calculateFileHash(file) {
const arrayBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
2024-05-27 23:12:31 +00:00
2024-08-06 19:45:00 +00:00
return hashHex;
2024-05-27 23:12:31 +00:00
}
2023-09-04 23:22:11 +00:00
function getDirView(event) {
2023-06-05 03:08:27 +00:00
let path = event.target.parentElement.getAttribute("data-path");
if (document.getElementById(path).classList.contains('clicked')) {
return;
2023-09-04 23:22:11 +00:00
} else {
2023-06-05 03:08:27 +00:00
getTreeView(path);
}
2021-01-17 17:20:28 +00:00
2022-04-02 17:06:20 +00:00
}
2023-09-04 23:22:11 +00:00
async function getTreeView(path) {
const token = getCookie("_xsrf");
2023-06-05 03:08:27 +00:00
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
2023-09-04 23:22:11 +00:00
method: 'POST',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({ "page": "files", "path": path }),
});
let responseData = await res.json();
if (responseData.status === "ok") {
console.log(responseData);
process_tree_response(responseData);
2022-01-08 23:03:45 +00:00
2023-09-04 23:22:11 +00:00
} else {
2021-01-17 17:20:28 +00:00
2023-09-04 23:22:11 +00:00
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
2023-06-05 03:08:27 +00:00
}
2021-01-17 17:20:28 +00:00
2023-06-05 03:08:27 +00:00
function process_tree_response(response) {
let path = response.data.root_path.path;
let text = ``;
2023-09-04 23:22:11 +00:00
if (!response.data.root_path.top) {
2023-06-05 03:08:27 +00:00
text = `< ul class = "tree-nested d-block" id = "${path}ul" > `;
}
2023-09-04 23:22:11 +00:00
Object.entries(response.data).forEach(([key, value]) => {
if (key === "root_path" || key === "db_stats") {
//continue is not valid in for each. Return acts as a continue.
return;
}
2023-06-05 03:08:27 +00:00
let checked = ""
let dpath = value.path;
let filename = key;
2023-09-04 23:22:11 +00:00
if (value.dir) {
if (value.excluded) {
2023-06-05 03:08:27 +00:00
checked = "checked"
}
text += `< li class = "tree-item" id = "${dpath}li" data-path = "${dpath}" >
\n< div id = "${dpath}" data-path = "${dpath}" data-name = "${filename}" class = "tree-caret tree-ctx-item tree-folder" >
< input type = "checkbox" class = "checkBoxClass d-none file-check" name = "root_path" value = "${dpath}" $ { checked } >
< span id = "${dpath}span" class = "files-tree-title" data-path = "${dpath}" data-name = "${filename}" onclick = "getDirView(event)" >
< i style = "color: var(--info);" class = "far fa-folder" > < / i >
< i style = "color: var(--info);" class = "far fa-folder-open" > < / i >
${filename}
< / span >
< / input > < / div > < / li > `
2023-09-04 23:22:11 +00:00
} else {
2023-06-05 03:08:27 +00:00
text += `< li
class="d-block tree-ctx-item tree-file"
data-path="${dpath}"
data-name="${filename}"
onclick="clickOnFile(event)" id="${dpath}li">< input type = 'checkbox' class = "checkBoxClass d-none file-check" name = 'root_path' value = "${dpath}" $ { checked } > < span style = "margin-right: 6px;" >
< i class = "far fa-file" > < / i > < / span > < / input > ${filename}< / li > `
2022-02-27 17:26:54 +00:00
}
2023-06-05 03:08:27 +00:00
});
2023-09-04 23:22:11 +00:00
if (!response.data.root_path.top) {
2023-06-05 03:08:27 +00:00
text += `< / ul > `;
}
2023-09-04 23:22:11 +00:00
if (response.data.root_path.top) {
2023-06-05 03:08:27 +00:00
try {
2023-09-04 23:22:11 +00:00
document.getElementById('main-tree-div').innerHTML += text;
document.getElementById('main-tree').parentElement.classList.add("clicked");
} catch {
document.getElementById('files-tree').innerHTML = text;
}
} else {
2023-06-05 03:08:27 +00:00
try {
2023-09-04 23:22:11 +00:00
document.getElementById(path + "span").classList.add('tree-caret-down');
document.getElementById(path).innerHTML += text;
document.getElementById(path).classList.add("clicked");
} catch {
console.log("Bad")
}
2022-01-08 23:03:45 +00:00
2023-09-04 23:22:11 +00:00
var toggler = document.getElementById(path + "span");
2022-01-21 22:50:04 +00:00
2023-09-04 23:22:11 +00:00
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");
});
}
2021-01-17 17:20:28 +00:00
}
2023-06-05 03:08:27 +00:00
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");
2021-08-20 18:58:52 +00:00
}
2022-02-27 17:26:54 +00:00
function setTreeViewContext() {
2022-06-03 19:53:12 +00:00
var treeItems = Array.from(document.getElementsByClassName('tree-ctx-item'));
2022-02-27 17:26:54 +00:00
2022-06-03 18:49:51 +00:00
treeItems.forEach(item => {
2022-04-02 17:06:20 +00:00
if ([
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
].includes(navigator.platform)
2022-03-22 14:24:15 +00:00
// iPad on iOS 13 detection
2022-04-02 17:06:20 +00:00
|| (navigator.userAgent.includes("Mac") & & "ontouchend" in document)) {
2022-06-03 18:49:51 +00:00
item.addEventListener("touchstart", touchstart, false);
item.addEventListener("touchend", touchend, false);
2022-04-02 17:06:20 +00:00
}
2022-06-03 18:49:51 +00:00
item.addEventListener('contextmenu', function contextListener(event) {
2022-02-27 17:26:54 +00:00
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);
2022-05-15 17:38:48 +00:00
document.getElementById('context-title').innerHTML = ctxmenuName;
2022-02-27 17:26:54 +00:00
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
$('#downloadFile').toggle(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();
} else {
$('#unzip').hide();
}
2021-01-17 17:20:28 +00:00
2022-04-02 17:06:20 +00:00
var clientX = event.clientX;
var clientY = event.clientY;
2021-01-17 17:20:28 +00:00
2022-02-27 17:26:54 +00:00
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();
2022-04-02 17:06:20 +00:00
sum = (clientY + domRect['height']) - window.innerHeight
if (domRect['height'] + clientY > window.innerHeight) {
2022-02-27 17:26:54 +00:00
clientY = clientY - sum
}
document.getElementById("files-tree-nav").style.top = clientY + 'px';
2022-04-02 17:06:20 +00:00
document.getElementById("files-tree-nav").style.left = clientX + 'px';
2021-01-17 17:20:28 +00:00
})
2022-06-03 18:49:51 +00:00
})
2022-02-27 17:26:54 +00:00
}
2022-04-02 17:06:20 +00:00
document.addEventListener('click', function (e) {
2022-02-27 17:26:54 +00:00
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');
2021-08-25 01:33:29 +00:00
}
2022-02-27 17:26:54 +00:00
});
2021-01-17 17:20:28 +00:00
2022-02-27 17:26:54 +00:00
if (webSocket) {
webSocket.on('close_upload_box', function (close_upload_box) {
hideUploadBox();
});
}
2022-04-02 17:06:20 +00:00
function hideUploadBox() {
2022-02-27 17:26:54 +00:00
if (!uploadWaitDialog) return;
uploadWaitDialog.modal('hide');
}
function createFileE(event) {
2022-04-02 17:06:20 +00:00
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion', data['lang']) %}", function (result) {
2021-08-25 02:05:30 +00:00
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
2022-02-27 17:26:54 +00:00
if (!result) return;
createFile(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.display = 'none';
2021-08-25 02:05:30 +00:00
});
2022-02-27 17:26:54 +00:00
})
}
2021-01-17 17:20:28 +00:00
2022-02-27 17:26:54 +00:00
function createDirE(event) {
2022-04-02 17:06:20 +00:00
bootbox.prompt("{% raw translate('serverFiles', 'createDirQuestion', data['lang']) %}", function (result) {
2021-01-17 17:20:28 +00:00
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
2022-02-27 17:26:54 +00:00
if (!result) return;
createDir(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.display = 'none';
2021-01-17 17:20:28 +00:00
});
2022-02-27 17:26:54 +00:00
})
}
function downloadFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
2022-12-01 01:28:24 +00:00
encoded_path = encodeURIComponent(path)
encoded_name = encodeURIComponent(name)
window.location.href = `/panel/download_file?id=${serverId}&path=${encoded_path}&name=${encoded_name}`;
2022-02-27 17:26:54 +00:00
}
2021-01-17 17:20:28 +00:00
2022-02-27 17:26:54 +00:00
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,
2022-04-02 17:06:20 +00:00
callback: function (result) {
2022-02-27 17:26:54 +00:00
if (!result) return;
renameItem(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.display = 'none';
});
}
});
}
2022-04-02 17:06:20 +00:00
function unzipFilesE(event) {
path = event.target.parentElement.getAttribute('data-path');
2022-06-03 19:53:12 +00:00
console.log(path)
2022-02-27 17:26:54 +00:00
unZip(path)
}
function deleteFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
2022-04-02 17:06:20 +00:00
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'
2022-02-27 17:26:54 +00:00
},
cancel: {
label: "{{ translate('serverFiles', 'noDelete', data['lang']) }}",
className: 'btn-link'
}
},
2022-04-02 17:06:20 +00:00
callback: function (result) {
2022-02-27 17:26:54 +00:00
if (!result) return;
2023-06-05 22:20:45 +00:00
deleteItem(path);
2022-02-27 17:26:54 +00:00
}
});
}
2021-01-17 17:20:28 +00:00
2023-06-05 03:08:27 +00:00
getTreeView($('#root_dir').data('path'));
2022-02-27 17:26:54 +00:00
setTreeViewContext();
2021-01-22 22:12:52 +00:00
2022-02-27 17:26:54 +00:00
function setKeyboard(target) {
var handlerName = target.getAttribute('data-handler-name');
if (handlerName == 'null') handlerName = null;
2022-06-13 23:45:17 +00:00
editor.setKeyboardHandler(handlerName, () => {
if (handlerName == 'ace/keyboard/vim') {
2022-06-21 20:41:06 +00:00
require("ace/keyboard/vim").Vim.defineEx('write', 'w', function () {
2022-06-13 23:45:17 +00:00
save();
});
}
});
2021-01-22 22:12:52 +00:00
2022-02-27 17:26:54 +00:00
var nodes = target.parentNode.querySelectorAll("[data-handler-name]");
2022-06-03 18:49:51 +00:00
nodes.forEach(node => {
node.classList.remove('btn-primary');
node.classList.add('btn-secondary');
})
2021-01-22 22:12:52 +00:00
2022-02-27 17:26:54 +00:00
target.classList.remove('btn-secondary');
target.classList.add('btn-primary');
}
2022-01-08 23:03:45 +00:00
2021-12-20 17:51:27 +00:00
2021-01-15 12:13:06 +00:00
< / script >
2024-08-06 19:45:00 +00:00
< script src = "../../static/assets/js/shared/upload.js" > < / script >
2022-06-21 20:41:06 +00:00
{% end %}