Merge branch 'lukas-development' into 'dev'

File Manager

See merge request crafty-controller/crafty-commander!19
This commit is contained in:
Phillip Tarrant 2021-01-19 12:28:58 +00:00
commit c154f316b7
8 changed files with 945 additions and 121 deletions

View File

@ -51,7 +51,7 @@ class Controller:
continue continue
# if this server path no longer exists - let's warn and bomb out # if this server path no longer exists - let's warn and bomb out
if not helper.check_path_exits(s['path']): if not helper.check_path_exists(s['path']):
logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'], logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
s['path'])) s['path']))

View File

@ -9,6 +9,7 @@ import base64
import socket import socket
import random import random
import logging import logging
import html
from datetime import datetime from datetime import datetime
from socket import gethostname from socket import gethostname
@ -280,7 +281,7 @@ class Helpers:
return "%.1f%s%s" % (num, 'Y', suffix) return "%.1f%s%s" % (num, 'Y', suffix)
@staticmethod @staticmethod
def check_path_exits(path: str): def check_path_exists(path: str):
logger.debug('Looking for path: {}'.format(path)) logger.debug('Looking for path: {}'.format(path))
if os.path.exists(path): if os.path.exists(path):
@ -463,6 +464,34 @@ class Helpers:
return data return data
@staticmethod
def generate_tree(folder, output=""):
for raw_filename in os.listdir(folder):
print(raw_filename)
filename = html.escape(raw_filename)
print(filename)
rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel):
output += \
"""<li class="tree-item" data-path="{}">
\n<div data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">{}</div>
\n<ul class="tree-nested">"""\
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
output += helper.generate_tree(rel)
output += '</ul>\n</li>'
else:
console.debug('os.path.isdir(rel): "{}", rel: "{}"'.format(os.path.isdir(rel), rel))
output += """<li
class="tree-item tree-ctx-item tree-file"
data-path="{}"
data-name="{}"
onclick="clickOnFile(event)">{}</li>""".format(os.path.join(folder, filename), filename, filename)
return output
@staticmethod
def in_path(x, y):
return os.path.abspath(y).__contains__(os.path.abspath(x))
helper = Helpers() helper = Helpers()

View File

@ -97,7 +97,7 @@ class Server:
console.critical("Server executable path: {} does not seem to exist".format(full_path)) console.critical("Server executable path: {} does not seem to exist".format(full_path))
helper.do_exit() helper.do_exit()
if not helper.check_path_exits(self.server_path): if not helper.check_path_exists(self.server_path):
logger.critical("Server path: {} does not seem to exits".format(self.server_path)) logger.critical("Server path: {} does not seem to exits".format(self.server_path))
console.critical("Server path: {} does not seem to exits".format(self.server_path)) console.critical("Server path: {} does not seem to exits".format(self.server_path))
helper.do_exit() helper.do_exit()

View File

