mirror of
https://github.com/akuker/RASCSI.git
synced 2024-05-29 01:41:27 +00:00
Ability to download and upload config files (#1083)
- Rename `/config/load` endpoint to `/config/action` since it has multiple functions - Add a `send` function to above endpoint, which triggers a download of the config file, and use it with a new Download button on the index page - Add an option to upload to the CFG_DIR - Improve layout of the file transfer destination web form: radio buttons before labels, and better padding between options - Add a test for config downloading
This commit is contained in:
parent
b6941c9e81
commit
956195d67e
|
@ -49,6 +49,10 @@ a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
margin: 0 0.1rem 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
Tables
|
Tables
|
||||||
|
@ -145,10 +149,6 @@ select {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone label {
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone p,
|
.dropzone p,
|
||||||
.dropzone .dz-default {
|
.dropzone .dz-default {
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<form action="/config/load" method="post" id="config-actions">
|
<form action="/config/action" method="post" id="config-actions">
|
||||||
<label for="config_load_name">{{ _("File Name:") }}</label>
|
<label for="config_load_name">{{ _("File Name:") }}</label>
|
||||||
<select name="name" id="config_load_name" required="" width="14">
|
<select name="name" id="config_load_name" required="" width="14">
|
||||||
{% if config_files %}
|
{% if config_files %}
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
</select>
|
</select>
|
||||||
<input name="load" type="submit" value="{{ _("Load") }}" onclick="return confirm('{{ _("Detach all current device and Load configuration?") }}')">
|
<input name="load" type="submit" value="{{ _("Load") }}" onclick="return confirm('{{ _("Detach all current device and Load configuration?") }}')">
|
||||||
<input name="delete" type="submit" value="{{ _("Delete") }}" onclick="return confirm('{{ _("Delete configuration file?") }}')">
|
<input name="delete" type="submit" value="{{ _("Delete") }}" onclick="return confirm('{{ _("Delete configuration file?") }}')">
|
||||||
|
<input name="send" type="submit" value="{{ _("Download") }}">
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -368,10 +369,10 @@
|
||||||
<form action="/files/download_url" method="post">
|
<form action="/files/download_url" method="post">
|
||||||
<label for="download_url">{{ _("Download file from URL:") }}</label>
|
<label for="download_url">{{ _("Download file from URL:") }}</label>
|
||||||
<input name="url" id="download_url" required="" type="url">
|
<input name="url" id="download_url" required="" type="url">
|
||||||
<label for="disk_images">{{ _("Disk Images") }}</label>
|
|
||||||
<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="shared_files">{{ _("Shared Files") }}</label>
|
<label for="disk_images">{{ _("Disk Images") }}</label>
|
||||||
<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>
|
||||||
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File...") }}')">
|
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File...") }}')">
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -7,14 +7,17 @@
|
||||||
<li>{{ _("You have to manually clean up partially uploaded files, as a result of cancelling the upload or closing this page.") }}</li>
|
<li>{{ _("You have to manually clean up partially uploaded files, as a result of cancelling the upload or closing this page.") }}</li>
|
||||||
<li>{{ _("Disk Images") }} = {{ env["image_dir"] }}</li>
|
<li>{{ _("Disk Images") }} = {{ env["image_dir"] }}</li>
|
||||||
<li>{{ _("Shared Files") }} = {{ FILE_SERVER_DIR }}</li>
|
<li>{{ _("Shared Files") }} = {{ FILE_SERVER_DIR }}</li>
|
||||||
|
<li>{{ _("PiSCSI Config") }} = {{ CFG_DIR }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>{{ _("Destination") }}</h3>
|
<h3>{{ _("Destination") }}</h3>
|
||||||
<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">
|
||||||
<label for="disk_images">{{ _("Disk Images") }}</label>
|
|
||||||
<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="shared_files">{{ _("Shared Files") }}</label>
|
<label for="disk_images">{{ _("Disk Images") }}</label>
|
||||||
<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>
|
||||||
|
<input type="radio" name="destination" id="piscsi_config" value="piscsi_config">
|
||||||
|
<label for="piscsi_config">{{ _("PiSCSI Config") }}</label>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
|
|
@ -322,6 +322,7 @@ def upload_page():
|
||||||
template="upload.html",
|
template="upload.html",
|
||||||
page_title=_("PiSCSI File Upload"),
|
page_title=_("PiSCSI File Upload"),
|
||||||
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
|
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
|
||||||
|
CFG_DIR=CFG_DIR,
|
||||||
FILE_SERVER_DIR=FILE_SERVER_DIR,
|
FILE_SERVER_DIR=FILE_SERVER_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -467,13 +468,16 @@ def config_save():
|
||||||
return response(error=True, message=process["msg"])
|
return response(error=True, message=process["msg"])
|
||||||
|
|
||||||
|
|
||||||
@APP.route("/config/load", methods=["POST"])
|
@APP.route("/config/action", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def config_load():
|
def config_action():
|
||||||
"""
|
"""
|
||||||
Loads a config file from disk
|
Carries out on an operation on the config file
|
||||||
"""
|
"""
|
||||||
file_name = Path(request.form.get("name")).name
|
file_name = Path(request.form.get("name"))
|
||||||
|
safe_path = is_safe_path(file_name)
|
||||||
|
if not safe_path["status"]:
|
||||||
|
return response(error=True, message=safe_path["msg"])
|
||||||
|
|
||||||
if "load" in request.form:
|
if "load" in request.form:
|
||||||
process = file_cmd.read_config(file_name)
|
process = file_cmd.read_config(file_name)
|
||||||
|
@ -484,15 +488,20 @@ def config_load():
|
||||||
return response(error=True, message=process["msg"])
|
return response(error=True, message=process["msg"])
|
||||||
|
|
||||||
if "delete" in request.form:
|
if "delete" in request.form:
|
||||||
file_path = Path(CFG_DIR) / file_name
|
process = file_cmd.delete_file(Path(CFG_DIR) / file_name)
|
||||||
process = file_cmd.delete_file(file_path)
|
|
||||||
process = ReturnCodeMapper.add_msg(process)
|
process = ReturnCodeMapper.add_msg(process)
|
||||||
if process["status"]:
|
if process["status"]:
|
||||||
return response(message=process["msg"])
|
return response(message=process["msg"])
|
||||||
|
|
||||||
return response(error=True, message=process["msg"])
|
return response(error=True, message=process["msg"])
|
||||||
|
|
||||||
return response(error=True, message="Action field (load, delete) missing")
|
if "send" in request.form:
|
||||||
|
return send_from_directory(CFG_DIR, str(file_name), as_attachment=True)
|
||||||
|
|
||||||
|
return response(
|
||||||
|
error=True,
|
||||||
|
message="No known operation in request header. Expected one of: load, delete, send",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@APP.route("/files/diskinfo", methods=["POST"])
|
@APP.route("/files/diskinfo", methods=["POST"])
|
||||||
|
@ -986,6 +995,8 @@ def upload_file():
|
||||||
destination_dir = server_info["image_dir"]
|
destination_dir = server_info["image_dir"]
|
||||||
elif destination == "shared_files":
|
elif destination == "shared_files":
|
||||||
destination_dir = FILE_SERVER_DIR
|
destination_dir = FILE_SERVER_DIR
|
||||||
|
elif destination == "piscsi_config":
|
||||||
|
destination_dir = CFG_DIR
|
||||||
else:
|
else:
|
||||||
return make_response(f"Invalid destination '{destination}'", 403)
|
return make_response(f"Invalid destination '{destination}'", 403)
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ def test_show_logs(http_client):
|
||||||
|
|
||||||
|
|
||||||
# route("/config/save", methods=["POST"])
|
# route("/config/save", methods=["POST"])
|
||||||
# route("/config/load", methods=["POST"])
|
# route("/config/action", methods=["POST"])
|
||||||
def test_save_load_and_delete_configs(env, http_client):
|
def test_save_load_and_delete_configs(env, http_client):
|
||||||
config_name = str(uuid.uuid4())
|
config_name = str(uuid.uuid4())
|
||||||
config_json_file = f"{config_name}.json"
|
config_json_file = f"{config_name}.json"
|
||||||
|
@ -115,7 +115,7 @@ def test_save_load_and_delete_configs(env, http_client):
|
||||||
|
|
||||||
# Load the saved config
|
# Load the saved config
|
||||||
response = http_client.post(
|
response = http_client.post(
|
||||||
"/config/load",
|
"/config/action",
|
||||||
data={
|
data={
|
||||||
"name": config_json_file,
|
"name": config_json_file,
|
||||||
"load": True,
|
"load": True,
|
||||||
|
@ -135,7 +135,7 @@ def test_save_load_and_delete_configs(env, http_client):
|
||||||
|
|
||||||
# Delete the saved config
|
# Delete the saved config
|
||||||
response = http_client.post(
|
response = http_client.post(
|
||||||
"/config/load",
|
"/config/action",
|
||||||
data={
|
data={
|
||||||
"name": config_json_file,
|
"name": config_json_file,
|
||||||
"delete": True,
|
"delete": True,
|
||||||
|
@ -153,6 +153,49 @@ def test_save_load_and_delete_configs(env, http_client):
|
||||||
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
|
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
|
||||||
|
|
||||||
|
|
||||||
|
# route("/config/save", methods=["POST"])
|
||||||
|
# route("/config/action", methods=["POST"])
|
||||||
|
def test_download_configs(env, http_client, delete_file):
|
||||||
|
config_name = str(uuid.uuid4())
|
||||||
|
config_json_file = f"{config_name}.json"
|
||||||
|
|
||||||
|
# Save the initial state to a config
|
||||||
|
response = http_client.post(
|
||||||
|
"/config/save",
|
||||||
|
data={
|
||||||
|
"name": config_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert config_json_file in http_client.get("/").json()["data"]["config_files"]
|
||||||
|
|
||||||
|
# Download the saved config
|
||||||
|
response = http_client.post(
|
||||||
|
"/config/action",
|
||||||
|
data={
|
||||||
|
"name": config_json_file,
|
||||||
|
"send": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["content-type"] == "application/json"
|
||||||
|
assert response.headers["content-disposition"] == f"attachment; filename={config_json_file}"
|
||||||
|
|
||||||
|
# Delete the saved config
|
||||||
|
response = http_client.post(
|
||||||
|
"/config/action",
|
||||||
|
data={
|
||||||
|
"name": config_json_file,
|
||||||
|
"delete": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
|
||||||
|
|
||||||
|
|
||||||
# route("/theme", methods=["POST"])
|
# route("/theme", methods=["POST"])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"theme",
|
"theme",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user