{% 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"> <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> <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" style="background-color: #9f9daf !important;"> <!-- Button to close the overlay navigation --> <!-- Overlay content --> <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> <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: #9f9daf; 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: #9f9daf; 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: grey; 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; } html, body, body>.container-scroller { overflow: initial; } .editorManager { top: 63px; position: sticky; } </style> <div id="editor_container" class="col-md-6 col-sm-12"> <br> <br> <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> <span class="d-none d-md-block "> <button class="btn btn-info" id="screen-size">{{ translate('serverFiles', 'size', data['lang']) }}</button></span> </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> <span style="color: #2fb689; margin-left: 10px;" id="save_status"></span> </div> </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); editor.commands.addCommand({ name: 'saveFile', bindKey: { win: 'Ctrl-S', mac: 'Command-S', sender: 'editor|cli' }, exec: function (env, args, request) { save() } }); // 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' }, ]; let filePath = '', serverFileContent = ''; 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); serverFileContent = json.content; setSaveStatus(true); } }, }); } 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'); document.getElementById('context-title').innerHTML = ctxmenuName; 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(); } 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; }; } /** * @param {boolean} saved */ const setSaveStatus = (saved) => { document.getElementById('save_status').innerHTML = `<i class="fal ${saved ? "fa-file-check" : "fa-file"}"></i>`; document.getElementById('save_status').style.color = saved ? '#2fb689' : 'gray'; } ['change', 'undo', 'redo'].forEach(event => editor.on(event, (event) => setSaveStatus(serverFileContent === editor.session.getValue()))) $('#screen-size').on('click', function (e) { $('#editor_container').toggleClass(`col-md-6`) }); 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: (data) => { serverFileContent = text; setSaveStatus(true) } }); } 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:"); 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:"); 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:"); 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:"); 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:"); callback(); }, }); } function unZip(path, callback) { console.log('path: ', path) var token = getCookie("_xsrf") $.ajax({ type: "POST", headers: { 'X-XSRFToken': token }, url: "/files/unzip_file?id=" + serverId, data: { path: path }, success: function (data) { 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) }, false); xmlHttpRequest.send(file); } let uploadWaitDialog; let doUpload = true; function uploadFilesE(event) { path = event.target.parentElement.getAttribute('data-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'>" + "<i class='fa fa-cloud-upload fa-3x'></i>" + "<br />" + "{{translate('serverFiles', 'clickUpload', data['lang'])}}" + "<input style='margin-left: 21%;' id='files' name='files' type='file' multiple='true'>" + "</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 < nFiles; i++) { if (!doUpload) { doUpload = true; hideUploadBox(); break; } 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); sendFile(files.files[i], path, serverId, nFiles - 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 = ""; let files = Array.from(this.files) files.forEach(file => { list += "<li class='col-xs-12 file-list'>" + file.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:"); 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 = Array.from(document.getElementsByClassName('tree-ctx-item')); treeItems.forEach(item => { 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)) { item.addEventListener("touchstart", touchstart, false); item.addEventListener("touchend", touchend, false); } item.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); document.getElementById('context-title').innerHTML = ctxmenuName; 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(); } 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'; }) }) } 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'); console.log(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, () => { if (handlerName == 'ace/keyboard/vim') { require("ace/keyboard/vim").Vim.defineEx('write', 'w', function () { save(); }); } }); var nodes = target.parentNode.querySelectorAll("[data-handler-name]"); nodes.forEach(node => { node.classList.remove('btn-primary'); node.classList.add('btn-secondary'); }) target.classList.remove('btn-secondary'); target.classList.add('btn-primary'); } </script> {% end %}