Introduce info.html template and use it to render detailed info (#863)

new:
- new templates to render structured info contents in
- get_diskinfo() class method that calls disktype and returns the results
- /diskinfo endpoint in the Flask app that renders the results from get_diskinfo()

changed:
- /logs/show and /scsi/info endpoints in the Flask app render in templates
- Now using the "RaSCSI Reloaded Control Page" header to function as the link back to the homepage (instead of the github project) which is in line with how most webapps work
- Removed the center style for "Attached!" to allow the ? button to be placed on the same line
- Remove individual device info, and introduced show all device info in a template
This commit is contained in:
Daniel Markstedt 2022-09-26 17:44:41 -07:00 committed by GitHub
parent edbaaf645d
commit 5da3d6c24b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 188 additions and 86 deletions

View File

@ -161,7 +161,21 @@ class SysCmds:
process = run(
["journalctl"] + line_param + scope_param,
capture_output=True,
check=True,
)
if process.returncode == 0:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")
@staticmethod
def get_diskinfo(file_path):
"""
Takes (str) file_path path to image file to inspect.
Returns either the disktype output, or the stderr output.
"""
process = run(
["disktype", file_path],
capture_output=True,
)
if process.returncode == 0:
return process.returncode, process.stdout.decode("utf-8")

View File

@ -63,7 +63,7 @@
<tbody>
<tr align="center">
<td>
<a href="http://github.com/akuker/RASCSI" target="_blank">
<a href="/">
<h1>{{ _("RaSCSI Reloaded Control Page") }}</h1>
</a>
</td>

View File

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("Detailed Info for Attached Devices") }}</h3>
{% for device in devices %}
<p>
<table border="black" cellpadding="3">
<tr>
<td><i>{{ _("SCSI ID") }}</i></td>
<td>{{ device["id"] }}</td>
</tr>
<tr>
<td><i>{{ _("LUN") }}</i></td>
<td>{{ device["unit"] }}</td>
</tr>
<tr>
<td><i>{{ _("Type") }}</i></td>
<td>{{ device["device_type"] }}</td>
</tr>
<tr>
<td><i>{{ _("Status") }}</i></td>
<td>{{ device["status"] }}</td>
</tr>
<tr>
<td><i>{{ _("File") }}</i></td>
<td>{{ device["image"] }}</td>
</tr>
<tr>
<td><i>{{ _("Parameters") }}</i></td>
<td>{{ device["params"] }}</td>
</tr>
<tr>
<td><i>{{ _("Vendor") }}</i></td>
<td>{{ device["vendor"] }}</td>
</tr>
<tr>
<td><i>{{ _("Product") }}</i></td>
<td>{{ device["product"] }}</td>
</tr>
<tr>
<td><i>{{ _("Revision") }}</i></td>
<td>{{ device["revision"] }}</td>
</tr>
<tr>
<td><i>{{ _("Block Size") }}</i></td>
<td>{{ device["block_size"] }}</td>
</tr>
<tr>
<td><i>{{ _("Image Size") }}</i></td>
<td>{{ device["size"] }}</td>
</tr>
</table>
</p>
{% endfor %}
<p><a href="/">{{ _("Go to Home") }}</a></p>
{% endblock content %}

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("Disk Image Details: %(file_name)s", file_name=file_name) }}</h3>
<p><pre>{{ diskinfo }}</pre></p>
<p><a href="/">{{ _("Go to Home") }}</a></p>
{% endblock content %}

View File

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block content %}
<p><a href="/">{{ _("Cancel") }}</a></p>
<h2>{{ _("Disclaimer") }}</h2>
<p>{{ _("These device profiles are provided as-is with no guarantee to work equally to the actual physical device they are named after. You may need to provide appropirate device drivers and/or configuration parameters for them to function properly. If you would like to see data modified, or have additional devices to add to the list, please raise an issue ticket at <a href=\"%(url)s\">GitHub</a>.", url="https://github.com/akuker/RASCSI/issues") }}</p>
<h2>{{ _("Hard Drives") }}</h2>
@ -135,7 +134,7 @@
{% endfor %}
</tbody>
</table>
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}</small></p>
<p><a href="/">{{ _("Cancel") }}</a></p>
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}</small></p>
<p><a href="/">{{ _("Go to Home") }}</a></p>
{% endblock content %}

