mirror of
https://github.com/akuker/RASCSI.git
synced 2024-06-12 00:29:29 +00:00
Formatted image file data structure that breaks down by subdir (#1102)
- New utility method for the web app, which sorts image files into dicts where the subdir is the key - In the web ui, display each subdir in a table nested in a details tag. - Allow for picking destination subdir when uploading files - Style the expandable details blocks in the images table - Add a check for ~ paths to the is_safe_path() utility method
This commit is contained in:
parent
983cff735b
commit
dd00547f92
|
@ -104,6 +104,15 @@ ul.inline_list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary.dirname {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.filename {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.dropzone, .dropzone * {
|
.dropzone, .dropzone * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ input[type="radio"] {
|
||||||
margin: 0 0.1rem 0 0.75rem;
|
margin: 0 0.1rem 0 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.noscriptmsg {
|
div.notice {
|
||||||
background: var(--danger);
|
background: var(--danger);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@ -717,6 +717,16 @@ section#files p {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section#files details.subdir summary.dirname {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-family: monospace;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#files details.contents summary.filename {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
section#files table#images tr th:nth-child(2),
|
section#files table#images tr th:nth-child(2),
|
||||||
section#files table#images tr td:nth-child(2) {
|
section#files table#images tr td:nth-child(2) {
|
||||||
|
|
|
@ -209,6 +209,19 @@
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
{% if not files|length: %}
|
||||||
|
<div class="notice">
|
||||||
|
{{ _("The images directory is currently empty.") }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% for subdir, group in formatted_image_files.items() %}
|
||||||
|
|
||||||
|
<details class="subdir"{% if subdir == "images/" %} open{% endif %}>
|
||||||
|
<summary class="dirname">
|
||||||
|
{{ subdir }}
|
||||||
|
</summary>
|
||||||
<table id="images" border="black" cellpadding="3" summary="List of files in the image directory">
|
<table id="images" border="black" cellpadding="3" summary="List of files in the image directory">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -216,19 +229,12 @@
|
||||||
<th scope="col">{{ _("Size") }}</th>
|
<th scope="col">{{ _("Size") }}</th>
|
||||||
<th scope="col">{{ _("Actions") }}</th>
|
<th scope="col">{{ _("Actions") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% if not files|length: %}
|
{% for file in group|sort(attribute='name') %}
|
||||||
<tr class="directory-empty">
|
|
||||||
<td colspan="3">
|
|
||||||
{{ _("The images directory is currently empty.") }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% for file in files|sort(attribute='name') %}
|
|
||||||
<tr>
|
<tr>
|
||||||
{% if file["prop"] %}
|
{% if file["prop"] %}
|
||||||
<td>
|
<td>
|
||||||
<details>
|
<details class="contents">
|
||||||
<summary>
|
<summary class="filename">
|
||||||
{{ file["name"] }}
|
{{ file["name"] }}
|
||||||
</summary>
|
</summary>
|
||||||
<ul class="inline_list">
|
<ul class="inline_list">
|
||||||
|
@ -244,8 +250,8 @@
|
||||||
</td>
|
</td>
|
||||||
{% elif file["archive_contents"] %}
|
{% elif file["archive_contents"] %}
|
||||||
<td>
|
<td>
|
||||||
<details>
|
<details class="contents">
|
||||||
<summary>
|
<summary class="filename">
|
||||||
{{ file["name"] }}
|
{{ file["name"] }}
|
||||||
</summary>
|
</summary>
|
||||||
<ul class="inline_list">
|
<ul class="inline_list">
|
||||||
|
@ -253,8 +259,8 @@
|
||||||
{% if not member["is_properties_file"] %}
|
{% if not member["is_properties_file"] %}
|
||||||
<li>
|
<li>
|
||||||
{% if member["related_properties_file"] %}
|
{% if member["related_properties_file"] %}
|
||||||
<details>
|
<details id="contents">
|
||||||
<summary>
|
<summary class="filename">
|
||||||
<label>{{ member["path"] }}</label>
|
<label>{{ member["path"] }}</label>
|
||||||
<form action="/files/extract_image" method="post" class="file-extract">
|
<form action="/files/extract_image" method="post" class="file-extract">
|
||||||
<input name="archive_file" type="hidden" value="{{ file['name'] }}">
|
<input name="archive_file" type="hidden" value="{{ file['name'] }}">
|
||||||
|
@ -361,6 +367,12 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% if subdir != "/" %}
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the system", disk_space=env["free_disk_space"]) }}</small></p>
|
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the system", disk_space=env["free_disk_space"]) }}</small></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -396,7 +408,7 @@
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
section#upload { display: none; }
|
section#upload { display: none; }
|
||||||
</style>
|
</style>
|
||||||
<div class="noscriptmsg">
|
<div class="notice">
|
||||||
{{ _("The file uploading functionality requires JavaScript.") }}
|
{{ _("The file uploading functionality requires JavaScript.") }}
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
|
@ -14,6 +14,14 @@
|
||||||
<form name="dropper" action="/files/upload" method="post" class="dropzone dz-clickable" enctype="multipart/form-data" id="dropper">
|
<form name="dropper" action="/files/upload" method="post" class="dropzone dz-clickable" enctype="multipart/form-data" id="dropper">
|
||||||
<input type="radio" name="destination" id="disk_images" value="disk_images" checked="checked">
|
<input type="radio" name="destination" id="disk_images" value="disk_images" checked="checked">
|
||||||
<label for="disk_images">{{ _("Disk Images") }}</label>
|
<label for="disk_images">{{ _("Disk Images") }}</label>
|
||||||
|
<select name="subdir" id="subdir">
|
||||||
|
<option value="">images/</option>
|
||||||
|
{% for subdir, group in formatted_image_files.items() %}
|
||||||
|
{% if subdir != "/" %}
|
||||||
|
<option value="{{subdir}}">images/{{subdir}}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
<input type="radio" name="destination" id="shared_files" value="shared_files">
|
<input type="radio" name="destination" id="shared_files" value="shared_files">
|
||||||
<label for="shared_files">{{ _("Shared Files") }}</label>
|
<label for="shared_files">{{ _("Shared Files") }}</label>
|
||||||
<input type="radio" name="destination" id="piscsi_config" value="piscsi_config">
|
<input type="radio" name="destination" id="piscsi_config" value="piscsi_config">
|
||||||
|
|
|
@ -49,6 +49,7 @@ from web_utils import (
|
||||||
map_device_types_and_names,
|
map_device_types_and_names,
|
||||||
get_device_name,
|
get_device_name,
|
||||||
map_image_file_descriptions,
|
map_image_file_descriptions,
|
||||||
|
format_image_list,
|
||||||
format_drive_properties,
|
format_drive_properties,
|
||||||
get_properties_by_drive_name,
|
get_properties_by_drive_name,
|
||||||
auth_active,
|
auth_active,
|
||||||
|
@ -223,12 +224,7 @@ def index():
|
||||||
image_files = file_cmd.list_images()
|
image_files = file_cmd.list_images()
|
||||||
config_files = file_cmd.list_config_files()
|
config_files = file_cmd.list_config_files()
|
||||||
ip_addr, host = sys_cmd.get_ip_and_host()
|
ip_addr, host = sys_cmd.get_ip_and_host()
|
||||||
|
formatted_image_files = format_image_list(image_files["files"], device_types)
|
||||||
extended_image_files = []
|
|
||||||
for image in image_files["files"]:
|
|
||||||
if image["detected_type"] != "UNDEFINED":
|
|
||||||
image["detected_type_name"] = device_types[image["detected_type"]]["name"]
|
|
||||||
extended_image_files.append(image)
|
|
||||||
|
|
||||||
attached_images = []
|
attached_images = []
|
||||||
units = 0
|
units = 0
|
||||||
|
@ -266,7 +262,8 @@ def index():
|
||||||
bridge_configured=sys_cmd.is_bridge_setup(),
|
bridge_configured=sys_cmd.is_bridge_setup(),
|
||||||
devices=formatted_devices,
|
devices=formatted_devices,
|
||||||
attached_images=attached_images,
|
attached_images=attached_images,
|
||||||
files=extended_image_files,
|
formatted_image_files=formatted_image_files,
|
||||||
|
files=image_files["files"],
|
||||||
config_files=config_files,
|
config_files=config_files,
|
||||||
device_types=device_types,
|
device_types=device_types,
|
||||||
scan_depth=server_info["scan_depth"],
|
scan_depth=server_info["scan_depth"],
|
||||||
|
@ -317,10 +314,13 @@ def upload_page():
|
||||||
"""
|
"""
|
||||||
Sets up the data structures and kicks off the rendering of the file uploading page
|
Sets up the data structures and kicks off the rendering of the file uploading page
|
||||||
"""
|
"""
|
||||||
|
image_files = file_cmd.list_images()
|
||||||
|
formatted_image_files = format_image_list(image_files["files"])
|
||||||
|
|
||||||
return response(
|
return response(
|
||||||
template="upload.html",
|
template="upload.html",
|
||||||
page_title=_("PiSCSI File Upload"),
|
page_title=_("PiSCSI File Upload"),
|
||||||
|
formatted_image_files=formatted_image_files,
|
||||||
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
|
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
|
||||||
CFG_DIR=CFG_DIR,
|
CFG_DIR=CFG_DIR,
|
||||||
FILE_SERVER_DIR=FILE_SERVER_DIR,
|
FILE_SERVER_DIR=FILE_SERVER_DIR,
|
||||||
|
@ -990,15 +990,19 @@ def upload_file():
|
||||||
return make_response(auth["msg"], 403)
|
return make_response(auth["msg"], 403)
|
||||||
|
|
||||||
destination = request.form.get("destination")
|
destination = request.form.get("destination")
|
||||||
|
subdir = request.form.get("subdir")
|
||||||
if destination == "disk_images":
|
if destination == "disk_images":
|
||||||
|
safe_path = is_safe_path(Path(subdir))
|
||||||
|
if not safe_path["status"]:
|
||||||
|
return make_response(safe_path["msg"], 403)
|
||||||
server_info = piscsi_cmd.get_server_info()
|
server_info = piscsi_cmd.get_server_info()
|
||||||
destination_dir = server_info["image_dir"]
|
destination_dir = Path(server_info["image_dir"]) / subdir
|
||||||
elif destination == "shared_files":
|
elif destination == "shared_files":
|
||||||
destination_dir = FILE_SERVER_DIR
|
destination_dir = FILE_SERVER_DIR
|
||||||
elif destination == "piscsi_config":
|
elif destination == "piscsi_config":
|
||||||
destination_dir = CFG_DIR
|
destination_dir = CFG_DIR
|
||||||
else:
|
else:
|
||||||
return make_response("Invalid destination", 403)
|
return make_response("Unknown destination", 403)
|
||||||
|
|
||||||
return upload_with_dropzonejs(destination_dir)
|
return upload_with_dropzonejs(destination_dir)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from grp import getgrall
|
||||||
from os import path
|
from os import path
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ua_parser import user_agent_parser
|
from ua_parser import user_agent_parser
|
||||||
|
from re import findall
|
||||||
|
|
||||||
from flask import request, make_response
|
from flask import request, make_response
|
||||||
from flask_babel import _
|
from flask_babel import _
|
||||||
|
@ -146,6 +147,33 @@ def get_image_description(file_suffix):
|
||||||
return file_suffix
|
return file_suffix
|
||||||
|
|
||||||
|
|
||||||
|
def format_image_list(image_files, device_types=None):
|
||||||
|
"""
|
||||||
|
Takes a (list) of (dict) image_files and optional (list) device_types
|
||||||
|
Returns a formatted (dict) with groups of image_files per subdir key
|
||||||
|
"""
|
||||||
|
|
||||||
|
root_image_files = []
|
||||||
|
subdir_image_files = {}
|
||||||
|
for image in image_files:
|
||||||
|
if (image["detected_type"] != "UNDEFINED") and device_types:
|
||||||
|
image["detected_type_name"] = device_types[image["detected_type"]]["name"]
|
||||||
|
subdir_path = findall("^.*/", image["name"])
|
||||||
|
if subdir_path:
|
||||||
|
subdir = subdir_path[0]
|
||||||
|
if subdir in subdir_image_files.keys():
|
||||||
|
subdir_image_files[f"images/{subdir}"].append(image)
|
||||||
|
else:
|
||||||
|
subdir_image_files[f"images/{subdir}"] = [image]
|
||||||
|
else:
|
||||||
|
root_image_files.append(image)
|
||||||
|
|
||||||
|
formatted_image_files = dict(sorted(subdir_image_files.items()))
|
||||||
|
if root_image_files:
|
||||||
|
formatted_image_files["images/"] = root_image_files
|
||||||
|
return formatted_image_files
|
||||||
|
|
||||||
|
|
||||||
def format_drive_properties(drive_properties):
|
def format_drive_properties(drive_properties):
|
||||||
"""
|
"""
|
||||||
Takes a (dict) with structured drive properties data
|
Takes a (dict) with structured drive properties data
|
||||||
|
@ -256,10 +284,10 @@ def is_safe_path(file_name):
|
||||||
Returns True if the path is safe
|
Returns True if the path is safe
|
||||||
Returns False if the path is either absolute, or tries to traverse the file system
|
Returns False if the path is either absolute, or tries to traverse the file system
|
||||||
"""
|
"""
|
||||||
if file_name.is_absolute() or ".." in str(file_name):
|
if file_name.is_absolute() or ".." in str(file_name) or str(file_name)[0] == "~":
|
||||||
return {
|
return {
|
||||||
"status": False,
|
"status": False,
|
||||||
"msg": _("%(file_name)s is not a valid path", file_name=file_name),
|
"msg": _("No permission to use path '%(file_name)s'", file_name=file_name),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {"status": True, "msg": ""}
|
return {"status": True, "msg": ""}
|
||||||
|
|
|
@ -254,6 +254,7 @@ def test_upload_file(http_client, delete_file):
|
||||||
|
|
||||||
form_data = {
|
form_data = {
|
||||||
"destination": "disk_images",
|
"destination": "disk_images",
|
||||||
|
"subdir": "",
|
||||||
"dzuuid": str(uuid.uuid4()),
|
"dzuuid": str(uuid.uuid4()),
|
||||||
"dzchunkindex": chunk_number,
|
"dzchunkindex": chunk_number,
|
||||||
"dzchunksize": chunk_size,
|
"dzchunksize": chunk_size,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user