Web UI: Rework the Attach Device section to be universal

This commit is contained in:
Daniel Markstedt 2023-12-01 13:30:30 +09:00
parent a52fda0fbd
commit 342ab56735
3 changed files with 166 additions and 184 deletions

View File

@ -83,11 +83,10 @@
<td class="name" align="center">{{ device.device_name }}</td>
<td class="parameters">
{% if "No Media" in device.status %}
<form action="/scsi/attach" method="post">
<form action="/scsi/attach_device" method="post">
<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') %}
@ -187,6 +186,140 @@
<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>{{ _("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 device_types.keys() %}
<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 DISK_DEVICE_TYPES %}
<label for="{{ type }}_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name">
<option value="">
{{ _("None") }}
</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">
{% if type != "SCHD" %}
<option value="">
{{ _("None") }}
</option>
{% endif %}
{% 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 %}
{% 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/>
<section id="files">
<details>
<summary class="heading">
@ -218,7 +351,7 @@
<div>
{% for subdir, group in formatted_image_files.items() %}
<details class="subdir"{% if subdir == env["image_root_dir"] + "/" %} open{% endif %}>
<details class="subdir">
<summary class="dirname">
{{ subdir }}
</summary>
@ -307,9 +440,8 @@
<input type="submit" value="{{ _("Extract") }}" title="{{ _("Extract") }}" onclick="processNotify('{{ _("Extracting all files...") }}')">
</form>
{% else %}
<form action="/scsi/attach" method="post" class="file-attach">
<form action="/scsi/attach_device" 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"] %}
@ -568,105 +700,5 @@
<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

@ -125,6 +125,7 @@ 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,
"hd_suffixes": tuple(server_info["schd"]),
"cd_suffixes": tuple(server_info["sccd"]),
"rm_suffixes": tuple(server_info["scrm"]),
"mo_suffixes": tuple(server_info["scmo"]),
@ -651,12 +652,13 @@ def log_level():
@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 +692,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 +731,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

@ -2,20 +2,18 @@ import pytest
from conftest import (
SCSI_ID,
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
)
# route("/scsi/attach", methods=["POST"])
def test_attach_image(http_client, create_test_image, detach_devices):
# route("/scsi/attach_device", methods=["POST"])
def test_attach_device_with_image(http_client, create_test_image, detach_devices):
test_image = create_test_image()
response = http_client.post(
"/scsi/attach",
"/scsi/attach_device",
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",
@ -26,7 +24,7 @@ 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
@ -110,10 +108,9 @@ def test_detach_device(http_client, create_test_image):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
"/scsi/attach_device",
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",
@ -145,10 +142,9 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images
test_images.append(test_image)
http_client.post(
"/scsi/attach",
"/scsi/attach_device",
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": scsi_id,
"unit": 0,
"type": "SCHD",
@ -170,10 +166,9 @@ def test_eject_device(http_client, create_test_image, detach_devices):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
"/scsi/attach_device",
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCCD", # CD-ROM
@ -203,10 +198,9 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
test_image = create_test_image()
http_client.post(
"/scsi/attach",
"/scsi/attach_device",
data={
"file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID,
"unit": 0,
"type": "SCHD",