View File

@ -106,11 +106,6 @@
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Detach") }}">
</form>
<form action="/scsi/info" method="post">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Info") }}">
</form>
{% else %}
<form action="/scsi/reserve" method="post" onsubmit="var memo = prompt('{{ _("Enter a memo for this reservation") }}'); if (memo === null) event.preventDefault(); document.getElementById('memo_{{ device.id }}').value = memo;">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
@ -140,9 +135,20 @@
</tbody>
</table>
<p><form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
<input type="submit" value="{{ _("Detach All Devices") }}">
</form></p>
<table style="border: none;" cellpadding="3">
<tr style="border: none;">
<td style="border: none;">
<form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
<input type="submit" value="{{ _("Detach All Devices") }}">
</form>
</td>
<td style="border: none;">
<form action="/scsi/info" method="post">
<input type="submit" value="{{ _("Show Device Info") }}">
</form>
</td>
</tr>
</table>
<hr/>
@ -243,9 +249,7 @@
</td>
<td>
{% if file["name"] in attached_images %}
<center>
{{ _("Attached!") }}
</center>
{{ _("In use") }}
{% else %}
{% if file["archive_contents"] %}
<form action="/files/extract_image" method="post">
@ -303,6 +307,12 @@
<input type="submit" value="{{ _("Delete") }}">
</form>
{% endif %}
{% if not file["archive_contents"] %}
<form action="/files/diskinfo" method="post">
<input name="file_name" type="hidden" value="{{ file['name'] }}">
<input type="submit" value="{{ _("?") }}">
</form>
{% endif %}
</td>
</tr>
{% endfor %}
@ -393,8 +403,8 @@
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<table style="border: none;">
<tr style="border: none;">
<td style="border: none; vertical-align:top;">
<form name="dropper" action="/files/upload" method="post" class="dropzone dz-clickable" enctype="multipart/form-data" id="dropper"></form>
</td>

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("System Logs: %(scope)s %(lines)s lines", scope=scope, lines=lines) }}</h3>
<p><pre>{{ logs }}</pre></p>
<p><a href="/">{{ _("Go to Home") }}</a></p>
{% endblock content %}

View File

