mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Setup backup file trees through API
This commit is contained in:
parent
a06f8664c2
commit
1ff9bc9e83
@ -108,138 +108,6 @@ class AjaxHandler(BaseHandler):
|
||||
)
|
||||
self.finish()
|
||||
|
||||
elif page == "get_backup_tree":
|
||||
server_id = self.get_argument("id", None)
|
||||
folder = self.get_argument("path", None)
|
||||
|
||||
output = ""
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||
unsorted_files, key=str.casefold
|
||||
)
|
||||
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
||||
server_id
|
||||
):
|
||||
if os.path.isdir(rel):
|
||||
output += f"""<li class="tree-item" 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" 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>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""
|
||||
else:
|
||||
output += f"""<li
|
||||
class="d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}" checked><span style="margin-right: 6px;">
|
||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
|
||||
else:
|
||||
if os.path.isdir(rel):
|
||||
output += f"""<li class="tree-item" 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" name="root_path" value="{dpath}">
|
||||
<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>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>
|
||||
\n"""
|
||||
else:
|
||||
output += f"""<li
|
||||
class="d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="{dpath}">
|
||||
<span style="margin-right: 6px;"><i class="far fa-file">
|
||||
</i></span></input>{filename}</li>"""
|
||||
self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
|
||||
self.finish()
|
||||
|
||||
elif page == "get_backup_dir":
|
||||
server_id = self.get_argument("id", None)
|
||||
folder = self.get_argument("path", None)
|
||||
output = ""
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||
unsorted_files, key=str.casefold
|
||||
)
|
||||
output += f"""<ul class="tree-nested d-block" id="{folder}ul">"""
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
||||
server_id
|
||||
):
|
||||
if os.path.isdir(rel):
|
||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" name="root_path" value="{dpath}" checked>
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>"""
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}' checked><span style="margin-right: 6px;">
|
||||
<i class="far fa-file"></i></span></input>{filename}</li>"""
|
||||
|
||||
else:
|
||||
if os.path.isdir(rel):
|
||||
output += f"""<li class="tree-item" data-path="{dpath}">
|
||||
\n<div id="{dpath}" data-path="{dpath}" data-name="{filename}" class="tree-caret tree-ctx-item tree-folder">
|
||||
<input type="checkbox" name="root_path" value="{dpath}">
|
||||
<span id="{dpath}span" class="files-tree-title" data-path="{dpath}" data-name="{filename}" onclick="getDirView(event)">
|
||||
<i class="far fa-folder"></i>
|
||||
<i class="far fa-folder-open"></i>
|
||||
<strong>{filename}</strong>
|
||||
</span>
|
||||
</input></div><li>"""
|
||||
else:
|
||||
output += f"""<li
|
||||
class="tree-item tree-nested d-block tree-ctx-item tree-file"
|
||||
data-path="{dpath}"
|
||||
data-name="{filename}"
|
||||
onclick=""><input type='checkbox' name='root_path' value='{dpath}'>
|
||||
<span style="margin-right: 6px;"><i class="far fa-file">
|
||||
</i></span></input>{filename}</li>"""
|
||||
|
||||
self.write(Helpers.get_os_understandable_path(folder) + "\n" + output)
|
||||
self.finish()
|
||||
|
||||
elif page == "get_dir":
|
||||
server_id = self.get_argument("id", None)
|
||||
path = self.get_argument("path", None)
|
||||
|
@ -32,6 +32,9 @@ from app.classes.web.routes.api.servers.server.backups.index import (
|
||||
from app.classes.web.routes.api.servers.server.backups.backup.index import (
|
||||
ApiServersServerBackupsBackupIndexHandler,
|
||||
)
|
||||
from app.classes.web.routes.api.servers.server.backups.files import (
|
||||
ApiServersServerBackupsFilesIndexHandler,
|
||||
)
|
||||
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
||||
ApiServersServerTasksTaskChildrenHandler,
|
||||
)
|
||||
@ -128,6 +131,11 @@ def api_handlers(handler_args):
|
||||
ApiServersServerBackupsBackupIndexHandler,
|
||||
handler_args,
|
||||
),
|
||||
(
|
||||
r"/api/v2/servers/([0-9]+)/backups/files/?",
|
||||
ApiServersServerBackupsFilesIndexHandler,
|
||||
handler_args,
|
||||
),
|
||||
(
|
||||
r"/api/v2/servers/([0-9]+)/tasks/?",
|
||||
ApiServersServerTasksIndexHandler,
|
||||
|
125
app/classes/web/routes/api/servers/server/backups/files.py
Normal file
125
app/classes/web/routes/api/servers/server/backups/files.py
Normal file
@ -0,0 +1,125 @@
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
import html
|
||||
from jsonschema import validate
|
||||
from jsonschema.exceptions import ValidationError
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
from app.classes.shared.helpers import Helpers
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
files_get_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "string", "minLength": 1},
|
||||
"folder": {"type": "string"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"minProperties": 1,
|
||||
}
|
||||
|
||||
|
||||
class ApiServersServerBackupsFilesIndexHandler(BaseApiHandler):
|
||||
def post(self, server_id: str):
|
||||
auth_data = self.authenticate_user()
|
||||
if not auth_data:
|
||||
return
|
||||
|
||||
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
|
||||
# if the user doesn't have access to the server, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
if (
|
||||
EnumPermissionsServer.BACKUP
|
||||
not in self.controller.server_perms.get_user_id_permissions_list(
|
||||
auth_data[4]["user_id"], server_id
|
||||
)
|
||||
):
|
||||
# if the user doesn't have Config permission, return an error
|
||||
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
|
||||
|
||||
try:
|
||||
data = json.loads(self.request.body)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
|
||||
)
|
||||
try:
|
||||
validate(data, files_get_schema)
|
||||
except ValidationError as e:
|
||||
return self.finish_json(
|
||||
400,
|
||||
{
|
||||
"status": "error",
|
||||
"error": "INVALID_JSON_SCHEMA",
|
||||
"error_data": str(e),
|
||||
},
|
||||
)
|
||||
if not Helpers.validate_traversal(
|
||||
self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||
data["folder"],
|
||||
):
|
||||
return self.finish_json(
|
||||
400,
|
||||
{
|
||||
"status": "error",
|
||||
"error": "TRAVERSAL DETECTED",
|
||||
"error_data": str(e),
|
||||
},
|
||||
)
|
||||
# TODO: limit some columns for specific permissions?
|
||||
folder = data["folder"]
|
||||
return_json = {
|
||||
"root_path": {
|
||||
"path": folder,
|
||||
"top": data["folder"]
|
||||
== self.controller.servers.get_server_data_by_id(server_id)["path"],
|
||||
}
|
||||
}
|
||||
|
||||
dir_list = []
|
||||
unsorted_files = []
|
||||
file_list = os.listdir(folder)
|
||||
for item in file_list:
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
dir_list.append(item)
|
||||
else:
|
||||
unsorted_files.append(item)
|
||||
file_list = sorted(dir_list, key=str.casefold) + sorted(
|
||||
unsorted_files, key=str.casefold
|
||||
)
|
||||
for raw_filename in file_list:
|
||||
filename = html.escape(raw_filename)
|
||||
rel = os.path.join(folder, raw_filename)
|
||||
dpath = os.path.join(folder, filename)
|
||||
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
|
||||
server_id
|
||||
):
|
||||
if os.path.isdir(rel):
|
||||
return_json[filename] = {
|
||||
"path": dpath,
|
||||
"dir": True,
|
||||
"excluded": True,
|
||||
}
|
||||
else:
|
||||
return_json[filename] = {
|
||||
"path": dpath,
|
||||
"dir": False,
|
||||
"excluded": True,
|
||||
}
|
||||
else:
|
||||
if os.path.isdir(rel):
|
||||
return_json[filename] = {
|
||||
"path": dpath,
|
||||
"dir": True,
|
||||
"excluded": False,
|
||||
}
|
||||
else:
|
||||
return_json[filename] = {
|
||||
"path": dpath,
|
||||
"dir": False,
|
||||
"excluded": False,
|
||||
}
|
||||
self.finish_json(200, {"status": "ok", "data": return_json})
|
@ -577,68 +577,81 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getTreeView(path) {
|
||||
path = path
|
||||
function getDirView(event){
|
||||
let path = event.target.parentElement.getAttribute("data-path");
|
||||
if (document.getElementById(path).classList.contains('clicked')) {
|
||||
return;
|
||||
}else{
|
||||
getTreeView(path);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: '/ajax/get_backup_tree?id=' + server_id + '&path=' + path,
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
console.log("got response:");
|
||||
console.log(data);
|
||||
}
|
||||
async function getTreeView(path){
|
||||
var token = getCookie("_xsrf");
|
||||
let res = await fetch(`/api/v2/servers/${server_id}/backups/files`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token
|
||||
},
|
||||
body: JSON.stringify({"page": "backups", "folder": path}),
|
||||
});
|
||||
let responseData = await res.json();
|
||||
if (responseData.status === "ok") {
|
||||
console.log(responseData);
|
||||
process_tree_response(responseData);
|
||||
|
||||
dataArr = data.split('\n');
|
||||
serverDir = dataArr.shift(); // Remove & return first element (server directory)
|
||||
text = dataArr.join('\n');
|
||||
} else {
|
||||
|
||||
try {
|
||||
bootbox.alert({
|
||||
title: responseData.status,
|
||||
message: responseData.error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function process_tree_response(response) {
|
||||
let path = response.data.root_path.path;
|
||||
let text = `<ul class="tree-nested d-block" id="${path}ul">`;
|
||||
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;
|
||||
}
|
||||
let checked = ""
|
||||
let dpath = value.path;
|
||||
let filename = key;
|
||||
if (value.dir){
|
||||
if (value.excluded){
|
||||
checked = "checked"
|
||||
}
|
||||
text += `<li class="tree-item" 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" 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>
|
||||
<strong>${filename}</strong>
|
||||
</span>
|
||||
</input></div><li>`
|
||||
}else{
|
||||
text += `<li
|
||||
class="d-block tree-ctx-item tree-file"
|
||||
data-path="${dpath}"
|
||||
data-name="${filename}"
|
||||
onclick=""><input type='checkbox' class="checkBoxClass" name='root_path' value="${dpath}" ${checked}><span style="margin-right: 6px;">
|
||||
<i class="far fa-file"></i></span></input>${filename}</li>`
|
||||
}
|
||||
});
|
||||
text += `</ul>`;
|
||||
if(response.data.root_path.top){
|
||||
try {
|
||||
document.getElementById('main-tree-div').innerHTML += text;
|
||||
document.getElementById('main-tree').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');
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
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) {
|
||||
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: '/ajax/get_backup_dir?id=' + server_id + '&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 {
|
||||
}else{
|
||||
try {
|
||||
document.getElementById(path + "span").classList.add('tree-caret-down');
|
||||
document.getElementById(path).innerHTML += text;
|
||||
document.getElementById(path).classList.add("clicked");
|
||||
@ -646,7 +659,7 @@
|
||||
console.log("Bad")
|
||||
}
|
||||
|
||||
var toggler = document.getElementById(path);
|
||||
var toggler = document.getElementById(path + "span");
|
||||
|
||||
if (toggler.classList.contains('files-tree-title')) {
|
||||
document.getElementById(path + "span").addEventListener("click", function caretListener() {
|
||||
@ -654,10 +667,15 @@
|
||||
document.getElementById(path + "span").classList.toggle("tree-caret-down");
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 show_file_tree() {
|
||||
$("#dir_select").modal();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user