mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Merge branch 'refactor/upload-api' into 'dev'
Chunked Uploads | Fix Upload Authentication See merge request crafty-controller/crafty-4!762
This commit is contained in:
commit
b71b7cb1c4
@ -4,6 +4,10 @@
|
|||||||
TBD
|
TBD
|
||||||
### Refactor
|
### Refactor
|
||||||
- Backups | Allow multiple backup configurations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/711))
|
- Backups | Allow multiple backup configurations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/711))
|
||||||
|
- UploadAPI | Use Crafty's JWT authentication for file uploads ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/762))
|
||||||
|
- UploadAPI | Splice files on the frontend to allow chunked uploads as well as bulk uploads ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/762))
|
||||||
|
- UploadAPI | Enhance upload progress feedback on all upload pages ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/762))
|
||||||
|
- UploadAPI | Consolidate and improve speed on uploads, supporting 100mb+ uploads through Cloudflare(Free) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/762))
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
- Fix zip imports so the root dir selection is functional ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/764))
|
- Fix zip imports so the root dir selection is functional ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/764))
|
||||||
- Fix bug where full access gives minimal access ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/768))
|
- Fix bug where full access gives minimal access ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/768))
|
||||||
|
@ -4,6 +4,9 @@ import logging
|
|||||||
import pathlib
|
import pathlib
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import hashlib
|
||||||
|
from typing import BinaryIO
|
||||||
|
import mimetypes
|
||||||
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
|
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import ssl
|
import ssl
|
||||||
@ -22,6 +25,7 @@ class FileHelpers:
|
|||||||
|
|
||||||
def __init__(self, helper):
|
def __init__(self, helper):
|
||||||
self.helper: Helpers = helper
|
self.helper: Helpers = helper
|
||||||
|
self.mime_types = mimetypes.MimeTypes()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ssl_get_file(
|
def ssl_get_file(
|
||||||
@ -142,6 +146,32 @@ class FileHelpers:
|
|||||||
logger.error(f"Path specified is not a file or does not exist. {path}")
|
logger.error(f"Path specified is not a file or does not exist. {path}")
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
def check_mime_types(self, file_path):
|
||||||
|
m_type, _value = self.mime_types.guess_type(file_path)
|
||||||
|
return m_type
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_file_hash(file_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Takes one parameter of file path.
|
||||||
|
It will generate a SHA256 hash for the path and return it.
|
||||||
|
"""
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
for byte_block in iter(lambda: f.read(4096), b""):
|
||||||
|
sha256_hash.update(byte_block)
|
||||||
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_buffer_hash(buffer: BinaryIO) -> str:
|
||||||
|
"""
|
||||||
|
Takes one argument of a stream buffer. Will return a
|
||||||
|
sha256 hash of the buffer
|
||||||
|
"""
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
sha256_hash.update(buffer)
|
||||||
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def copy_dir(src_path, dest_path, dirs_exist_ok=False):
|
def copy_dir(src_path, dest_path, dirs_exist_ok=False):
|
||||||
# pylint: disable=unexpected-keyword-arg
|
# pylint: disable=unexpected-keyword-arg
|
||||||
|
@ -508,7 +508,6 @@ class Helpers:
|
|||||||
"max_log_lines": 700,
|
"max_log_lines": 700,
|
||||||
"max_audit_entries": 300,
|
"max_audit_entries": 300,
|
||||||
"disabled_language_files": [],
|
"disabled_language_files": [],
|
||||||
"stream_size_GB": 1,
|
|
||||||
"keywords": ["help", "chunk"],
|
"keywords": ["help", "chunk"],
|
||||||
"allow_nsfw_profile_pictures": False,
|
"allow_nsfw_profile_pictures": False,
|
||||||
"enable_user_self_delete": False,
|
"enable_user_self_delete": False,
|
||||||
|
@ -799,6 +799,18 @@ class TasksManager:
|
|||||||
self.helper.ensure_dir_exists(
|
self.helper.ensure_dir_exists(
|
||||||
os.path.join(self.controller.project_root, "import", "upload")
|
os.path.join(self.controller.project_root, "import", "upload")
|
||||||
)
|
)
|
||||||
|
self.helper.ensure_dir_exists(
|
||||||
|
os.path.join(self.controller.project_root, "temp")
|
||||||
|
)
|
||||||
|
for file in os.listdir(os.path.join(self.controller.project_root, "temp")):
|
||||||
|
if self.helper.is_file_older_than_x_days(
|
||||||
|
os.path.join(self.controller.project_root, "temp", file)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(file))
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.debug("Could not clear out file from temp directory")
|
||||||
|
|
||||||
for file in os.listdir(
|
for file in os.listdir(
|
||||||
os.path.join(self.controller.project_root, "import", "upload")
|
os.path.join(self.controller.project_root, "import", "upload")
|
||||||
):
|
):
|
||||||
@ -807,7 +819,7 @@ class TasksManager:
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(file))
|
os.remove(os.path.join(file))
|
||||||
except:
|
except FileNotFoundError:
|
||||||
logger.debug("Could not clear out file from import directory")
|
logger.debug("Could not clear out file from import directory")
|
||||||
|
|
||||||
def log_watcher(self):
|
def log_watcher(self):
|
||||||
|
@ -45,6 +45,7 @@ from app.classes.web.routes.api.servers.server.files import (
|
|||||||
ApiServersServerFilesCreateHandler,
|
ApiServersServerFilesCreateHandler,
|
||||||
ApiServersServerFilesZipHandler,
|
ApiServersServerFilesZipHandler,
|
||||||
)
|
)
|
||||||
|
from app.classes.web.routes.api.crafty.upload.index import ApiFilesUploadHandler
|
||||||
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
||||||
ApiServersServerTasksTaskChildrenHandler,
|
ApiServersServerTasksTaskChildrenHandler,
|
||||||
)
|
)
|
||||||
@ -238,6 +239,21 @@ def api_handlers(handler_args):
|
|||||||
ApiServersServerFilesZipHandler,
|
ApiServersServerFilesZipHandler,
|
||||||
handler_args,
|
handler_args,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/crafty/admin/upload/?",
|
||||||
|
ApiFilesUploadHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/import/upload/?",
|
||||||
|
ApiFilesUploadHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r"/api/v2/servers/([a-z0-9-]+)/files/upload/?",
|
||||||
|
ApiFilesUploadHandler,
|
||||||
|
handler_args,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
r"/api/v2/servers/([a-z0-9-]+)/files(?:/([a-zA-Z0-9-]+))?/?",
|
r"/api/v2/servers/([a-z0-9-]+)/files(?:/([a-zA-Z0-9-]+))?/?",
|
||||||
ApiServersServerFilesIndexHandler,
|
ApiServersServerFilesIndexHandler,
|
||||||
|
308
app/classes/web/routes/api/crafty/upload/index.py
Normal file
308
app/classes/web/routes/api/crafty/upload/index.py
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
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__)
|
||||||
|
IMAGE_MIME_TYPES = [
|
||||||
|
"image/bmp",
|
||||||
|
"image/cis-cod",
|
||||||
|
"image/gif",
|
||||||
|
"image/ief",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/pipeg",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/tiff",
|
||||||
|
"image/x-cmu-raster",
|
||||||
|
"image/x-cmx",
|
||||||
|
"image/x-icon",
|
||||||
|
"image/x-portable-anymap",
|
||||||
|
"image/x-portable-bitmap",
|
||||||
|
"image/x-portable-graymap",
|
||||||
|
"image/x-portable-pixmap",
|
||||||
|
"image/x-rgb",
|
||||||
|
"image/x-xbitmap",
|
||||||
|
"image/x-xpixmap",
|
||||||
|
"image/x-xwindowdump",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
]
|
||||||
|
|
||||||
|
ARCHIVE_MIME_TYPES = ["application/zip"]
|
||||||
|
|
||||||
|
|
||||||
|
class ApiFilesUploadHandler(BaseApiHandler):
|
||||||
|
async def post(self, server_id=None):
|
||||||
|
auth_data = self.authenticate_user()
|
||||||
|
if not auth_data:
|
||||||
|
return
|
||||||
|
|
||||||
|
upload_type = self.request.headers.get("type")
|
||||||
|
accepted_types = []
|
||||||
|
|
||||||
|
if server_id:
|
||||||
|
# Check to make sure user is authorized for the server
|
||||||
|
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"}
|
||||||
|
)
|
||||||
|
mask = self.controller.server_perms.get_lowest_api_perm_mask(
|
||||||
|
self.controller.server_perms.get_user_permissions_mask(
|
||||||
|
auth_data[4]["user_id"], server_id
|
||||||
|
),
|
||||||
|
auth_data[5],
|
||||||
|
)
|
||||||
|
# Make sure user has file access for the server
|
||||||
|
server_permissions = self.controller.server_perms.get_permissions(mask)
|
||||||
|
if EnumPermissionsServer.FILES not in server_permissions:
|
||||||
|
# if the user doesn't have Files permission, return an error
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "NOT_AUTHORIZED"}
|
||||||
|
)
|
||||||
|
|
||||||
|
u_type = "server_upload"
|
||||||
|
# Make sure user is a super user if they're changing panel settings
|
||||||
|
elif auth_data[4]["superuser"] and upload_type == "background":
|
||||||
|
u_type = "admin_config"
|
||||||
|
self.upload_dir = os.path.join(
|
||||||
|
self.controller.project_root,
|
||||||
|
"app/frontend/static/assets/images/auth/custom",
|
||||||
|
)
|
||||||
|
accepted_types = IMAGE_MIME_TYPES
|
||||||
|
elif upload_type == "import":
|
||||||
|
# Check that user can make servers
|
||||||
|
if (
|
||||||
|
not self.controller.crafty_perms.can_create_server(
|
||||||
|
auth_data[4]["user_id"]
|
||||||
|
)
|
||||||
|
and not auth_data[4]["superuser"]
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT_AUTHORIZED",
|
||||||
|
"data": {"message": ""},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Set directory to upload import dir
|
||||||
|
self.upload_dir = os.path.join(
|
||||||
|
self.controller.project_root, "import", "upload"
|
||||||
|
)
|
||||||
|
u_type = "server_import"
|
||||||
|
accepted_types = ARCHIVE_MIME_TYPES
|
||||||
|
else:
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT_AUTHORIZED",
|
||||||
|
"data": {"message": ""},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Get the headers from the request
|
||||||
|
self.chunk_hash = self.request.headers.get("chunkHash", 0)
|
||||||
|
self.file_id = self.request.headers.get("fileId")
|
||||||
|
self.chunked = self.request.headers.get("chunked", False)
|
||||||
|
self.filename = self.request.headers.get("fileName", None)
|
||||||
|
try:
|
||||||
|
file_size = int(self.request.headers.get("fileSize", None))
|
||||||
|
total_chunks = int(self.request.headers.get("totalChunks", 0))
|
||||||
|
except TypeError:
|
||||||
|
return self.finish_json(
|
||||||
|
400, {"status": "error", "error": "TYPE ERROR", "data": {}}
|
||||||
|
)
|
||||||
|
self.chunk_index = self.request.headers.get("chunkId")
|
||||||
|
if u_type == "server_upload":
|
||||||
|
self.upload_dir = self.request.headers.get("location", None)
|
||||||
|
self.temp_dir = os.path.join(self.controller.project_root, "temp", self.file_id)
|
||||||
|
|
||||||
|
if u_type == "server_upload":
|
||||||
|
# If this is an upload from a server the path will be what
|
||||||
|
# Is requested
|
||||||
|
full_path = os.path.join(self.upload_dir, self.filename)
|
||||||
|
|
||||||
|
# Check to make sure the requested path is inside the server's directory
|
||||||
|
if not self.helper.is_subdir(
|
||||||
|
full_path,
|
||||||
|
Helpers.get_os_understandable_path(
|
||||||
|
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NOT AUTHORIZED",
|
||||||
|
"data": {"message": "Traversal detected"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Check to make sure the file type we're being sent is what we're expecting
|
||||||
|
if (
|
||||||
|
self.file_helper.check_mime_types(self.filename) not in accepted_types
|
||||||
|
and u_type != "server_upload"
|
||||||
|
):
|
||||||
|
return self.finish_json(
|
||||||
|
422,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID FILE TYPE",
|
||||||
|
"data": {
|
||||||
|
"message": f"Invalid File Type only accepts {accepted_types}"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_total, _used, free = shutil.disk_usage(self.upload_dir)
|
||||||
|
|
||||||
|
# Check to see if we have enough space
|
||||||
|
if free <= file_size:
|
||||||
|
return self.finish_json(
|
||||||
|
507,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "NO STORAGE SPACE",
|
||||||
|
"data": {"message": "Out Of Space!"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# If this has no chunk index we know it's the inital request
|
||||||
|
if self.chunked and not self.chunk_index:
|
||||||
|
return self.finish_json(
|
||||||
|
200, {"status": "ok", "data": {"file-id": self.file_id}}
|
||||||
|
)
|
||||||
|
# Create the upload and temp directories if they don't exist
|
||||||
|
os.makedirs(self.upload_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Check for chunked header. We will handle this request differently
|
||||||
|
# if it doesn't exist
|
||||||
|
if not self.chunked:
|
||||||
|
# Write the file directly to the upload dir
|
||||||
|
with open(os.path.join(self.upload_dir, self.filename), "wb") as file:
|
||||||
|
chunk = self.request.body
|
||||||
|
if chunk:
|
||||||
|
file.write(chunk)
|
||||||
|
# We'll check the file hash against the sent hash once the file is
|
||||||
|
# written. We cannot check this buffer.
|
||||||
|
calculated_hash = self.file_helper.calculate_file_hash(
|
||||||
|
os.path.join(self.upload_dir, self.filename)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"File upload completed. Filename: {self.filename} Type: {u_type}"
|
||||||
|
)
|
||||||
|
return self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "completed",
|
||||||
|
"data": {"message": "File uploaded successfully"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Since this is a chunked upload we'll create the temp dir for parts.
|
||||||
|
os.makedirs(self.temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Read headers and query parameters
|
||||||
|
content_length = int(self.request.headers.get("Content-Length"))
|
||||||
|
if content_length <= 0:
|
||||||
|
logger.error(
|
||||||
|
f"File upload failed. Filename: {self.filename}"
|
||||||
|
f"Type: {u_type} Error: INVALID CONTENT LENGTH"
|
||||||
|
)
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID CONTENT LENGTH",
|
||||||
|
"data": {"message": "Invalid content length"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# At this point filename, chunk index and total chunks are required
|
||||||
|
# in the request
|
||||||
|
if not self.filename or self.chunk_index is None:
|
||||||
|
logger.error(
|
||||||
|
f"File upload failed. Filename: {self.filename}"
|
||||||
|
f"Type: {u_type} Error: CHUNK INDEX NOT FOUND"
|
||||||
|
)
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INDEX ERROR",
|
||||||
|
"data": {
|
||||||
|
"message": "Filename, chunk_index,"
|
||||||
|
" and total_chunks are required"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate the hash of the buffer and compare it against the expected hash
|
||||||
|
calculated_hash = self.file_helper.calculate_buffer_hash(self.request.body)
|
||||||
|
if str(self.chunk_hash) != str(calculated_hash):
|
||||||
|
logger.error(
|
||||||
|
f"File upload failed. Filename: {self.filename}"
|
||||||
|
f"Type: {u_type} Error: INVALID HASH"
|
||||||
|
)
|
||||||
|
return self.finish_json(
|
||||||
|
400,
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"error": "INVALID_HASH",
|
||||||
|
"data": {
|
||||||
|
"message": "Hash recieved does not match reported sent hash.",
|
||||||
|
"chunk_id": self.chunk_index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# File paths
|
||||||
|
file_path = os.path.join(self.upload_dir, self.filename)
|
||||||
|
chunk_path = os.path.join(
|
||||||
|
self.temp_dir, f"{self.filename}.part{self.chunk_index}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the chunk
|
||||||
|
with open(chunk_path, "wb") as f:
|
||||||
|
f.write(self.request.body)
|
||||||
|
|
||||||
|
# Check if all chunks are received
|
||||||
|
received_chunks = [
|
||||||
|
f
|
||||||
|
for f in os.listdir(self.temp_dir)
|
||||||
|
if f.startswith(f"{self.filename}.part")
|
||||||
|
]
|
||||||
|
# When we've reached the total chunks we'll
|
||||||
|
# Compare the hash and write the file
|
||||||
|
if len(received_chunks) == total_chunks:
|
||||||
|
with open(file_path, "wb") as outfile:
|
||||||
|
for i in range(total_chunks):
|
||||||
|
chunk_file = os.path.join(self.temp_dir, f"{self.filename}.part{i}")
|
||||||
|
with open(chunk_file, "rb") as infile:
|
||||||
|
outfile.write(infile.read())
|
||||||
|
os.remove(chunk_file)
|
||||||
|
logger.info(
|
||||||
|
f"File upload completed. Filename: {self.filename}"
|
||||||
|
f" Path: {file_path} Type: {u_type}"
|
||||||
|
)
|
||||||
|
self.controller.management.add_to_audit_log(
|
||||||
|
auth_data[4]["user_id"],
|
||||||
|
f"Uploaded file {self.filename}",
|
||||||
|
server_id,
|
||||||
|
self.request.remote_ip,
|
||||||
|
)
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "completed",
|
||||||
|
"data": {"message": "File uploaded successfully"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.finish_json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
"status": "partial",
|
||||||
|
"data": {"message": f"Chunk {self.chunk_index} received"},
|
||||||
|
},
|
||||||
|
)
|
@ -24,7 +24,6 @@ from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers
|
|||||||
from app.classes.web.server_handler import ServerHandler
|
from app.classes.web.server_handler import ServerHandler
|
||||||
from app.classes.web.websocket_handler import WebSocketHandler
|
from app.classes.web.websocket_handler import WebSocketHandler
|
||||||
from app.classes.web.static_handler import CustomStaticHandler
|
from app.classes.web.static_handler import CustomStaticHandler
|
||||||
from app.classes.web.upload_handler import UploadHandler
|
|
||||||
from app.classes.web.status_handler import StatusHandler
|
from app.classes.web.status_handler import StatusHandler
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +141,6 @@ class Webserver:
|
|||||||
(r"/panel/(.*)", PanelHandler, handler_args),
|
(r"/panel/(.*)", PanelHandler, handler_args),
|
||||||
(r"/server/(.*)", ServerHandler, handler_args),
|
(r"/server/(.*)", ServerHandler, handler_args),
|
||||||
(r"/ws", WebSocketHandler, handler_args),
|
(r"/ws", WebSocketHandler, handler_args),
|
||||||
(r"/upload", UploadHandler, handler_args),
|
|
||||||
(r"/status", StatusHandler, handler_args),
|
(r"/status", StatusHandler, handler_args),
|
||||||
# API Routes V2
|
# API Routes V2
|
||||||
*api_handlers(handler_args),
|
*api_handlers(handler_args),
|
||||||
|
@ -1,331 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import urllib.parse
|
|
||||||
import tornado.web
|
|
||||||
import tornado.options
|
|
||||||
import tornado.httpserver
|
|
||||||
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
|
|
||||||
|
|
||||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
|
||||||
from app.classes.shared.console import Console
|
|
||||||
from app.classes.shared.helpers import Helpers
|
|
||||||
from app.classes.shared.main_controller import Controller
|
|
||||||
from app.classes.web.base_handler import BaseHandler
|
|
||||||
from app.classes.shared.websocket_manager import WebSocketManager
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@tornado.web.stream_request_body
|
|
||||||
class UploadHandler(BaseHandler):
|
|
||||||
# noinspection PyAttributeOutsideInit
|
|
||||||
def initialize(
|
|
||||||
self,
|
|
||||||
helper: Helpers = None,
|
|
||||||
controller: Controller = None,
|
|
||||||
tasks_manager=None,
|
|
||||||
translator=None,
|
|
||||||
file_helper=None,
|
|
||||||
):
|
|
||||||
self.helper = helper
|
|
||||||
self.controller = controller
|
|
||||||
self.tasks_manager = tasks_manager
|
|
||||||
self.translator = translator
|
|
||||||
self.file_helper = file_helper
|
|
||||||
|
|
||||||
def prepare(self):
|
|
||||||
# Class & Function Defination
|
|
||||||
api_key, _token_data, exec_user = self.current_user
|
|
||||||
self.upload_type = str(self.request.headers.get("X-Content-Upload-Type"))
|
|
||||||
|
|
||||||
if self.upload_type == "server_import":
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.full_access
|
|
||||||
user_id = exec_user["user_id"]
|
|
||||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
|
||||||
|
|
||||||
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
|
||||||
|
|
||||||
self.content_len = int(self.request.headers.get("Content-Length"))
|
|
||||||
if self.content_len > max_streamed_size:
|
|
||||||
logger.error(
|
|
||||||
f"User with ID {user_id} attempted to upload a file that"
|
|
||||||
f" exceeded the max body size."
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.finish_json(
|
|
||||||
413,
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"error": "TOO LARGE",
|
|
||||||
"info": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"fileTooLarge",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.do_upload = True
|
|
||||||
|
|
||||||
if superuser:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.server_perms.list_defined_permissions()
|
|
||||||
)
|
|
||||||
elif api_key is not None:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.crafty_perms.get_api_key_permissions_list(api_key)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.crafty_perms.get_crafty_permissions_list(
|
|
||||||
exec_user["user_id"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_id is None:
|
|
||||||
logger.warning("User ID not found in upload handler call")
|
|
||||||
Console.warning("User ID not found in upload handler call")
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
if (
|
|
||||||
EnumPermissionsCrafty.SERVER_CREATION
|
|
||||||
not in exec_user_server_permissions
|
|
||||||
and not exec_user["superuser"]
|
|
||||||
):
|
|
||||||
logger.warning(
|
|
||||||
f"User {user_id} tried to upload a server" " without permissions!"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"User {user_id} tried to upload a server" " without permissions!"
|
|
||||||
)
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
path = os.path.join(self.controller.project_root, "import", "upload")
|
|
||||||
self.helper.ensure_dir_exists(path)
|
|
||||||
# Delete existing files
|
|
||||||
if len(os.listdir(path)) > 0:
|
|
||||||
for item in os.listdir():
|
|
||||||
try:
|
|
||||||
os.remove(os.path.join(path, item))
|
|
||||||
except:
|
|
||||||
logger.debug("Could not delete file on user server upload")
|
|
||||||
|
|
||||||
self.helper.ensure_dir_exists(path)
|
|
||||||
filename = urllib.parse.unquote(
|
|
||||||
self.request.headers.get("X-FileName", None)
|
|
||||||
)
|
|
||||||
if not str(filename).endswith(".zip"):
|
|
||||||
WebSocketManager().broadcast("close_upload_box", "error")
|
|
||||||
self.finish("error")
|
|
||||||
full_path = os.path.join(path, filename)
|
|
||||||
|
|
||||||
if self.do_upload:
|
|
||||||
try:
|
|
||||||
self.f = open(full_path, "wb")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Upload failed with error: {e}")
|
|
||||||
self.do_upload = False
|
|
||||||
# If max_body_size is not set, you cannot upload files > 100MB
|
|
||||||
self.request.connection.set_max_body_size(max_streamed_size)
|
|
||||||
|
|
||||||
elif self.upload_type == "background":
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.full_access
|
|
||||||
user_id = exec_user["user_id"]
|
|
||||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
|
||||||
|
|
||||||
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
|
||||||
|
|
||||||
self.content_len = int(self.request.headers.get("Content-Length"))
|
|
||||||
if self.content_len > max_streamed_size:
|
|
||||||
logger.error(
|
|
||||||
f"User with ID {user_id} attempted to upload a file that"
|
|
||||||
f" exceeded the max body size."
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.finish_json(
|
|
||||||
413,
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"error": "TOO LARGE",
|
|
||||||
"info": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"fileTooLarge",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.do_upload = True
|
|
||||||
|
|
||||||
if not superuser:
|
|
||||||
return self.finish_json(
|
|
||||||
401,
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"error": "UNAUTHORIZED ACCESS",
|
|
||||||
"info": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"superError",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if not self.request.headers.get("X-Content-Type", None).startswith(
|
|
||||||
"image/"
|
|
||||||
):
|
|
||||||
return self.finish_json(
|
|
||||||
415,
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"error": "TYPE ERROR",
|
|
||||||
"info": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"fileError",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if user_id is None:
|
|
||||||
logger.warning("User ID not found in upload handler call")
|
|
||||||
Console.warning("User ID not found in upload handler call")
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
path = os.path.join(
|
|
||||||
self.controller.project_root,
|
|
||||||
"app/frontend/static/assets/images/auth/custom",
|
|
||||||
)
|
|
||||||
filename = self.request.headers.get("X-FileName", None)
|
|
||||||
full_path = os.path.join(path, filename)
|
|
||||||
|
|
||||||
if self.do_upload:
|
|
||||||
try:
|
|
||||||
self.f = open(full_path, "wb")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Upload failed with error: {e}")
|
|
||||||
self.do_upload = False
|
|
||||||
# If max_body_size is not set, you cannot upload files > 100MB
|
|
||||||
self.request.connection.set_max_body_size(max_streamed_size)
|
|
||||||
else:
|
|
||||||
server_id = self.get_argument("server_id", None)
|
|
||||||
superuser = exec_user["superuser"]
|
|
||||||
if api_key is not None:
|
|
||||||
superuser = superuser and api_key.full_access
|
|
||||||
user_id = exec_user["user_id"]
|
|
||||||
stream_size_value = self.helper.get_setting("stream_size_GB")
|
|
||||||
|
|
||||||
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
|
|
||||||
|
|
||||||
self.content_len = int(self.request.headers.get("Content-Length"))
|
|
||||||
if self.content_len > max_streamed_size:
|
|
||||||
logger.error(
|
|
||||||
f"User with ID {user_id} attempted to upload a file that"
|
|
||||||
f" exceeded the max body size."
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.finish_json(
|
|
||||||
413,
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"error": "TOO LARGE",
|
|
||||||
"info": self.helper.translation.translate(
|
|
||||||
"error",
|
|
||||||
"fileTooLarge",
|
|
||||||
self.controller.users.get_user_lang_by_id(user_id),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.do_upload = True
|
|
||||||
|
|
||||||
if superuser:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.server_perms.list_defined_permissions()
|
|
||||||
)
|
|
||||||
elif api_key is not None:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.server_perms.get_api_key_permissions_list(
|
|
||||||
api_key, server_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
exec_user_server_permissions = (
|
|
||||||
self.controller.server_perms.get_user_id_permissions_list(
|
|
||||||
exec_user["user_id"], server_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
server_id = self.request.headers.get("X-ServerId", None)
|
|
||||||
if server_id is None:
|
|
||||||
logger.warning("Server ID not found in upload handler call")
|
|
||||||
Console.warning("Server ID not found in upload handler call")
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
if user_id is None:
|
|
||||||
logger.warning("User ID not found in upload handler call")
|
|
||||||
Console.warning("User ID not found in upload handler call")
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
|
|
||||||
logger.warning(
|
|
||||||
f"User {user_id} tried to upload a file to "
|
|
||||||
f"{server_id} without permissions!"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"User {user_id} tried to upload a file to "
|
|
||||||
f"{server_id} without permissions!"
|
|
||||||
)
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
path = self.request.headers.get("X-Path", None)
|
|
||||||
filename = self.request.headers.get("X-FileName", None)
|
|
||||||
full_path = os.path.join(path, filename)
|
|
||||||
|
|
||||||
if not self.helper.is_subdir(
|
|
||||||
full_path,
|
|
||||||
Helpers.get_os_understandable_path(
|
|
||||||
self.controller.servers.get_server_data_by_id(server_id)["path"]
|
|
||||||
),
|
|
||||||
):
|
|
||||||
logger.warning(
|
|
||||||
f"User {user_id} tried to upload a file to {server_id} "
|
|
||||||
f"but the path is not inside of the server!"
|
|
||||||
)
|
|
||||||
Console.warning(
|
|
||||||
f"User {user_id} tried to upload a file to {server_id} "
|
|
||||||
f"but the path is not inside of the server!"
|
|
||||||
)
|
|
||||||
self.do_upload = False
|
|
||||||
|
|
||||||
if self.do_upload:
|
|
||||||
try:
|
|
||||||
self.f = open(full_path, "wb")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Upload failed with error: {e}")
|
|
||||||
self.do_upload = False
|
|
||||||
# If max_body_size is not set, you cannot upload files > 100MB
|
|
||||||
self.request.connection.set_max_body_size(max_streamed_size)
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
logger.info("Upload completed")
|
|
||||||
if self.upload_type == "server_files":
|
|
||||||
files_left = int(self.request.headers.get("X-Files-Left", None))
|
|
||||||
else:
|
|
||||||
files_left = 0
|
|
||||||
|
|
||||||
if self.do_upload:
|
|
||||||
time.sleep(5)
|
|
||||||
if files_left == 0:
|
|
||||||
WebSocketManager().broadcast("close_upload_box", "success")
|
|
||||||
self.finish("success") # Nope, I'm sending "success"
|
|
||||||
self.f.close()
|
|
||||||
else:
|
|
||||||
time.sleep(5)
|
|
||||||
if files_left == 0:
|
|
||||||
WebSocketManager().broadcast("close_upload_box", "error")
|
|
||||||
self.finish("error")
|
|
||||||
|
|
||||||
def data_received(self, chunk):
|
|
||||||
if self.do_upload:
|
|
||||||
self.f.write(chunk)
|
|
208
app/frontend/static/assets/js/shared/upload.js
Normal file
208
app/frontend/static/assets/js/shared/upload.js
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
async function uploadFile(type, file = null, path = null, file_num = 0, _onProgress = null) {
|
||||||
|
if (file == null) {
|
||||||
|
try {
|
||||||
|
file = $("#file")[0].files[0];
|
||||||
|
} catch {
|
||||||
|
bootbox.alert("Please select a file first.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const fileId = uuidv4();
|
||||||
|
const token = getCookie("_xsrf");
|
||||||
|
if (type !== "server_upload") {
|
||||||
|
document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%;"><div id="upload-progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = ``
|
||||||
|
if (type === "server_upload") {
|
||||||
|
url = `/api/v2/servers/${serverId}/files/upload/`;
|
||||||
|
} else if (type === "background") {
|
||||||
|
url = `/api/v2/crafty/admin/upload/`
|
||||||
|
} else if (type === "import") {
|
||||||
|
url = `/api/v2/servers/import/upload/`
|
||||||
|
}
|
||||||
|
console.log(url)
|
||||||
|
const chunkSize = 1024 * 1024 * 10; // 10MB
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
const uploadPromises = [];
|
||||||
|
let errors = []; // Array to store errors
|
||||||
|
try {
|
||||||
|
let res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRFToken': token,
|
||||||
|
'chunked': true,
|
||||||
|
'fileSize': file.size,
|
||||||
|
'type': type,
|
||||||
|
'totalChunks': totalChunks,
|
||||||
|
'fileName': file.name,
|
||||||
|
'location': path,
|
||||||
|
'fileId': fileId,
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
let errorResponse = await res.json();
|
||||||
|
throw new Error(JSON.stringify(errorResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseData = await res.json();
|
||||||
|
|
||||||
|
if (responseData.status !== "ok") {
|
||||||
|
throw new Error(JSON.stringify(responseData));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < totalChunks; i++) {
|
||||||
|
const start = i * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
const chunk_hash = await calculateFileHash(chunk);
|
||||||
|
|
||||||
|
const uploadPromise = fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: chunk,
|
||||||
|
headers: {
|
||||||
|
'Content-Range': `bytes ${start}-${end - 1}/${file.size}`,
|
||||||
|
'Content-Length': chunk.size,
|
||||||
|
'fileSize': file.size,
|
||||||
|
'chunkHash': chunk_hash,
|
||||||
|
'chunked': true,
|
||||||
|
'type': type,
|
||||||
|
'totalChunks': totalChunks,
|
||||||
|
'fileName': file.name,
|
||||||
|
'location': path,
|
||||||
|
'fileId': fileId,
|
||||||
|
'chunkId': i,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(JSON.stringify(errorData) || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
return response.json(); // Return the JSON data
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.status !== "completed" && data.status !== "partial") {
|
||||||
|
throw new Error(data.message || 'Unknown error occurred');
|
||||||
|
}
|
||||||
|
// Update progress bar
|
||||||
|
const progress = (i + 1) / totalChunks * 100;
|
||||||
|
updateProgressBar(Math.round(progress), type, file_num);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
errors.push(error); // Store the error
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadPromises.push(uploadPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(uploadPromises);
|
||||||
|
} catch (error) {
|
||||||
|
errors.push(error); // Store the error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const errorMessage = errors.map(error => JSON.parse(error.message).data.message || 'Unknown error occurred').join('<br>');
|
||||||
|
console.log(errorMessage)
|
||||||
|
bootbox.alert({
|
||||||
|
title: 'Error',
|
||||||
|
message: errorMessage,
|
||||||
|
callback: function () {
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (type !== "server_upload") {
|
||||||
|
// All promises resolved successfully
|
||||||
|
$("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${file.name}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
|
||||||
|
if (type === "import") {
|
||||||
|
document.getElementById("lower_half").style.visibility = "visible";
|
||||||
|
document.getElementById("lower_half").hidden = false;
|
||||||
|
} else if (type === "background") {
|
||||||
|
setTimeout(function () {
|
||||||
|
location.href = `/panel/custom_login`;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let caught = false;
|
||||||
|
let expanded = false;
|
||||||
|
try {
|
||||||
|
expanded = document.getElementById(path).classList.contains("clicked");
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
let par_el;
|
||||||
|
let items;
|
||||||
|
try {
|
||||||
|
par_el = document.getElementById(path + "ul");
|
||||||
|
items = par_el.children;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
caught = true;
|
||||||
|
par_el = document.getElementById("files-tree");
|
||||||
|
items = par_el.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = file.name;
|
||||||
|
let full_path = path + '/' + name;
|
||||||
|
let flag = false;
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
if ($(item).attr("data-name") === name) {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flag) {
|
||||||
|
if (caught && !expanded) {
|
||||||
|
$(par_el).append(`<li id="${full_path}li" class="d-block tree-ctx-item tree-file tree-item" data-path="${full_path}" data-name="${name}" onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>${name}</li>`);
|
||||||
|
} else if (expanded) {
|
||||||
|
$(par_el).append(`<li id="${full_path}li" class="tree-ctx-item tree-file tree-item" data-path="${full_path}" data-name="${name}" onclick="clickOnFile(event)"><span style="margin-right: 6px;"><i class="far fa-file"></i></span>${name}</li>`);
|
||||||
|
}
|
||||||
|
setTreeViewContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(`#upload-progress-bar-${file_num + 1}`).removeClass("progress-bar-striped");
|
||||||
|
$(`#upload-progress-bar-${file_num + 1}`).addClass("bg-success");
|
||||||
|
$(`#upload-progress-bar-${file_num + 1}`).html('<i style="color: black;" class="fas fa-box-check"></i>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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('');
|
||||||
|
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgressBar(progress, type, i) {
|
||||||
|
if (type !== "server_upload") {
|
||||||
|
if (progress === 100) {
|
||||||
|
$(`#upload-progress-bar`).removeClass("progress-bar-striped")
|
||||||
|
|
||||||
|
$(`#upload-progress-bar`).removeClass("progress-bar-animated")
|
||||||
|
}
|
||||||
|
$(`#upload-progress-bar`).css('width', progress + '%');
|
||||||
|
$(`#upload-progress-bar`).html(progress + '%');
|
||||||
|
} else {
|
||||||
|
if (progress === 100) {
|
||||||
|
$(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-striped")
|
||||||
|
|
||||||
|
$(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-animated")
|
||||||
|
}
|
||||||
|
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
|
||||||
|
$(`#upload-progress-bar-${i + 1}`).html(progress + '%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuidv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
const r = Math.random() * 16 | 0,
|
||||||
|
v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
@ -69,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-info upload-button" id="upload-button"
|
<button type="button" class="btn btn-info upload-button" id="upload-button"
|
||||||
onclick="sendFile()" disabled>UPLOAD</button>
|
onclick="uploadFile('background')" disabled>UPLOAD</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -381,61 +381,6 @@
|
|||||||
}
|
}
|
||||||
img.src = src_path;
|
img.src = src_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var file;
|
|
||||||
function sendFile() {
|
|
||||||
file = $("#file")[0].files[0]
|
|
||||||
document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>';
|
|
||||||
let xmlHttpRequest = new XMLHttpRequest();
|
|
||||||
let token = getCookie("_xsrf")
|
|
||||||
let fileName = file.name
|
|
||||||
let target = '/upload'
|
|
||||||
let mimeType = file.type
|
|
||||||
let size = file.size
|
|
||||||
let type = 'background'
|
|
||||||
|
|
||||||
xmlHttpRequest.upload.addEventListener('progress', function (e) {
|
|
||||||
|
|
||||||
if (e.loaded <= size) {
|
|
||||||
var percent = Math.round(e.loaded / size * 100);
|
|
||||||
$(`#upload-progress-bar`).css('width', percent + '%');
|
|
||||||
$(`#upload-progress-bar`).html(percent + '%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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-Content-Upload-Type', type);
|
|
||||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
|
||||||
xmlHttpRequest.addEventListener('load', (event) => {
|
|
||||||
if (event.target.responseText == 'success') {
|
|
||||||
console.log('Upload for file', file.name, 'was successful!')
|
|
||||||
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.reload();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let response_text = JSON.parse(event.target.responseText);
|
|
||||||
var x = document.querySelector('.bootbox');
|
|
||||||
console.log(JSON.parse(event.target.responseText).info)
|
|
||||||
bootbox.alert({
|
|
||||||
message: JSON.parse(event.target.responseText).info,
|
|
||||||
callback: function () {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
doUpload = false;
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.addEventListener('error', (e) => {
|
|
||||||
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.send(file);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="../../static/assets/js/shared/upload.js"></script>
|
||||||
{% end %}
|
{% end %}
|
@ -67,7 +67,8 @@
|
|||||||
translate('serverFiles', 'download', data['lang']) }}</a>
|
translate('serverFiles', 'download', data['lang']) }}</a>
|
||||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
||||||
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{
|
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteDir" href="#"
|
||||||
|
style="color: red">{{
|
||||||
translate('serverFiles', 'delete', data['lang']) }}</a>
|
translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||||
<a href="javascript:void(0)" class="closebtn" style="color: var(--info);"
|
<a href="javascript:void(0)" class="closebtn" style="color: var(--info);"
|
||||||
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
||||||
@ -156,6 +157,7 @@
|
|||||||
right: 35px;
|
right: 35px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-file:hover {
|
.tree-file:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -721,105 +723,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendFile(file, path, serverId, left, i, 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.upload.addEventListener('progress', function (e) {
|
|
||||||
|
|
||||||
if (e.loaded <= size) {
|
|
||||||
var percent = Math.round(e.loaded / size * 100);
|
|
||||||
$(`#upload-progress-bar-${i + 1}`).css('width', percent + '%');
|
|
||||||
$(`#upload-progress-bar-${i + 1}`).html(percent + '%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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-Content-Upload-Type', 'server_files')
|
|
||||||
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!');
|
|
||||||
let caught = false;
|
|
||||||
try {
|
|
||||||
if (document.getElementById(path).classList.contains("clicked")) {
|
|
||||||
var expanded = true;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
var expanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var par_el = document.getElementById(path + "ul");
|
|
||||||
var items = par_el.children;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
caught = true;
|
|
||||||
var par_el = document.getElementById("files-tree");
|
|
||||||
var items = par_el.children;
|
|
||||||
}
|
|
||||||
let name = file.name;
|
|
||||||
console.log(par_el)
|
|
||||||
let full_path = path + '/' + name
|
|
||||||
let flag = false;
|
|
||||||
for (var k = 0; k < items.length; ++k) {
|
|
||||||
if ($(items[k]).attr("data-name") == name) {
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!flag) {
|
|
||||||
if (caught && expanded == false) {
|
|
||||||
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="d-block tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
|
|
||||||
} else if (expanded == true) {
|
|
||||||
$(par_el).append('<li id=' + '"' + full_path.toString() + 'li' + '"' + 'class="tree-ctx-item tree-file tree-item" data-path=' + '"' + full_path.toString() + '"' + ' data-name=' + '"' + name.toString() + '"' + ' onclick="clickOnFile(event)" ><span style="margin-right: 6px;"><i class="far fa-file"></i></span>' + name + '</li>');
|
|
||||||
}
|
|
||||||
setTreeViewContext();
|
|
||||||
}
|
|
||||||
$(`#upload-progress-bar-${i + 1}`).removeClass("progress-bar-striped");
|
|
||||||
$(`#upload-progress-bar-${i + 1}`).addClass("bg-success");
|
|
||||||
$(`#upload-progress-bar-${i + 1}`).html('<i style="color: black;" class="fas fa-box-check"></i>')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let response_text = JSON.parse(event.target.responseText);
|
|
||||||
var x = document.querySelector('.bootbox');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
var x = document.querySelector('.modal-content');
|
|
||||||
if (x) {
|
|
||||||
x.remove()
|
|
||||||
}
|
|
||||||
console.log(JSON.parse(event.target.responseText).info)
|
|
||||||
bootbox.alert({
|
|
||||||
message: JSON.parse(event.target.responseText).info,
|
|
||||||
callback: function () {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
doUpload = false;
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.addEventListener('error', (e) => {
|
|
||||||
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.send(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
let uploadWaitDialog;
|
let uploadWaitDialog;
|
||||||
let doUpload = true;
|
|
||||||
|
|
||||||
async function uploadFilesE(event) {
|
async function uploadFilesE(event) {
|
||||||
path = event.target.parentElement.getAttribute('data-path');
|
path = event.target.parentElement.getAttribute('data-path');
|
||||||
@ -842,6 +746,9 @@
|
|||||||
label: "{{ translate('serverFiles', 'upload', data['lang']) }}",
|
label: "{{ translate('serverFiles', 'upload', data['lang']) }}",
|
||||||
className: "btn-default",
|
className: "btn-default",
|
||||||
callback: async function () {
|
callback: async function () {
|
||||||
|
if ($("#files").get(0).files.length === 0) {
|
||||||
|
return hideUploadBox();
|
||||||
|
}
|
||||||
var height = files.files.length * 50;
|
var height = files.files.length * 50;
|
||||||
|
|
||||||
var waitMessage = '<p class="text-center mb-0">' +
|
var waitMessage = '<p class="text-center mb-0">' +
|
||||||
@ -858,16 +765,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
let nFiles = files.files.length;
|
let nFiles = files.files.length;
|
||||||
for (i = 0; i < nFiles; i++) {
|
const uploadPromises = [];
|
||||||
if (!doUpload) {
|
|
||||||
doUpload = true;
|
|
||||||
hideUploadBox();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (let i = 0; i < nFiles; i++) {
|
||||||
|
const file = files.files[i];
|
||||||
const progressHtml = `
|
const progressHtml = `
|
||||||
<div style="width: 100%; min-width: 100%;">
|
<div style="width: 100%; min-width: 100%;">
|
||||||
${files.files[i].name}:
|
${file.name}:
|
||||||
<br><div
|
<br><div
|
||||||
id="upload-progress-bar-${i + 1}"
|
id="upload-progress-bar-${i + 1}"
|
||||||
class="progress-bar progress-bar-striped progress-bar-animated"
|
class="progress-bar progress-bar-striped progress-bar-animated"
|
||||||
@ -879,33 +783,38 @@
|
|||||||
></div>
|
></div>
|
||||||
</div><br>
|
</div><br>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
$('#upload-progress-bar-parent').append(progressHtml);
|
$('#upload-progress-bar-parent').append(progressHtml);
|
||||||
|
|
||||||
await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => {
|
const uploadPromise = uploadFile("server_upload", file, path, i, (progress) => {
|
||||||
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
|
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
|
||||||
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
|
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
|
||||||
});
|
});
|
||||||
|
uploadPromises.push(uploadPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await Promise.all(uploadPromises);
|
||||||
|
setTimeout(() => {
|
||||||
hideUploadBox();
|
hideUploadBox();
|
||||||
//$('#upload_file').submit(); //.trigger('submit');
|
}, 2000);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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('');
|
||||||
|
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
function getDirView(event) {
|
function getDirView(event) {
|
||||||
let path = event.target.parentElement.getAttribute("data-path");
|
let path = event.target.parentElement.getAttribute("data-path");
|
||||||
if (document.getElementById(path).classList.contains('clicked')) {
|
if (document.getElementById(path).classList.contains('clicked')) {
|
||||||
@ -1211,5 +1120,5 @@
|
|||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="../../static/assets/js/shared/upload.js"></script>
|
||||||
{% end %}
|
{% end %}
|
@ -313,8 +313,8 @@
|
|||||||
'labelZipFile', data['lang']) }}</label>
|
'labelZipFile', data['lang']) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()"
|
<button type="button" class="btn btn-info upload-button" id="upload-button"
|
||||||
disabled>{{ translate('serverWizard',
|
onclick="uploadFile('import')" disabled>{{ translate('serverWizard',
|
||||||
'uploadButton', data['lang']) }}</button>
|
'uploadButton', data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -513,61 +513,8 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% block js%}
|
{% block js%}
|
||||||
|
<script src="../../static/assets/js/shared/upload.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var upload;
|
|
||||||
var file;
|
|
||||||
function sendFile() {
|
|
||||||
file = $("#file")[0].files[0]
|
|
||||||
document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%;"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>'
|
|
||||||
let xmlHttpRequest = new XMLHttpRequest();
|
|
||||||
let token = getCookie("_xsrf")
|
|
||||||
let fileName = encodeURIComponent(file.name)
|
|
||||||
let target = '/upload'
|
|
||||||
let mimeType = file.type
|
|
||||||
let size = file.size
|
|
||||||
let type = 'server_import'
|
|
||||||
|
|
||||||
xmlHttpRequest.upload.addEventListener('progress', function (e) {
|
|
||||||
|
|
||||||
if (e.loaded <= size) {
|
|
||||||
var percent = Math.round(e.loaded / size * 100);
|
|
||||||
$(`#upload-progress-bar`).css('width', percent + '%');
|
|
||||||
$(`#upload-progress-bar`).html(percent + '%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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-Content-Upload-Type', type);
|
|
||||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
|
||||||
xmlHttpRequest.addEventListener('load', (event) => {
|
|
||||||
if (event.target.responseText == 'success') {
|
|
||||||
console.log('Upload for file', file.name, 'was successful!')
|
|
||||||
$("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${decodeURIComponent(fileName)}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
|
|
||||||
document.getElementById("lower_half").style.visibility = "visible";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let response_text = JSON.parse(event.target.responseText);
|
|
||||||
var x = document.querySelector('.bootbox');
|
|
||||||
console.log(JSON.parse(event.target.responseText).info)
|
|
||||||
bootbox.alert({
|
|
||||||
message: JSON.parse(event.target.responseText).info,
|
|
||||||
callback: function () {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
doUpload = false;
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.addEventListener('error', (e) => {
|
|
||||||
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.send(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("root_upload_button").addEventListener("click", function (event) {
|
document.getElementById("root_upload_button").addEventListener("click", function (event) {
|
||||||
if (file) {
|
if (file) {
|
||||||
upload = true;
|
upload = true;
|
||||||
|
@ -509,8 +509,8 @@
|
|||||||
'labelZipFile', data['lang']) }}</label>
|
'labelZipFile', data['lang']) }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-info upload-button" id="upload-button" onclick="sendFile()"
|
<button type="button" class="btn btn-info upload-button" id="upload-button"
|
||||||
disabled>{{ translate('serverWizard',
|
onclick="uploadFile('import')" disabled>{{ translate('serverWizard',
|
||||||
'uploadButton', data['lang']) }}</button>
|
'uploadButton', data['lang']) }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -805,6 +805,7 @@
|
|||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
<script src="../../static/assets/js/shared/upload.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("root_files_button").addEventListener("click", function (event) {
|
document.getElementById("root_files_button").addEventListener("click", function (event) {
|
||||||
if (document.forms["zip"]["server_path"].value != "") {
|
if (document.forms["zip"]["server_path"].value != "") {
|
||||||
@ -842,55 +843,7 @@
|
|||||||
});
|
});
|
||||||
var upload = false;
|
var upload = false;
|
||||||
var file;
|
var file;
|
||||||
function sendFile() {
|
|
||||||
file = $("#file")[0].files[0]
|
|
||||||
document.getElementById("upload_input").innerHTML = '<div class="progress" style="width: 100%;"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <i class="fa-solid fa-spinner"></i></div></div>'
|
|
||||||
let xmlHttpRequest = new XMLHttpRequest();
|
|
||||||
let token = getCookie("_xsrf")
|
|
||||||
let fileName = file.name
|
|
||||||
let target = '/upload'
|
|
||||||
let mimeType = file.type
|
|
||||||
let size = file.size
|
|
||||||
let type = 'server_import'
|
|
||||||
|
|
||||||
xmlHttpRequest.upload.addEventListener('progress', function (e) {
|
|
||||||
|
|
||||||
if (e.loaded <= size) {
|
|
||||||
var percent = Math.round(e.loaded / size * 100);
|
|
||||||
$(`#upload-progress-bar`).css('width', percent + '%');
|
|
||||||
$(`#upload-progress-bar`).html(percent + '%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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-Content-Upload-Type', type);
|
|
||||||
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
|
|
||||||
xmlHttpRequest.addEventListener('load', (event) => {
|
|
||||||
if (event.target.responseText == 'success') {
|
|
||||||
console.log('Upload for file', file.name, 'was successful!')
|
|
||||||
$("#upload_input").html(`<div class="card-header header-sm d-flex justify-content-between align-items-center" style="width: 100%;"><input value="${fileName}" type="text" id="file-uploaded" disabled></input> 🔒</div>`);
|
|
||||||
document.getElementById("lower_half").style.visibility = "visible";
|
|
||||||
document.getElementById("lower_half").hidden = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(JSON.parse(event.target.responseText).info)
|
|
||||||
bootbox.alert({
|
|
||||||
message: JSON.parse(event.target.responseText).info,
|
|
||||||
callback: function () {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.addEventListener('error', (e) => {
|
|
||||||
console.error('Error while uploading file', file.name + '.', 'Event:', e)
|
|
||||||
}, false);
|
|
||||||
xmlHttpRequest.send(file);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="../../static/assets/js/shared/root-dir.js"></script>
|
<script type="text/javascript" src="../../static/assets/js/shared/root-dir.js"></script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user