@ -3,6 +3,8 @@ import logging
import tornado.web import tornado.web
import tornado.escape import tornado.escape
import bleach import bleach
import os
import shutil
from app.classes.shared.console import console from app.classes.shared.console import console
from app.classes.shared.models import Users, installer from app.classes.shared.models import Users, installer
@ -79,7 +81,58 @@ class AjaxHandler(BaseHandler):
page_data['notify_data'] = data page_data['notify_data'] = data
self.render_page('ajax/notify.html', page_data) self.render_page('ajax/notify.html', page_data)
elif page == "get_file":
file_path = self.get_argument('file_path', None)
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in get_file ajax call")
console.warning("Invalid path in get_file ajax call")
return False
file = open(file_path)
file_contents = file.read()
file.close()
console.debug("Send file contents")
self.write(file_contents)
self.finish()
elif page == "get_tree":
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
self.write(db_helper.get_server_data_by_id(server_id)['path'] + '\n' +
helper.generate_tree(db_helper.get_server_data_by_id(server_id)['path']))
self.finish()
@tornado.web.authenticated
def post(self, page): def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data")) user_data = json.loads(self.get_secure_cookie("user_data"))
error = bleach.clean(self.get_argument('error', "WTF Error!")) error = bleach.clean(self.get_argument('error', "WTF Error!"))
@ -90,11 +143,12 @@ class AjaxHandler(BaseHandler):
} }
if page == "send_command": if page == "send_command":
command = bleach.clean(self.get_body_argument('command', default=None, strip=True)) command = self.get_body_argument('command', default=None, strip=True)
server_id = bleach.clean(self.get_argument('id')) server_id = self.get_argument('id')
if server_id is None: if server_id is None:
logger.warning("Server ID not found in send_command ajax call") logger.warning("Server ID not found in send_command ajax call")
console.warning("Server ID not found in send_command ajax call")
srv_obj = controller.get_server_obj(server_id) srv_obj = controller.get_server_obj(server_id)
@ -102,3 +156,192 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running(): if srv_obj.check_running():
srv_obj.send_command(command) srv_obj.send_command(command)
elif page == "create_file":
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
file_name = self.get_body_argument('file_name', default=None, strip=True)
file_path = os.path.join(file_parent, file_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_file ajax call")
console.warning("Server ID not found in create_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_file ajax call")
console.warning("Server ID not found in create_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in create_file ajax call")
console.warning("Invalid path in create_file ajax call")
return False
# Create the file by opening it
with open(file_path, 'w') as file_object:
file_object.close()
elif page == "create_dir":
dir_parent = self.get_body_argument('dir_parent', default=None, strip=True)
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
dir_path = os.path.join(dir_parent, dir_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_dir ajax call")
console.warning("Server ID not found in create_dir ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_dir ajax call")
console.warning("Server ID not found in create_dir ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in create_dir ajax call")
console.warning("Invalid path in create_dir ajax call")
return False
# Create the directory
os.mkdir(dir_path)
@tornado.web.authenticated
def delete(self, page):
if page == "del_file":
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in del_file ajax call")
console.warning("Invalid path in del_file ajax call")
return False
# Delete the file
os.remove(file_path)
elif page == "del_dir":
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or not helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in del_file ajax call")
console.warning("Invalid path in del_file ajax call")
return False
# Delete the file
# os.rmdir(dir_path)
shutil.rmtree(dir_path) # Removes also when there are contents
@tornado.web.authenticated
def put(self, page):
if page == "save_file":
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(file_contents)
print(file_path)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in save_file ajax call")
console.warning("Server ID not found in save_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in save_file ajax call")
console.warning("Server ID not found in save_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path)\
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in save_file ajax call")
console.warning("Invalid path in save_file ajax call")
return False
# Open the file in write mode and store the content in file_object
with open(file_path, 'w') as file_object:
file_object.write(file_contents)
elif page == "rename_item":
item_path = self.get_body_argument('item_path', default=None, strip=True)
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in rename_item ajax call")
console.warning("Server ID not found in rename_item ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in rename_item ajax call (1)")
console.warning("Server ID not found in rename_item ajax call (1)")
return False
if item_path is None or new_item_name is None:
logger.warning("Invalid path in rename_item ajax call (2)")
console.warning("Invalid path in rename_item ajax call (2)")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], item_path) \
or not helper.check_path_exists(os.path.abspath(item_path)):
logger.warning("Invalid path in rename_item ajax call (3)")
console.warning("Invalid path in rename_item ajax call (3)")
return False
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], new_item_path) \
or helper.check_path_exists(os.path.abspath(new_item_path)):
logger.warning("Invalid path 2 in rename_item ajax call")
console.warning("Invalid path 2 in rename_item ajax call")
return False
# RENAME
os.rename(item_path, new_item_path)

View File

@ -68,9 +68,6 @@ class PanelHandler(BaseHandler):
elif page == 'file_edit': elif page == 'file_edit':
template = "panel/file_edit.html" template = "panel/file_edit.html"
elif page == 'files_menu':
template = "panel/files_menu.html"
elif page == "remove_server": elif page == "remove_server":
server_id = self.get_argument('id', None) server_id = self.get_argument('id', None)
server_data = controller.get_server_data(server_id) server_data = controller.get_server_data(server_id)
@ -112,10 +109,12 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Server ID") self.redirect("/panel/error?error=Invalid Server ID")
return False return False
valid_subpages = ['term', 'logs', 'config'] valid_subpages = ['term', 'logs', 'config', 'files']
if subpage not in valid_subpages: if subpage not in valid_subpages:
console.debug('not a valid subpage')
subpage = 'term' subpage = 'term'
console.debug('Subpage: "{}"'.format(subpage))
# server_data isn't needed since the server_stats also pulls server data # server_data isn't needed since the server_stats also pulls server data
# page_data['server_data'] = db_helper.get_server_data_by_id(server_id) # page_data['server_data'] = db_helper.get_server_data_by_id(server_id)

View File

@ -64,6 +64,14 @@
</li> </li>
{% end %} {% end %}
<!--
<li class="nav-item">
<a class="nav-link" href="/panel/files?id=1">
<i class="fas fa-copy"></i> &nbsp;
<span class="menu-title">Files Test</span>
</a>
</li>-->
</ul> </ul>
</nav> </nav>

View File

