Web UI: Rework the Attach Device section to be universal (#1393)

* Correct German translation for Key

* Web UI: Rework the Attach Device section to be universal

* Web UI: Warn when working dirs are missing

* Refactor tests to use global endpoint constants

* Add fallback for unknown disk type devices

* Rearrange the index page sections

* Move Macproxy help text to admins page

* Remove image list exception for SCHD

* Show Settings button when auth is diabled

* Tweak CSS styles for both themes

* Move Eject action next to the file name, and improve UI labels
This commit is contained in:
Daniel Markstedt 2023-12-07 17:38:24 -08:00 committed by GitHub
parent 0f352396be
commit 05b9e0eb18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 455 additions and 428 deletions

View File

@ -58,6 +58,15 @@ div.footer div.theme-change-hint {
margin-bottom: 15px;
}
div.login-status {
text-align: right;
}
div.login-status a {
color: white;
text-decoration: underline;
}
div.logged-in {
background-color: green;
}

View File

@ -282,18 +282,6 @@ div.header div.login-form-title {
display: none;
}
div.header div.authentication-disabled span.separator {
display: none;
}
div.header div.authentication-disabled span.wiki-help-text {
display: block;
}
div.header div.authentication-disabled a {
color: #fff;
}
@media (max-width: 900px) {
div.header {
flex-wrap: wrap;
@ -663,10 +651,11 @@ table#attached-devices td.actions {
table#attached-devices td.parameters form {
display: flex;
align-items: center;
}
table#attached-devices td.parameters form label {
display: none;
padding: 0 0.5rem 0 0;
}
table#attached-devices td.parameters form select {
@ -775,6 +764,11 @@ section#files p {
margin-top: 1rem;
}
section#files details.subdir {
padding-left: 1rem;
padding-right: 1rem;
}
section#files details.subdir summary.dirname {
text-decoration: underline;
font-family: monospace;
@ -864,7 +858,7 @@ section#upload a p {
/*
------------------------------------------------------------------------------
Index > Section: Attach peripheral devices
Index > Section: Attach devices
------------------------------------------------------------------------------
*/
section#attach-devices table th:last-child,
@ -876,6 +870,10 @@ section#attach-devices form {
display: block;
}
section#attach-devices table form select.table-dropdown {
width: 16rem;
}
@media (max-width: 900px) {
section#attach-devices table tr th:nth-child(2),
section#attach-devices table tr td:nth-child(2) {

View File

@ -115,6 +115,8 @@
<ul>
<li>{{ _("If you want to add a service, run the easyinstall.sh script and choose the one to install.") }}</li>
<li>{{ _("In order to manage the services in the Web UI, you may install Webmin as well.") }}</li>
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\" target=\"_blank\">Macproxy</a>.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}
</li>
</ul>
</details>
<ul class="service_status">

View File

@ -27,37 +27,31 @@
<body class="{{ body_classes|join(' ') }}">
<div class="header">
{% if env["auth_active"] %}
{% if env["logged_in"] or not env["auth_active"] %}
<div align="center" class="login-status logged-in">
{% if env["logged_in"] %}
<div align="center" class="login-status logged-in">
<span class="logged-in-as-text">{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }}</span>
<span class="separator">-</span>
<span class="log-out-button"><a href="/logout">{{ _("Log Out") }}</a></span>
<span class="separator">-</span>
<span class="admin-button"><a href="/sys/admin">{{ _("Settings") }}</a></span>
</div>
{% else %}
<div align="center" class="login-status logged-out">
<form method="POST" action="/login">
<div class="login-form-title">{{ _("Log in to use Web Interface") }}</div>
<span>
<label for="username">{{ _("Username:") }}</label>
<input type="text" name="username" id="username">
</span>
<span>
<label for="password">{{ _("Password:") }}</label>
<input type="password" name="password" id="password">
</span>
<input type="submit" value="Login">
</form>
</div>
<span class="logged-in-as-text">{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }}</span>
<span class="separator">-</span>
<span class="log-out-button"><a href="/logout">{{ _("Log Out") }}</a></span>
<span class="separator">-</span>
{% endif %}
<span class="admin-button"><a href="/sys/admin">{{ _("Settings") }}</a></span>
</div>
{% else %}
<div align="center" class="login-status authentication-disabled">
<span class="authentication-disabled-text">{{ _("Web Interface Authentication Disabled") }}</span>
<span class="separator">-</span>
<span class="wiki-help-text">{{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/PiSCSI/piscsi/wiki/Web-Interface#enable-authentication") }}</span>
</div>
<div align="center" class="login-status logged-out">
<form method="POST" action="/login">
<div class="login-form-title">{{ _("Log in to use Web Interface") }}</div>
<span>
<label for="username">{{ _("Username:") }}</label>
<input type="text" name="username" id="username">
</span>
<span>
<label for="password">{{ _("Password:") }}</label>
<input type="password" name="password" id="password">
</span>
<input type="submit" value="Login">
</form>
</div>
{% endif %}
<div align="center" class="title">

View File

@ -24,6 +24,7 @@
</ul>
</details>
{% if env["cfg_dir_exists"] %}
<p>
<form action="/config/action" method="post" id="config-actions">
<label for="config_load_name">{{ _("File Name:") }}</label>
@ -54,6 +55,13 @@
<input type="submit" value="{{ _("Save") }}">
</form>
</p>
{% else %}
<div class="notice">
{{ _("Please create the PiSCSI configuration dir to use configurations:")}} {{ CFG_DIR }}
</div>
{% endif %}
<table id="attached-devices" border="black" cellpadding="3" summary="List of attached devices">
<tbody>
@ -82,13 +90,12 @@
{% endif %}
<td class="name" align="center">{{ device.device_name }}</td>
<td class="parameters">
{% if "No Media" in device.status %}
{% if "No Media" in device.status %}
<form action="/scsi/attach" method="post">
<label for="device_list_file_name_{{ device.id }}_{{ device.unit }}">{{ _("File:") }}</label>
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input name="type" type="hidden" value="{{ device.device_type }}">
<input name="file_size" type="hidden" value="{{ device.size }}">
<label for="device_list_file_name_{{ device.id }}_{{ device.unit }}">{{ _("File name") }}</label>
<select type="select" name="file_name" id="device_list_file_name_{{ device.id }}_{{ device.unit }}">
{% for f in files|sort(attribute='name') %}
{% if device.device_type == "SCCD" %}
@ -106,7 +113,7 @@
{% endif %}
{% endfor %}
</select>
<input type="submit" value="{{ _("Attach") }}">
<input type="submit" value="{{ _("Insert") }}">
</form>
{% else %}
{% if device.params %}
@ -120,7 +127,14 @@
{% endif %}
{% endfor %}
{% elif device.file %}
<span class="filename">{{ device.file }}</span>
<form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
<label>{{ device.file }}</label>
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Eject") }}">
{% endif %}
</form>
{% endif %}
{% endif %}
</td>
@ -135,13 +149,6 @@
</td>
<td class="actions" align="center">
{% if device.id in scsi_ids["occupied_ids"] %}
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
<form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Eject") }}">
</form>
{% endif %}
<form action="/scsi/detach" method="post" onsubmit="return confirm('{{ _("Detach Device?") }}')">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
@ -209,12 +216,8 @@
</ul>
</details>
{% if not files|length: %}
<div class="notice">
{{ _("The images directory is currently empty.") }}
</div>
{% else %}
{% if env["image_dir_exists"] %}
{% if files|length %}
<div>
{% for subdir, group in formatted_image_files.items() %}
@ -309,7 +312,6 @@
{% else %}
<form action="/scsi/attach" method="post" class="file-attach">
<input name="file_name" type="hidden" value="{{ file['name'] }}">
<input name="file_size" type="hidden" value="{{ file['size'] }}">
<label for="image_list_scsi_id_{{ file["name"] }}">{{ _("ID") }}</label>
<select name="scsi_id" id="image_list_scsi_id_{{ file["name"] }}">
{% for id in scsi_ids["valid_ids"] %}
@ -370,8 +372,170 @@
</details>
{% endfor %}
</div>
{% else %}
<div class="notice">
{{ _("The images directory is currently empty.") }}
</div>
{% endif %}
<p><small>{{ _("%(disk_space)s MiB disk space remaining for images", disk_space=env["free_disk_space"]) }}</small></p>
<p>
<small>{{ _("%(disk_space)s MiB disk space remaining for images", disk_space=env["free_disk_space"]) }}</small>
</p>
{% else %}
<div class="notice">
{{ _("Please create the PiSCSI images dir to work with disk images:")}} {{ env["image_dir"] }}
</div>
{% endif %}
</section>
<hr/>
<section id="attach-devices">
<details>
<summary class="heading">
{{ _("Attach Device") }}
</summary>
<ul>
</li>
{% if bridge_configured %}
<li>{{ _("The <tt>piscsi_bridge</tt> network bridge is active and ready to be used by an emulated network adapter!") }}</li>
{% else %}
<li>{{ _("Please configure the <tt>piscsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
{% endif %}
<li>{{ _("Read more about <a href=\"%(url)s\" target=\"_blank\">supported device types</a> on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }}
</li>
</ul>
</details>
<table border="black" cellpadding="3" summary="List of peripheral devices">
<tr>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Key") }}</th>
<th scope="col">{{ _("Actions") }}</th>
</tr>
{% for type in device_types.keys() %}
<tr>
<td>
{% if device_types[type]["name"] == type %}
{% if type in REMOVABLE_DEVICE_TYPES %}
<div>{{ _("Unknown Removable Disk Drive") }}</div>
{% elif type in DISK_DEVICE_TYPES %}
<div>{{ _("Unknown Fixed Disk Drive") }}</div>
{% else %}
<div>{{ _("Unknown Device") }}</div>
{% endif %}
{% else %}
<div>{{ device_types[type]["name"] }}</div>
{% endif %}
</td>
<td>
<div>{{ type }}</div>
</td>
<td>
<form action="/scsi/attach" method="post" class="device-attach">
<input name="type" type="hidden" value="{{ type }}">
{% for key, value in device_types[type]["params"] | dictsort %}
<label for="param_{{ type }}_{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="param_{{ key }}" id="param_{{ type }}_{{ key }}">
{% for if in netinfo["ifs"] %}
<option value="{{ if }}">
{{ if }}
</option>
{% endfor %}
</select>
{% else %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
{% endif %}
{% endfor %}
{% if type in DISK_DEVICE_TYPES %}
<label for="{{ type }}_drive_name">{{ _("Identify as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name" class="table-dropdown">
<option value="">
{{ _("Generic device") }}
</option>
{% if type == "SCHD" %}
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCCD" %}
{% for drive in drive_properties["cd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCRM" %}
{% for drive in drive_properties["rm_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCMO" %}
{% for drive in drive_properties["mo_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
</select>
<label for="{{ type }}_image_file_name">{{ _("Image file:") }}</label>
<select name="file_name" id="{{ type }}_image_file_name" class="table-dropdown" {% if type not in REMOVABLE_DEVICE_TYPES %}required{% endif %}>
<option value="" selected {% if type not in REMOVABLE_DEVICE_TYPES %}disabled{% endif %}>
{% if type in REMOVABLE_DEVICE_TYPES %}
{{ _("None") }}
{% else %}
{{ _("Choose a file...") }}
{% endif %}
</option>
{% for f in files|sort(attribute='name') %}
{% if type == "SCHD" %}
{% if f["name"].lower().endswith(env['hd_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCCD" %}
{% if f["name"].lower().endswith(env['cd_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCRM" %}
{% if f["name"].lower().endswith(env['rm_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCMO" %}
{% if f["name"].lower().endswith(env['mo_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% else %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% endfor %}
</select>
{% endif %}
<label for="{{ type }}_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="{{ type }}_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="{{ type }}_unit">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="{{ type }}_unit" type="number" value="0" min="0" max="31" step="1" size="3">
<input type="submit" value="{{ _("Attach") }}" title="{{ _("Attach") }}">
</form>
</td>
</tr>
{% endfor %}
</table>
</section>
<hr/>
@ -432,6 +596,69 @@
<hr/>
<section id="create-image">
<details>
<summary class="heading">
{{ _("Create Empty Disk Image") }}
</summary>
<ul>
<li>{{ _("Please refer to <a href=\"%(url)s\" target=\"_blank\">wiki documentation</a> to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}</li>
<li>{{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}</li>
</ul>
</details>
<form action="/files/create" method="post">
<label for="image_create_file_name">{{ _("File Name:") }}</label>
<input name="file_name" id="image_create_file_name" required="" type="text">
<label for="image_create_type">{{ _("Type:") }}</label>
<select name="type" id="image_create_type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="image_create_size">{{ _("Size:") }}</label>
<input name="size" id="image_create_size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<label for="image_create_drive_name">{{ _("Identify as:") }}</label>
<select name="drive_name" id="image_create_drive_name">
<option value="">
{{ _("Generic device") }}
</option>
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
</select>
<label for="drive_format">{{ _("Format as:") }}</label>
<select name="drive_format" id="drive_format">
<option value="">
{{ _("Unformatted") }}
</option>
<option value="Lido 7.56">
HFS + Lido
</option>
<option value="SpeedTools 3.6">
HFS + SpeedTools
</option>
<option value="FAT16">
FAT16
</option>
<option value="FAT32">
FAT32
</option>
</select>
<input type="submit" value="{{ _("Create") }}">
</form>
</section>
<section id="create-drive">
<a href="/drive/list"><p>{{ _("Create Disk Image With Properties") }}</p></a>
</section>
<hr/>
<section id="create-iso">
<details>
<summary class="heading">
@ -507,166 +734,4 @@
<hr/>
<section id="create-image">
<details>
<summary class="heading">
{{ _("Create Empty Disk Image") }}
</summary>
<ul>
<li>{{ _("Please refer to <a href=\"%(url)s\" target=\"_blank\">wiki documentation</a> to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}</li>
<li>{{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}</li>
</ul>
</details>
<form action="/files/create" method="post">
<label for="image_create_file_name">{{ _("File Name:") }}</label>
<input name="file_name" id="image_create_file_name" required="" type="text">
<label for="image_create_type">{{ _("Type:") }}</label>
<select name="type" id="image_create_type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="image_create_size">{{ _("Size:") }}</label>
<input name="size" id="image_create_size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<label for="image_create_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="image_create_drive_name">
<option value="">
{{ _("None") }}
</option>
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
</select>
<label for="drive_format">{{ _("Format as:") }}</label>
<select name="drive_format" id="drive_format">
<option value="">
{{ _("None") }}
</option>
<option value="Lido 7.56">
HFS + Lido
</option>
<option value="SpeedTools 3.6">
HFS + SpeedTools
</option>
<option value="FAT16">
FAT16
</option>
<option value="FAT32">
FAT32
</option>
</select>
<input type="submit" value="{{ _("Create") }}">
</form>
</section>
<section id="create-drive">
<a href="/drive/list"><p>{{ _("Create Disk Image With Properties") }}</p></a>
</section>
<hr/>
<section id="attach-devices">
<details>
<summary class="heading">
{{ _("Attach Peripheral Device") }}
</summary>
<ul>
</li>
{% if bridge_configured %}
<li>{{ _("The <tt>piscsi_bridge</tt> network bridge is active and ready to be used by an emulated network adapter!") }}</li>
{% else %}
<li>{{ _("Please configure the <tt>piscsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
{% endif %}
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\" target=\"_blank\">Macproxy</a>.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}</li>
</li>
<li>{{ _("Read more about <a href=\"%(url)s\" target=\"_blank\">supported device types</a> on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }}
</li>
</ul>
</details>
<table border="black" cellpadding="3" summary="List of peripheral devices">
<tr>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Key") }}</th>
<th scope="col">{{ _("Parameters and Actions") }}</th>
</tr>
{% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %}
<tr>
<td>
<div>{{ device_types[type]["name"] }}</div>
</td>
<td>
<div>{{ type }}</div>
</td>
<td>
<form action="/scsi/attach_device" method="post" class="device-attach">
<input name="type" type="hidden" value="{{ type }}">
{% for key, value in device_types[type]["params"] | dictsort %}
<label for="param_{{ type }}_{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="param_{{ key }}" id="param_{{ type }}_{{ key }}">
{% for if in netinfo["ifs"] %}
<option value="{{ if }}">
{{ if }}
</option>
{% endfor %}
</select>
{% else %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
{% endif %}
{% endfor %}
{% if type in REMOVABLE_DEVICE_TYPES %}
<label for="{{ type }}_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name">
<option value="">
{{ _("None") }}
</option>
{% if type == "SCCD" %}
{% for drive in drive_properties["cd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCRM" %}
{% for drive in drive_properties["rm_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCMO" %}
{% for drive in drive_properties["mo_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
</select>
{% endif %}
<label for="{{ type }}_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="{{ type }}_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="{{ type }}_unit">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="{{ type }}_unit" type="number" value="0" min="0" max="31" step="1" size="3">
<input type="submit" value="{{ _("Attach") }}" title="{{ _("Attach") }}">
</form>
</td>
</tr>
{% endfor %}
</table>
</section>
<hr/>
{% endblock content %}

View File

@ -1309,7 +1309,7 @@ msgstr ""
#: src/templates/index.html:594
msgid "Key"
msgstr "Taste"
msgstr "Kürzel"
#: src/templates/index.html:595
msgid "Parameters and Actions"

View File

@ -46,7 +46,6 @@ from return_code_mapper import ReturnCodeMapper
from socket_cmds_flask import SocketCmdsFlask
from web_utils import (
working_dirs_exist,
sort_and_format_devices,
get_valid_scsi_ids,
map_device_types_and_names,
@ -125,6 +124,9 @@ def get_env_info():
"image_dir": server_info["image_dir"],
"image_root_dir": Path(server_info["image_dir"]).name,
"shared_root_dir": Path(FILE_SERVER_DIR).name,
"image_dir_exists": Path(server_info["image_dir"]).exists(),
"cfg_dir_exists": Path(CFG_DIR).exists(),
"hd_suffixes": tuple(server_info["schd"]),
"cd_suffixes": tuple(server_info["sccd"]),
"rm_suffixes": tuple(server_info["scrm"]),
"mo_suffixes": tuple(server_info["scmo"]),
@ -219,7 +221,6 @@ def index():
Sets up data structures for and renders the index page
"""
server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
devices = piscsi_cmd.list_devices()
device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"])
@ -304,9 +305,6 @@ def drive_list():
"""
Sets up the data structures and kicks off the rendering of the drive list page
"""
server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
return response(
template="drives.html",
page_title=_("PiSCSI Create Drive"),
@ -342,7 +340,6 @@ def upload_page():
Sets up the data structures and kicks off the rendering of the file uploading page
"""
server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
return response(
template="upload.html",
@ -544,7 +541,6 @@ def show_diskinfo():
if not safe_path["status"]:
return response(error=True, message=safe_path["msg"])
server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
returncode, diskinfo = sys_cmd.get_diskinfo(Path(server_info["image_dir"]) / file_name)
if returncode == 0:
return response(
@ -647,16 +643,17 @@ def log_level():
return response(error=True, message=process["msg"])
@APP.route("/scsi/attach_device", methods=["POST"])
@APP.route("/scsi/attach", methods=["POST"])
@login_required
def attach_device():
"""
Attaches a peripheral device that doesn't take an image file as argument
Attaches device of any type
"""
scsi_id = request.form.get("scsi_id")
unit = request.form.get("unit")
device_type = request.form.get("type")
drive_name = request.form.get("drive_name")
file_name = request.form.get("file_name")
if not scsi_id:
return response(error=True, message=_("No SCSI ID specified"))
@ -690,11 +687,29 @@ def attach_device():
"device_type": device_type,
"params": params,
}
if file_name:
kwargs["params"]["file"] = file_name
# If drive_props is defined use properies from this dict,
# otherwise fall back to the properties file if it exists
if drive_props:
kwargs["vendor"] = drive_props["vendor"]
kwargs["product"] = drive_props["product"]
kwargs["revision"] = drive_props["revision"]
kwargs["block_size"] = drive_props["block_size"]
else:
drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
if drive_properties.is_file():
process = file_cmd.read_drive_properties(drive_properties)
process = ReturnCodeMapper.add_msg(process)
if not process["status"]:
return response(error=True, message=process["msg"])
conf = process["conf"]
kwargs["vendor"] = conf["vendor"]
kwargs["product"] = conf["product"]
kwargs["revision"] = conf["revision"]
kwargs["block_size"] = conf["block_size"]
process = piscsi_cmd.attach_device(scsi_id, **kwargs)
process = ReturnCodeMapper.add_msg(process)
@ -711,70 +726,6 @@ def attach_device():
return response(error=True, message=process["msg"])
@APP.route("/scsi/attach", methods=["POST"])
@login_required
def attach_image():
"""
Attaches a file image as a device
"""
file_name = request.form.get("file_name")
file_size = request.form.get("file_size")
scsi_id = request.form.get("scsi_id")
unit = request.form.get("unit")
device_type = request.form.get("type")
if not scsi_id:
return response(error=True, message=_("No SCSI ID specified"))
if not file_name:
return response(error=True, message=_("No image file to insert"))
kwargs = {"unit": int(unit), "params": {"file": file_name}}
if device_type:
kwargs["device_type"] = device_type
device_types = piscsi_cmd.get_device_types()
expected_block_size = min(device_types["device_types"][device_type]["block_sizes"])
# Attempt to load the device properties file:
# same file name with PROPERTIES_SUFFIX appended
drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
if drive_properties.is_file():
process = file_cmd.read_drive_properties(drive_properties)
process = ReturnCodeMapper.add_msg(process)
if not process["status"]:
return response(error=True, message=process["msg"])
conf = process["conf"]
kwargs["vendor"] = conf["vendor"]
kwargs["product"] = conf["product"]
kwargs["revision"] = conf["revision"]
kwargs["block_size"] = conf["block_size"]
expected_block_size = conf["block_size"]
process = piscsi_cmd.attach_device(scsi_id, **kwargs)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
if int(file_size) % int(expected_block_size):
logging.warning(
"The image file size %s bytes is not a multiple of %s. "
"PiSCSI will ignore the trailing data. "
"The image may be corrupted, so proceed with caution.",
file_size,
expected_block_size,
)
return response(
message=_(
"Attached %(file_name)s as %(device_type)s to "
"SCSI ID %(id_number)s LUN %(unit_number)s",
file_name=file_name,
device_type=get_device_name(device_type),
id_number=scsi_id,
unit_number=unit,
)
)
return response(error=True, message=process["msg"])
@APP.route("/scsi/detach_all", methods=["POST"])
@login_required
def detach_all_devices():

View File

@ -8,26 +8,13 @@ from pathlib import Path
from ua_parser import user_agent_parser
from re import findall
from flask import request, abort
from flask import request
from flask_babel import _
from werkzeug.utils import secure_filename
from piscsi.sys_cmds import SysCmds
def working_dirs_exist(working_dirs):
"""
Method for validating that working dirs exist.
Takes (tuple) of (str) working_dirs with paths to required dirs.
"""
for dir_path in working_dirs:
if not Path(dir_path).exists():
abort(
503,
_(f"Please create directory: {dir_path}"),
)
def get_valid_scsi_ids(devices, reserved_ids):
"""
Takes a list of (dict)s devices, and list of (int)s reserved_ids.

View File

@ -8,6 +8,41 @@ FILE_SIZE_1_MIB = 1048576
STATUS_SUCCESS = "success"
STATUS_ERROR = "error"
ENV_ENDPOINT = "/env"
HEALTHCHECK_ENDPOINT = "/healthcheck"
PWA_FAVICON_ENDPOINT = "/pwa/favicon.ico"
LOGIN_ENDPOINT = "/login"
LOGOUT_ENDPOINT = "/logout"
ATTACH_ENDPOINT = "/scsi/attach"
DETACH_ENDPOINT = "/scsi/detach"
DETACH_ALL_ENDPOINT = "/scsi/detach_all"
EJECT_ENDPOINT = "/scsi/eject"
RESERVE_ENDPOINT = "/scsi/reserve"
RELEASE_ENDPOINT = "/scsi/release"
INFO_ENDPOINT = "/scsi/info"
CREATE_ENDPOINT = "/files/create"
RENAME_ENDPOINT = "/files/rename"
COPY_ENDPOINT = "/files/copy"
DELETE_ENDPOINT = "/files/delete"
DOWNLOAD_URL_ENDPOINT = "/files/download_url"
DOWNLOAD_IMAGE_ENDPOINT = "/files/download_image"
DOWNLOAD_CONFIG_ENDPOINT = "/files/download_config"
EXTRACT_IMAGE_ENDPOINT = "/files/extract_image"
UPLOAD_ENDPOINT = "/files/upload"
CREATE_ISO_ENDPOINT = "/files/create_iso"
DISKINFO_ENDPOINT = "/files/diskinfo"
DRIVE_LIST_ENDPOINT = "/drive/list"
DRIVE_CREATE_ENDPOINT = "/drive/create"
DRIVE_CDROM_ENDPOINT = "/drive/cdrom"
MANPAGE_ENDPOINT = "/sys/manpage?app=piscsi"
LANGUAGE_ENDPOINT = "/language"
LOG_LEVEL_ENDPOINT = "/logs/level"
LOG_SHOW_ENDPOINT = "/logs/show"
CONFIG_SAVE_ENDPOINT = "/config/save"
CONFIG_ACTION_ENDPOINT = "/config/action"
THEME_ENDPOINT = "/theme"
SYS_RENAME_ENDPOINT = "/sys/rename"
@pytest.fixture(scope="function")
def create_test_image(request, http_client):
@ -18,7 +53,7 @@ def create_test_image(request, http_client):
file_name = f"{file_prefix}.{image_type}"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": image_type,
@ -42,7 +77,7 @@ def create_test_image(request, http_client):
def delete():
for image in images:
response = http_client.post("/files/delete", data={"file_name": image["file_name"]})
response = http_client.post(DELETE_ENDPOINT, data={"file_name": image["file_name"]})
if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
warnings.warn(
f"Failed to auto-delete file created with create_test_image fixture: {image}"
@ -71,7 +106,7 @@ def list_attached_images(http_client):
@pytest.fixture(scope="function")
def delete_file(http_client):
def delete(file_name):
response = http_client.post("/files/delete", data={"file_name": file_name})
response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name})
if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
warnings.warn(f"Failed to delete file via delete_file fixture: {file_name}")
@ -81,7 +116,7 @@ def delete_file(http_client):
@pytest.fixture(scope="function")
def detach_devices(http_client):
def detach():
response = http_client.post("/scsi/detach_all")
response = http_client.post(DETACH_ALL_ENDPOINT)
if response.json()["status"] == STATUS_SUCCESS:
return True
raise Exception("Failed to detach SCSI devices")

View File

@ -1,11 +1,10 @@
from conftest import STATUS_SUCCESS, STATUS_ERROR
from conftest import STATUS_SUCCESS, STATUS_ERROR, LOGIN_ENDPOINT, LOGOUT_ENDPOINT
# route("/login", methods=["POST"])
def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated):
# Note: This test depends on the piscsi group existing and 'username' a member the group
response = http_client_unauthenticated.post(
"/login",
LOGIN_ENDPOINT,
data={
"username": pytestconfig.getoption("piscsi_username"),
"password": pytestconfig.getoption("piscsi_password"),
@ -19,10 +18,9 @@ def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated)
assert "env" in response_data["data"]
# route("/login", methods=["POST"])
def test_login_with_invalid_credentials(http_client_unauthenticated):
response = http_client_unauthenticated.post(
"/login",
LOGIN_ENDPOINT,
data={
"username": "__INVALID_USER__",
"password": "__INVALID_PASS__",
@ -38,7 +36,6 @@ def test_login_with_invalid_credentials(http_client_unauthenticated):
)
# route("/logout")
def test_logout(http_client):
response = http_client.get("/logout")
response = http_client.get(LOGOUT_ENDPOINT)
assert response.status_code == 200

View File

@ -2,20 +2,24 @@ import pytest
from conftest import (
SCSI_ID,
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
ATTACH_ENDPOINT,
DETACH_ENDPOINT,
DETACH_ALL_ENDPOINT,
EJECT_ENDPOINT,
RESERVE_ENDPOINT,
RELEASE_ENDPOINT,
INFO_ENDPOINT,
)
# route("/scsi/attach", methods=["POST"])
def test_attach_image(http_client, create_test_image, detach_devices):
def test_attach_device_with_image(http_client, create_test_image, detach_devices):
test_image = create_test_image()
response = http_client.post(
"/scsi/attach",
ATTACH_ENDPOINT,
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",
@ -26,14 +30,13 @@ def test_attach_image(http_client, create_test_image, detach_devices):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == (
f"Attached {test_image} as Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0"
f"Attached Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0"
)
# Cleanup
detach_devices()
# route("/scsi/attach_device", methods=["POST"])
@pytest.mark.parametrize(
"device_name,device_config",
[
@ -89,7 +92,7 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con
device_config["unit"] = 0
response = http_client.post(
"/scsi/attach_device",
ATTACH_ENDPOINT,
data=device_config,
)
@ -105,15 +108,13 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con
detach_devices()
# route("/scsi/detach", methods=["POST"])
def test_detach_device(http_client, create_test_image):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
ATTACH_ENDPOINT,
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",
@ -121,7 +122,7 @@ def test_detach_device(http_client, create_test_image):
)
response = http_client.post(
"/scsi/detach",
DETACH_ENDPOINT,
data={
"scsi_id": SCSI_ID,
"unit": 0,
@ -135,7 +136,6 @@ def test_detach_device(http_client, create_test_image):
assert response_data["messages"][0]["message"] == f"Detached SCSI ID {SCSI_ID} LUN 0"
# route("/scsi/detach_all", methods=["POST"])
def test_detach_all_devices(http_client, create_test_image, list_attached_images):
test_images = []
scsi_ids = [4, 5, 6]
@ -145,10 +145,9 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images
test_images.append(test_image)
http_client.post(
"/scsi/attach",
ATTACH_ENDPOINT,
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": scsi_id,
"unit": 0,
"type": "SCHD",
@ -157,7 +156,7 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images
assert list_attached_images() == test_images
response = http_client.post("/scsi/detach_all")
response = http_client.post(DETACH_ALL_ENDPOINT)
response_data = response.json()
assert response.status_code == 200
@ -165,15 +164,13 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images
assert list_attached_images() == []
# route("/scsi/eject", methods=["POST"])
def test_eject_device(http_client, create_test_image, detach_devices):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
ATTACH_ENDPOINT,
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCCD", # CD-ROM
@ -181,7 +178,7 @@ def test_eject_device(http_client, create_test_image, detach_devices):
)
response = http_client.post(
"/scsi/eject",
EJECT_ENDPOINT,
data={
"scsi_id": SCSI_ID,
"unit": 0,
@ -198,15 +195,13 @@ def test_eject_device(http_client, create_test_image, detach_devices):
detach_devices()
# route("/scsi/info", methods=["POST"])
def test_show_device_info(http_client, create_test_image, detach_devices):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
ATTACH_ENDPOINT,
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",
@ -214,7 +209,7 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
)
response = http_client.post(
"/scsi/info",
INFO_ENDPOINT,
)
response_data = response.json()
@ -228,13 +223,11 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
detach_devices()
# route("/scsi/reserve", methods=["POST"])
# route("/scsi/release", methods=["POST"])
def test_reserve_and_release_device(http_client):
scsi_id = 0
response = http_client.post(
"/scsi/reserve",
RESERVE_ENDPOINT,
data={
"scsi_id": scsi_id,
"memo": "TEST",
@ -248,7 +241,7 @@ def test_reserve_and_release_device(http_client):
assert response_data["messages"][0]["message"] == f"Reserved SCSI ID {scsi_id}"
response = http_client.post(
"/scsi/release",
RELEASE_ENDPOINT,
data={
"scsi_id": scsi_id,
},

View File

@ -5,16 +5,26 @@ import os
from conftest import (
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
CREATE_ENDPOINT,
RENAME_ENDPOINT,
COPY_ENDPOINT,
DELETE_ENDPOINT,
DOWNLOAD_URL_ENDPOINT,
DOWNLOAD_IMAGE_ENDPOINT,
DOWNLOAD_CONFIG_ENDPOINT,
EXTRACT_IMAGE_ENDPOINT,
UPLOAD_ENDPOINT,
CREATE_ISO_ENDPOINT,
DISKINFO_ENDPOINT,
)
# route("/files/create", methods=["POST"])
def test_create_file(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": "hds",
@ -34,13 +44,12 @@ def test_create_file(http_client, list_files, delete_file):
delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_with_properties(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": "hds",
@ -64,13 +73,12 @@ def test_create_file_with_properties(http_client, list_files, delete_file):
delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_and_format_hfs(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hda"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": "hda",
@ -91,7 +99,6 @@ def test_create_file_and_format_hfs(http_client, list_files, delete_file):
delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_and_format_fat(env, http_client, list_files, delete_file):
if env["is_docker"]:
pytest.skip("Test not supported in Docker environment.")
@ -99,7 +106,7 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file):
file_name = f"{file_prefix}.hdr"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": "hdr",
@ -120,13 +127,12 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file):
delete_file(file_name)
# route("/files/rename", methods=["POST"])
def test_rename_file(http_client, create_test_image, list_files, delete_file):
original_file = create_test_image(auto_delete=False)
renamed_file = f"{uuid.uuid4()}.rename"
response = http_client.post(
"/files/rename",
RENAME_ENDPOINT,
data={"file_name": original_file, "new_file_name": renamed_file},
)
@ -141,13 +147,12 @@ def test_rename_file(http_client, create_test_image, list_files, delete_file):
delete_file(renamed_file)
# route("/files/copy", methods=["POST"])
def test_copy_file(http_client, create_test_image, list_files, delete_file):
original_file = create_test_image()
copy_file = f"{uuid.uuid4()}.copy"
response = http_client.post(
"/files/copy",
COPY_ENDPOINT,
data={
"file_name": original_file,
"copy_file_name": copy_file,
@ -167,11 +172,10 @@ def test_copy_file(http_client, create_test_image, list_files, delete_file):
delete_file(copy_file)
# route("/files/delete", methods=["POST"])
def test_delete_file(http_client, create_test_image, list_files):
file_name = create_test_image(auto_delete=False)
response = http_client.post("/files/delete", data={"file_name": file_name})
response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name})
response_data = response.json()
@ -181,7 +185,6 @@ def test_delete_file(http_client, create_test_image, list_files):
assert file_name not in list_files()
# route("/files/extract_image", methods=["POST"])
@pytest.mark.parametrize(
"archive_file_name,image_file_name",
[
@ -205,7 +208,7 @@ def test_extract_file(
)
http_client.post(
"/files/download_url",
DOWNLOAD_URL_ENDPOINT,
data={
"destination": "disk_images",
"images_subdir": "",
@ -214,7 +217,7 @@ def test_extract_file(
)
response = http_client.post(
"/files/extract_image",
EXTRACT_IMAGE_ENDPOINT,
data={
"archive_file": archive_file_name,
"archive_members": image_file_name,
@ -233,7 +236,6 @@ def test_extract_file(
delete_file(image_file_name)
# route("/files/upload", methods=["POST"])
def test_upload_file(http_client, delete_file):
file_name = f"{uuid.uuid4()}.test"
@ -267,7 +269,7 @@ def test_upload_file(http_client, delete_file):
file_data = {"file": (file_name, file.read(chunk_size))}
response = http_client.post(
"/files/upload",
UPLOAD_ENDPOINT,
data=form_data,
files=file_data,
)
@ -283,11 +285,10 @@ def test_upload_file(http_client, delete_file):
delete_file(file_name)
# route("/files/download_image", methods=["POST"])
def test_download_image(http_client, create_test_image):
file_name = create_test_image()
response = http_client.post("/files/download_image", data={"file": file_name})
response = http_client.post(DOWNLOAD_IMAGE_ENDPOINT, data={"file": file_name})
assert response.status_code == 200
assert response.headers["content-type"] == "application/octet-stream"
@ -295,13 +296,12 @@ def test_download_image(http_client, create_test_image):
assert response.headers["content-length"] == str(FILE_SIZE_1_MIB)
# route("/files/download_config", methods=["POST"])
def test_download_properties(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds"
response = http_client.post(
"/files/create",
CREATE_ENDPOINT,
data={
"file_name": file_prefix,
"type": "hds",
@ -321,7 +321,7 @@ def test_download_properties(http_client, list_files, delete_file):
)
assert file_name in list_files()
response = http_client.post("/files/download_config", data={"file": f"{file_name}.properties"})
response = http_client.post(DOWNLOAD_CONFIG_ENDPOINT, data={"file": f"{file_name}.properties"})
assert response.status_code == 200
assert response.headers["content-type"] == "application/octet-stream"
@ -331,7 +331,6 @@ def test_download_properties(http_client, list_files, delete_file):
delete_file(file_name)
# route("/files/download_url", methods=["POST"])
def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file):
file_name = str(uuid.uuid4())
http_path = f"/images/{file_name}"
@ -347,7 +346,7 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi
)
response = http_client.post(
"/files/download_url",
DOWNLOAD_URL_ENDPOINT,
data={
"destination": "disk_images",
"images_subdir": subdir,
@ -369,7 +368,6 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi
delete_file(file_name)
# route("/files/create_iso", methods=["POST"])
def test_create_iso_from_url(
httpserver,
http_client,
@ -392,7 +390,7 @@ def test_create_iso_from_url(
)
response = http_client.post(
"/files/create_iso",
CREATE_ISO_ENDPOINT,
data={
"type": ISO_TYPE,
"url": url,
@ -414,7 +412,6 @@ def test_create_iso_from_url(
delete_file(iso_file_name)
# route("/files/create_iso", methods=["POST"])
def test_create_iso_from_local_file(
http_client,
create_test_image,
@ -427,7 +424,7 @@ def test_create_iso_from_local_file(
ISO_TYPE = "HFS"
response = http_client.post(
"/files/create_iso",
CREATE_ISO_ENDPOINT,
data={
"type": ISO_TYPE,
"file": test_file_name,
@ -449,12 +446,11 @@ def test_create_iso_from_local_file(
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",
DISKINFO_ENDPOINT,
data={
"file_name": test_image,
},

View File

@ -3,10 +3,16 @@ import uuid
from conftest import (
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
ENV_ENDPOINT,
PWA_FAVICON_ENDPOINT,
HEALTHCHECK_ENDPOINT,
DRIVE_LIST_ENDPOINT,
DRIVE_CREATE_ENDPOINT,
DRIVE_CDROM_ENDPOINT,
MANPAGE_ENDPOINT,
)
# route("/")
def test_index(http_client):
response = http_client.get("/")
response_data = response.json()
@ -16,9 +22,8 @@ def test_index(http_client):
assert "devices" in response_data["data"]
# route("/env")
def test_get_env_info(http_client):
response = http_client.get("/env")
response = http_client.get(ENV_ENDPOINT)
response_data = response.json()
assert response.status_code == 200
@ -26,17 +31,15 @@ def test_get_env_info(http_client):
assert "running_env" in response_data["data"]
# route("/pwa/<path:pwa_path>")
def test_pwa_route(http_client):
response = http_client.get("/pwa/favicon.ico")
response = http_client.get(PWA_FAVICON_ENDPOINT)
assert response.status_code == 200
assert response.headers["content-disposition"] == "inline; filename=favicon.ico"
# route("/drive/list", methods=["GET"])
def test_show_named_drive_presets(http_client):
response = http_client.get("/drive/list")
response = http_client.get(DRIVE_LIST_ENDPOINT)
response_data = response.json()
prev_drive = {"name": ""}
@ -57,12 +60,11 @@ def test_show_named_drive_presets(http_client):
assert "files" in response_data["data"]
# route("/drive/cdrom", methods=["POST"])
def test_create_cdrom_properties_file(env, http_client):
file_name = f"{uuid.uuid4()}.iso"
response = http_client.post(
"/drive/cdrom",
DRIVE_CDROM_ENDPOINT,
data={
"drive_name": "Sony CDU-8012",
"file_name": file_name,
@ -78,13 +80,12 @@ def test_create_cdrom_properties_file(env, http_client):
)
# route("/drive/create", methods=["POST"])
def test_create_image_with_properties_file(http_client, delete_file):
file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds"
response = http_client.post(
"/drive/create",
DRIVE_CREATE_ENDPOINT,
data={
"drive_name": "Miniscribe M8425",
"size": FILE_SIZE_1_MIB,
@ -105,16 +106,14 @@ def test_create_image_with_properties_file(http_client, delete_file):
delete_file(file_name)
# route("/sys/manpage", methods=["POST"])
def test_show_manpage(http_client):
response = http_client.get("/sys/manpage?app=piscsi")
response = http_client.get(MANPAGE_ENDPOINT)
response_data = response.json()
assert response.status_code == 200
assert "piscsi" in response_data["data"]["manpage"]
# route("/healthcheck", methods=["GET"])
def test_healthcheck(http_client):
response = http_client.get("/healthcheck")
response = http_client.get(HEALTHCHECK_ENDPOINT)
assert response.status_code == 200

View File

@ -1,10 +1,20 @@
import pytest
import uuid
from conftest import STATUS_SUCCESS
from conftest import (
STATUS_SUCCESS,
ENV_ENDPOINT,
LANGUAGE_ENDPOINT,
LOG_LEVEL_ENDPOINT,
LOG_SHOW_ENDPOINT,
CONFIG_SAVE_ENDPOINT,
CONFIG_ACTION_ENDPOINT,
THEME_ENDPOINT,
SYS_RENAME_ENDPOINT,
RESERVE_ENDPOINT,
)
# route("/language", methods=["POST"])
@pytest.mark.parametrize(
"locale,confirm_message",
[
@ -18,7 +28,7 @@ from conftest import STATUS_SUCCESS
)
def test_set_language(http_client, locale, confirm_message):
response = http_client.post(
"/language",
LANGUAGE_ENDPOINT,
data={
"locale": locale,
},
@ -31,11 +41,10 @@ def test_set_language(http_client, locale, confirm_message):
assert response_data["messages"][0]["message"] == confirm_message
# route("/logs/level", methods=["POST"])
@pytest.mark.parametrize("level", ["trace", "debug", "info", "warn", "err", "off"])
def test_set_log_level(http_client, level):
response = http_client.post(
"/logs/level",
LOG_LEVEL_ENDPOINT,
data={
"level": level,
},
@ -49,17 +58,16 @@ def test_set_log_level(http_client, level):
# Cleanup
http_client.post(
"/logs/level",
LOG_LEVEL_ENDPOINT,
data={
"level": "debug",
},
)
# route("/logs/show", methods=["POST"])
def test_show_logs(http_client):
response = http_client.post(
"/logs/show",
LOG_SHOW_ENDPOINT,
data={
"lines": 100,
"scope": "piscsi",
@ -73,8 +81,6 @@ def test_show_logs(http_client):
assert response_data["data"]["scope"] == "piscsi"
# route("/config/save", methods=["POST"])
# route("/config/action", methods=["POST"])
def test_save_load_and_delete_configs(env, http_client):
config_name = str(uuid.uuid4())
config_json_file = f"{config_name}.json"
@ -86,7 +92,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Save the initial state to a config
response = http_client.post(
"/config/save",
CONFIG_SAVE_ENDPOINT,
data={
"name": config_name,
},
@ -104,7 +110,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Modify the state
http_client.post(
"/scsi/reserve",
RESERVE_ENDPOINT,
data={
"scsi_id": reserved_scsi_id,
"memo": reservation_memo,
@ -115,7 +121,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Load the saved config
response = http_client.post(
"/config/action",
CONFIG_ACTION_ENDPOINT,
data={
"name": config_json_file,
"load": True,
@ -135,7 +141,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Delete the saved config
response = http_client.post(
"/config/action",
CONFIG_ACTION_ENDPOINT,
data={
"name": config_json_file,
"delete": True,
@ -153,15 +159,13 @@ def test_save_load_and_delete_configs(env, http_client):
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):
def test_download_configs(env, http_client):
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",
CONFIG_SAVE_ENDPOINT,
data={
"name": config_name,
},
@ -172,7 +176,7 @@ def test_download_configs(env, http_client, delete_file):
# Download the saved config
response = http_client.post(
"/config/action",
CONFIG_ACTION_ENDPOINT,
data={
"name": config_json_file,
"send": True,
@ -185,7 +189,7 @@ def test_download_configs(env, http_client, delete_file):
# Delete the saved config
response = http_client.post(
"/config/action",
CONFIG_ACTION_ENDPOINT,
data={
"name": config_json_file,
"delete": True,
@ -196,7 +200,6 @@ def test_download_configs(env, http_client, delete_file):
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
# route("/theme", methods=["POST"])
@pytest.mark.parametrize(
"theme",
[
@ -206,7 +209,7 @@ def test_download_configs(env, http_client, delete_file):
)
def test_set_theme(http_client, theme):
response = http_client.post(
"/theme",
THEME_ENDPOINT,
data={
"name": theme,
},
@ -219,7 +222,6 @@ def test_set_theme(http_client, theme):
assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'."
# route("/theme", methods=["GET"])
@pytest.mark.parametrize(
"theme",
[
@ -229,7 +231,7 @@ def test_set_theme(http_client, theme):
)
def test_set_theme_via_query_string(http_client, theme):
response = http_client.get(
"/theme",
THEME_ENDPOINT,
params={
"name": theme,
},
@ -242,17 +244,16 @@ def test_set_theme_via_query_string(http_client, theme):
assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'."
# route("/sys/rename", methods=["POST"])
def test_rename_system(env, http_client):
new_name = "SYSTEM NAME TEST"
response = http_client.get("/env")
response = http_client.get(ENV_ENDPOINT)
response_data = response.json()
old_name = response_data["data"]["system_name"]
response = http_client.post(
"/sys/rename",
SYS_RENAME_ENDPOINT,
data={
"system_name": new_name,
},
@ -264,13 +265,13 @@ def test_rename_system(env, http_client):
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{new_name}'."
response = http_client.get("/env")
response = http_client.get(ENV_ENDPOINT)
response_data = response.json()
assert response_data["data"]["system_name"] == new_name
response = http_client.post(
"/sys/rename",
SYS_RENAME_ENDPOINT,
data={
"system_name": old_name,
},
@ -282,7 +283,7 @@ def test_rename_system(env, http_client):
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{old_name}'."
response = http_client.get("/env")
response = http_client.get(ENV_ENDPOINT)
response_data = response.json()
assert response_data["data"]["system_name"] == old_name