@ -217,11 +217,6 @@ def index():
server_info["sccd"]
)
if "username" in session:
username = session["username"]
else:
username = None
return response(
template="index.html",
locales=get_supported_locales(),
@ -310,7 +305,7 @@ def drive_list():
server_info = ractl_cmd.get_server_info()
return response(
"drives.html",
template="drives.html",
files=file_cmd.list_images()["files"],
base_dir=server_info["image_dir"],
hd_conf=hd_conf,
@ -479,6 +474,33 @@ def config_load():
return response(error=True, message="Action field (load, delete) missing")
@APP.route("/files/diskinfo", methods=["POST"])
def show_diskinfo():
"""
Displays disk image info
"""
file_name = request.form.get("file_name")
server_info = ractl_cmd.get_server_info()
returncode, diskinfo = sys_cmd.get_diskinfo(
server_info["image_dir"] +
"/" +
file_name
)
if returncode == 0:
return response(
template="diskinfo.html",
file_name=file_name,
diskinfo=diskinfo,
version=server_info["version"],
)
return response(
error=True,
message=_("An error occurred when getting disk info: %(error)s", error=diskinfo)
)
@APP.route("/logs/show", methods=["POST"])
def show_logs():
"""
@ -487,16 +509,21 @@ def show_logs():
lines = request.form.get("lines")
scope = request.form.get("scope")
# TODO: Render logs in a template (issue #836) and structured JSON
returncode, logs = sys_cmd.get_logs(lines, scope)
if returncode == 0:
headers = {"content-type": "text/plain"}
return logs, headers
server_info = ractl_cmd.get_server_info()
return response(
template="logs.html",
scope=scope,
lines=lines,
logs=logs,
version=server_info["version"],
)
return response(error=True, message=[
(_("An error occurred when fetching logs."), "error"),
(logs, "stderr"),
])
return response(
error=True,
message=_("An error occurred when fetching logs: %(error)s", error=logs)
)
@APP.route("/logs/level", methods=["POST"])
@ -684,56 +711,18 @@ def eject():
@APP.route("/scsi/info", methods=["POST"])
def device_info():
"""
Displays detailed info for a specific device
Displays detailed info for all attached devices
"""
scsi_id = request.form.get("scsi_id")
unit = request.form.get("unit")
server_info = ractl_cmd.get_server_info()
process = ractl_cmd.list_devices()
if process["status"]:
return response(
template="deviceinfo.html",
devices=process["device_list"],
version=server_info["version"],
)
devices = ractl_cmd.list_devices(scsi_id, unit)
# First check if any device at all was returned
if not devices["status"]:
return response(error=True, message=devices["msg"])
# Looking at the first dict in list to get
# the one and only device that should have been returned
device = devices["device_list"][0]
if str(device["id"]) == scsi_id:
# TODO: Move the device info to the template instead of a flash message
message = "\n".join([
_("DEVICE INFO"),
"===========",
_("SCSI ID: %(id_number)s", id_number=device["id"]),
_("LUN: %(unit_number)s", unit_number=device["unit"]),
_("Type: %(device_type)s", device_type=device["device_type"]),
_("Status: %(device_status)s", device_status=device["status"]),
_("File: %(image_file)s", image_file=device["image"]),
_("Parameters: %(value)s", value=device["params"]),
_("Vendor: %(value)s", value=device["vendor"]),
_("Product: %(value)s", value=device["product"]),
_("Revision: %(revision_number)s", revision_number=device["revision"]),
_("Block Size: %(value)s bytes", value=device["block_size"]),
_("Image Size: %(value)s bytes", value=device["size"]),
])
# Don't send redundant "info" message with the JSON response
if request.headers.get("accept") == "application/json":
return response(device_info={
"scsi_id": device["id"],
"lun": device["unit"],
"device_type": device["device_type"],
"status": device["status"],
"file": device["image"],
"parameters": device["params"],
"vendor": device["vendor"],
"product": device["product"],
"revision": device["revision"],
"block_size": device["block_size"],
"size": device["size"],
})
return response(message=[(message, "info")])
return response(error=True, message=devices["msg"])
return response(error=True, message=_("No devices attached"))
@APP.route("/scsi/reserve", methods=["POST"])

View File

@ -176,17 +176,14 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
response = http_client.post(
"/scsi/info",
data={
"scsi_id": SCSI_ID,
},
)
response_data = response.json()
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert "device_info" in response_data["data"]
assert response_data["data"]["device_info"]["file"] == f"{IMAGES_DIR}/{test_image}"
assert "devices" in response_data["data"]
assert response_data["data"]["devices"][0]["file"] == test_image
# Cleanup
detach_devices()

View File

@ -315,3 +315,20 @@ def test_download_url_to_iso(
# Cleanup
detach_devices()
delete_file(iso_file_name)
# route("/files/diskinfo", methods=["POST"])
def test_show_diskinfo(http_client, create_test_image):
test_image = create_test_image()
response = http_client.post(
"/files/diskinfo",
data={
"file_name": test_image,
},
)
response_data = response.json()
assert response.status_code == 200
assert "Regular file" in response_data["data"]["diskinfo"]

View File

@ -61,12 +61,15 @@ def test_show_logs(http_client):
"/logs/show",
data={
"lines": 100,
"scope": "",
"scope": "rascsi",
},
)
response_data = response.json()
assert response.status_code == 200
assert response.headers["content-type"] == "text/plain"
assert response_data["data"]["lines"] == "100"
assert response_data["data"]["scope"] == "rascsi"
# route("/config/save", methods=["POST"])