@ -1,112 +0,0 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Looking at files in server -< server name >- (-< server path id >-){% 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">
Looking at files in server -< server name >- (-< server path id >-)
<br />
<small>Path: -< location relative to server directory >-</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body">
<ul class="files-list list-group">
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="mx-1 btn btn-dark">Edit</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">File</span>
</span>
</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="mx-1 btn btn-dark">Open</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">Folder</span>
</span>
</span>
</li>
<!--
File:
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/file_edit?path=-< file location relative to server directory >-" class="mx-1 btn btn-dark">Edit</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">File</span>
</span>
</span>
</li>
Folder:
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background-color: var(--dark) !important;">
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="text-white">-< name >-</a>
<span>
<a href="/panel/files_menu?path=-< location relative to server directory >-" class="mx-1 btn btn-dark">Open</a>
<button onclick="alert('will make a modal')" class="mx-1 btn btn-dark">Rename</button>
<button onclick="alert('will make a confirmation modal')" class="mx-1 btn btn-danger">Delete</button>
<span style="margin-left: 0.3rem;width: 5rem;display: inline-block;">
<span class="badge badge-primary badge-pill">Folder</span>
</span>
</span>
</li>
-->
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
// empty for now
</script>
{% end %}

View File

@ -0,0 +1,657 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Server Details{% 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">
Server Details - {{ data['server_stats'][0]['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats'][0]['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">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=term" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Terminal</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=logs" role="tab" aria-selected="false">
<i class="fas fa-file-signature"></i>Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=tasks" role="tab" aria-selected="false">
<i class="fas fa-clock"></i>Schedule</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>Backup</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Files</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/server_detail?id={{ data['server_stats'][0]['server_id']['server_id'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
<noscript>
The file manager does not work without JavaScript
</noscript>
<div id="files-tree-nav" class="overlay">
<!-- Button to close the overlay navigation -->
<a href="javascript:void(0)" class="closebtn" onclick="document.getElementById('files-tree-nav').style.height = '0%';">&times;</a>
<!-- Overlay content -->
<div id="files-tree-nav-content" class="overlay-content">
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">Create file</a>
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">Create directory</a>
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">Rename</a>
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#">Delete</a>
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#">Delete</a>
</div>
</div>
<style>
/* The Overlay (background) */
.overlay {
/* Height & width depends on how you want to reveal the overlay (see JS below) */
height: 0;
width: 100vw;
position: fixed; /* Stay in place */
z-index: 1031; /* Sit on top */
left: 0;
top: 0;
background-color: rgb(0,0,0); /* Black fallback color */
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
overflow-x: hidden; /* Disable horizontal scroll */
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
}
/* Position the content inside the overlay */
.overlay-content {
position: relative;
top: 25%; /* 25% from the top */
width: 100%; /* 100% width */
text-align: center; /* Centered text/links */
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
}
/* The navigation links inside the overlay */
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block; /* Display block instead of inline */
transition: 0.3s; /* Transition effects on hover (color) */
}
/* When you mouse over the navigation links, change their color */
.overlay a:hover, .overlay a:focus {
color: #f1f1f1;
}
/* Position the close button (top right corner) */
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
/* 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 class="tree-caret tree-ctx-item files-tree-title">Files</div>
<ul class="tree-nested" id="files-tree">
<li>Error while getting files</li>
</ul>
</li>
</ul>
</div>
<style>
/* Remove default bullets */
.tree-view {
list-style-type: none;
margin: 0;
padding: 0;
}
/* Style the caret/arrow */
.tree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.tree-caret::before {
content: "\25B6";
color: white;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-caret-down::before {
transform: rotate(90deg);
}
/* Hide the nested list */
.tree-nested {
display: none;
}
</style>
<div class="col-md-6 col-sm-12">
Editing file <span id="editingFile"></span>
<div id="editor">file_contents</div>
<h3 id="file_warn"></h3>
<button class="btn btn-success" onclick="save()">Save</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>
//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);
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: /^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'
}
];
var editorEnabled = false;
var filePath = '';
function clickOnFile(event) {
editorEnabled = true;
filePath = event.target.getAttribute('data-path');
setFileName(event.target.innerText);
$.ajax({
type: 'GET',
url: '/ajax/get_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}&file_path=' + encodeURIComponent(filePath),
dataType: 'text',
success: function (data) {
console.log('Got File Contents From Server');
editor.session.setValue(data);
},
});
}
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 = 'Warning: This is not a supported language';
}
}
setFileName();
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 = 'Warning: This is not a supported language';
} 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: '/ajax/save_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_contents: text,
file_path: filePath
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
function createFile(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
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: '/ajax/create_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
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: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/rename_item?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
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: '/ajax/del_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
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: '/ajax/del_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
dir_path: path
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function getTreeView() {
$.ajax({
type: "GET",
url: '/ajax/get_tree?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
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');
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);
var toggler = document.getElementsByClassName("tree-caret");
var i;
for (i = 0; i < toggler.length; i++) {
if (toggler[i].classList.contains('files-tree-title')) continue;
toggler[i].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.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];
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('tree-folder');
$('#createFile').toggle(isDir);
$('#createDir').toggle(isDir);
$('#deleteDir').toggle(isDir);
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
console.log({ 'event.target': event.target, isDir, isFile });
if(event.target.classList.contains('files-tree-title')) {
$('#createFile').show();
$('#createDir').show();
$('#renameItem').hide();
$('#deleteDir').hide();
$('#deleteFile').hide();
}
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.height = "100%";
})
}
}
function createFileE(event) {
bootbox.prompt('What name do you want for the new file?', 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.height = '0%';
});
})
}
function createDirE(event) {
bootbox.prompt('What name do you want for the new directory?', 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.height = '0%';
});
})
}
function renameItemE(event) {
bootbox.prompt('What should the new name be?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
renameItem(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function deleteFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteFile(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
function deleteDirE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteDir(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
document.getElementsByClassName('files-tree-title')[0].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
getTreeView();
setTreeViewContext();
</script>
{% end %}