mirror of
https://github.com/akuker/RASCSI.git
synced 2025-01-21 13:33:20 +00:00
Disk image profile management using a sidecar config file (#242)
* Handle a case where reserved ids on the Web UI side are not actually reserved on the backend side * Better error handling when no device is found in list_devices * Better warning message * Tag message as error * Fix device_info * Get reserved ids from the server instead of storing a client side state, which caused front and backend to get out of sync in certain cases. * Initial implementation of sidecar configuration reading and writing * Use bytes for drive image creation internally * Add named drive section * Move header to base.html * Create the disk profile list page * Make lists of HDDs, CDRs, and Removable drives * Implement disk image + sidecar creation * Implement CD-ROM device sidecar creation method * Add more device configurations * Add disclaimer * Hide URL if none is provided * Make the web ui use the new protobuf parameter maps * Make daynaport attaching UI more flexible * Use the protobuf interface to create image files * Use new create image method for the sidecar flow as well * Move file deletion logic to protobuf commands; Refactor saving/loading config files * Update disk image creation * Fix error * Disk images the script makes are in Mac format * Add blurb about the risks with using Lido driver (issue#40) to the easyinstall script, while pushing less for using that feature. * Make shutdown and reboot async operations * More informative footer contents * Wordsmith the Mac drive options * Link to relevant section in the wiki * Added GET_IMAGE_FILES * Added default folder to GET_IMAGE_FILES * Renaming * Updated setting image folder * Lists available net interfaces as a drop down when attaching daynaport * Macs should use the hds image file ending * Fixed default image folder handling * Refer to device properties, instead of sidecars * Added NETWORK_INTERFACES_INFO * Filter "lo" * Use PF_INET in favor of PF_INET6 * Added network interfaces to server info * Drive property file ending defined in one place; Add handling of common urllib and file system exceptions. * Use protobuf interface to get network interface info * Use protobuf interface for list_files * Repeated field cleanup * Renaming * Added DEVICE_TYPES_INFO * Comment update * Added -y option to rasctl * Add the remaining recommended drive profiles provided by rpajarola * Fix typos * Add warnings to CD-ROM descriptions * Add more recommended Sun drives * Add capacity to name * Move footer into base.html * Handle removable drive insertion in the attach method (easy to do with protobuf) * Limit which arguments to pass to an image injection command * Cleanup * Sort image and config files alphabetically * Make compatible with updated protobuf interface * Sort drives alphabetically by name * Decriptive text for CD-ROM section * Better description * Hyperlink to disks page instead of button Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
This commit is contained in:
parent
6b082447c5
commit
15febd1ee6
@ -261,7 +261,7 @@ function createDrive() {
|
||||
driveSize=$1
|
||||
driveName=$2
|
||||
mkdir -p $VIRTUAL_DRIVER_PATH
|
||||
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hda"
|
||||
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hds"
|
||||
|
||||
if [ ! -f $drivePath ]; then
|
||||
echo "Creating a ${driveSize}MB Drive"
|
||||
@ -415,19 +415,6 @@ function reserveScsiIds() {
|
||||
|
||||
function runChoice() {
|
||||
case $1 in
|
||||
0)
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive"
|
||||
stopOldWebInterface
|
||||
updateRaScsiGit
|
||||
createImagesDir
|
||||
installPackages
|
||||
installRaScsi
|
||||
installRaScsiWebInterface
|
||||
createDrive600MB
|
||||
showRaScsiStatus
|
||||
showRaScsiWebStatus
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive - Complete!"
|
||||
;;
|
||||
1)
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface"
|
||||
stopOldWebInterface
|
||||
@ -501,11 +488,11 @@ function showMenu() {
|
||||
echo ""
|
||||
echo "Choose among the following options:"
|
||||
echo "INSTALL/UPDATE RASCSI (${CONNECT_TYPE-FULLSPEC} version)"
|
||||
echo " 0) install or update RaSCSI Service + web interface + 600MB Drive (recommended)"
|
||||
echo " 1) install or update RaSCSI Service + web interface"
|
||||
echo " 1) install or update RaSCSI Service + Web Interface"
|
||||
echo " 2) install or update RaSCSI Service"
|
||||
echo "CREATE EMPTY DRIVE IMAGE"
|
||||
echo " 3) 600MB drive (recommended)"
|
||||
echo "CREATE HFS FORMATTED (MAC) IMAGE WITH LIDO DRIVERS"
|
||||
echo "** For the Mac Plus, it's better to create an image through the Web Interface **"
|
||||
echo " 3) 600MB drive (suggested size)"
|
||||
echo " 4) custom drive size (up to 4000MB)"
|
||||
echo "NETWORK ASSISTANT"
|
||||
echo " 5) configure network forwarding over Ethernet (DHCP)"
|
||||
@ -531,7 +518,7 @@ while [ "$1" != "" ]; do
|
||||
;;
|
||||
esac
|
||||
case $VALUE in
|
||||
FULLSPEC | STANDARD | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7)
|
||||
FULLSPEC | STANDARD | 1 | 2 | 3 | 4 | 5 | 6 | 7)
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown option \"$VALUE\""
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <spdlog/async.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
266
src/web/drive_properties.json
Normal file
266
src/web/drive_properties.json
Normal file
@ -0,0 +1,266 @@
|
||||
[
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "DEC",
|
||||
"product": "RZ55 (C) DEC",
|
||||
"revision": "",
|
||||
"block_size": 512,
|
||||
"blocks": 660960,
|
||||
"name": "DEC RZ55",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on Ultrix 3.0",
|
||||
"url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf"
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "DEC",
|
||||
"product": "RZ57 (C) DEC",
|
||||
"revision": "5000",
|
||||
"block_size": 512,
|
||||
"blocks": 2050125,
|
||||
"name": "DEC RZ57",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on Ultrix 3.1 - 4.3",
|
||||
"url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf"
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "DEC",
|
||||
"product": "RZ59 (C) DEC",
|
||||
"revision": "2000",
|
||||
"block_size": 512,
|
||||
"blocks": 17755614,
|
||||
"name": "DEC RZ59",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on OSF/1 3.x - 5.x",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "DEC",
|
||||
"product": "RZ74 (C) DEC",
|
||||
"revision": "427H",
|
||||
"block_size": 512,
|
||||
"blocks": 6950125,
|
||||
"name": "DEC RZ74",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on Ultrix 4.4 - 4.5",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "HP",
|
||||
"product": "A2076",
|
||||
"revision": "DD24",
|
||||
"block_size": 512,
|
||||
"blocks": 2621688,
|
||||
"name": "HP A2076",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on HP-UX 8.0",
|
||||
"url": "http://www.bitsavers.org/pdf/micropolis/105389b_1528_1991.pdf"
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "HP",
|
||||
"product": "C3010",
|
||||
"revision": "6.0",
|
||||
"block_size": 512,
|
||||
"blocks": 3905792,
|
||||
"name": "HP C3010",
|
||||
"file_type": "hds",
|
||||
"description": "Largest recognized drive on HP-UX 9.0",
|
||||
"url": "https://stason.org/TULARC/pc/hard-drives-hdd/hewlett-packard/HP-C3010-001-2003MB-5-25-FH-SCSI2-FAST.html"
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "MICROP",
|
||||
"product": "1325",
|
||||
"revision": "",
|
||||
"block_size": 512,
|
||||
"blocks": 270336,
|
||||
"name": "Micropolis 1325",
|
||||
"file_type": "hds",
|
||||
"description": "Largest predefined on SunOS 2; Microp 1325 is actually an ESDI disk on an adapter.",
|
||||
"url": "https://stason.org/TULARC/pc/hard-drives-hdd/micropolis/1325-69MB-5-25-FH-MFM-ST506.html"
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "MICROP",
|
||||
"product": "1588T",
|
||||
"revision": "",
|
||||
"block_size": 512,
|
||||
"blocks": 651840,
|
||||
"name": "Micropolis 1588-15",
|
||||
"file_type": "hds",
|
||||
"description": "Largest predefined on SunOS 3/4 (Sun-3)",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "SEAGATE",
|
||||
"product": "ST32430N SUN2.1G",
|
||||
"revision": "0444",
|
||||
"block_size": 512,
|
||||
"blocks": 4197405,
|
||||
"name": "Seagate SUN2.1G",
|
||||
"file_type": "hds",
|
||||
"description": "Largest predefined for SunOS 4 (Sun-4) and Solaris 2.0-2.3",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "SEAGATE",
|
||||
"product": "ST34371W SUN4.2G",
|
||||
"revision": "7462",
|
||||
"block_size": 512,
|
||||
"blocks": 8380800,
|
||||
"name": "Seagate SUN4.2G",
|
||||
"file_type": "hds",
|
||||
"description": "Recommended for Solaris 2.4+",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "SEAGATE",
|
||||
"product": "ST39173W SUN9.0G",
|
||||
"revision": "2815",
|
||||
"block_size": 512,
|
||||
"blocks": 17689267,
|
||||
"name": "Seagate SUN9.0G",
|
||||
"file_type": "hds",
|
||||
"description": "Recommended for Solaris 2.4+",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "SEAGATE",
|
||||
"product": "ST914603SSUN146G",
|
||||
"revision": "0B70",
|
||||
"block_size": 512,
|
||||
"blocks": 286739329,
|
||||
"name": "Seagate SUN146G",
|
||||
"file_type": "hds",
|
||||
"description": "Recommended for Solaris 2.4+",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "QUANTUM",
|
||||
"product": "FIREBALL540S",
|
||||
"revision": "",
|
||||
"block_size": 512,
|
||||
"blocks": 1065235,
|
||||
"name": "Quantum Fireball 540S",
|
||||
"file_type": "hds",
|
||||
"description": "Recommended for older Macintosh systems. Recognized by Apple HD SC Setup.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCHD",
|
||||
"vendor": "QUANTUM",
|
||||
"product": "FIREBALL ST4.3S",
|
||||
"revision": "0F0C",
|
||||
"block_size": 512,
|
||||
"blocks": 8471232,
|
||||
"name": "Quantum Fireball ST4.3S",
|
||||
"file_type": "hds",
|
||||
"description": "Recommended for Macintosh System 6 or later. Recognized by Apple HD SC Setup.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCRM",
|
||||
"vendor": "IOMEGA",
|
||||
"product": "ZIP 100",
|
||||
"revision": "D.13",
|
||||
"block_size": 512,
|
||||
"blocks": 196608,
|
||||
"name": "Iomega ZIP 100",
|
||||
"file_type": "hdr",
|
||||
"description": "Removable Iomega ZIP drive, 100MB capacity",
|
||||
"url": "https://www.win.tue.nl/~aeb/linux/zip/zip-1.html"
|
||||
},
|
||||
{
|
||||
"device_type": "SCRM",
|
||||
"vendor": "IOMEGA",
|
||||
"product": "ZIP 250",
|
||||
"revision": "D.58",
|
||||
"block_size": 512,
|
||||
"blocks": 489532,
|
||||
"name": "Iomega ZIP 250",
|
||||
"file_type": "hdr",
|
||||
"description": "Removable Iomega ZIP drive, 250MB capacity",
|
||||
"url": "https://www.win.tue.nl/~aeb/linux/zip/zip-1.html"
|
||||
},
|
||||
{
|
||||
"device_type": "SCRM",
|
||||
"vendor": "IOMEGA",
|
||||
"product": "JAZ 2GB",
|
||||
"revision": "E.16",
|
||||
"block_size": 512,
|
||||
"blocks": 3909632,
|
||||
"name": "Iomega Jaz 2GB",
|
||||
"file_type": "hdr",
|
||||
"description": "Removable Iomega Jaz drive, 2GB capacity",
|
||||
"url": "https://archive.eol.ucar.edu/docs/isf/facilities/iss/manual/creating-data-disks.html"
|
||||
},
|
||||
{
|
||||
"device_type": "SCRM",
|
||||
"vendor": "SYQUEST",
|
||||
"product": "SQ5110C",
|
||||
"revision": "4AA0",
|
||||
"block_size": 512,
|
||||
"blocks": 173456,
|
||||
"name": "SyQuest 88MB",
|
||||
"file_type": "hdr",
|
||||
"description": "SyQuest removable hard drive cartridges, 88MB capacity",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCCD",
|
||||
"vendor": "TOSHIBA",
|
||||
"product": "CD-ROM XM-3401TA",
|
||||
"revision": "0283",
|
||||
"block_size": 512,
|
||||
"blocks": null,
|
||||
"name": "Toshiba XM-3401TA",
|
||||
"file_type": null,
|
||||
"description": "Boots most SGI, Sun, HP, IBM, DEC etc. Use only with Unix workstations of this vintage.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCCD",
|
||||
"vendor": "SONY",
|
||||
"product": "CD-ROM CDU-8012",
|
||||
"revision": "3.1a",
|
||||
"block_size": 512,
|
||||
"blocks": null,
|
||||
"name": "Sony CDU-8012",
|
||||
"file_type": null,
|
||||
"description": "Boots Sun-3. Use only with Unix workstations of this vintage.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCCD",
|
||||
"vendor": "HP",
|
||||
"product": "A1448A",
|
||||
"revision": "",
|
||||
"block_size": 512,
|
||||
"blocks": null,
|
||||
"name": "HP A1448A",
|
||||
"file_type": null,
|
||||
"description": "Recognized by HP-UX. Use only with Unix workstations of this vintage.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCCD",
|
||||
"vendor": "DEC",
|
||||
"product": "RRD42 (C) DEC",
|
||||
"revision": "4.5d",
|
||||
"block_size": 512,
|
||||
"blocks": null,
|
||||
"name": "DEC RRD42",
|
||||
"file_type": null,
|
||||
"description": "Boots DECstations and VAXstations. Use only with Unix workstations of this vintage.",
|
||||
"url": ""
|
||||
}
|
||||
]
|
@ -1,41 +1,30 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import logging
|
||||
|
||||
from ractl_cmds import (
|
||||
attach_image,
|
||||
detach_all,
|
||||
list_devices,
|
||||
send_pb_command,
|
||||
)
|
||||
from settings import *
|
||||
import rascsi_interface_pb2 as proto
|
||||
|
||||
|
||||
def list_files():
|
||||
from fnmatch import translate
|
||||
valid_file_types = list(VALID_FILE_SUFFIX)
|
||||
valid_file_types = ["*." + s for s in valid_file_types]
|
||||
valid_file_types = r"|".join([translate(x) for x in valid_file_types])
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.IMAGE_FILES_INFO
|
||||
|
||||
from re import match, IGNORECASE
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
files = []
|
||||
for f in result.image_files_info.image_files:
|
||||
size_mb = "{:,.1f}".format(f.size / 1024 / 1024)
|
||||
files.append({"name": f.name, "size": f.size, "size_mb": size_mb})
|
||||
|
||||
files_list = []
|
||||
for path, dirs, files in os.walk(base_dir):
|
||||
# Only list valid file types
|
||||
files = [f for f in files if match(valid_file_types, f, IGNORECASE)]
|
||||
files_list.extend(
|
||||
[
|
||||
(
|
||||
os.path.join(path, file),
|
||||
"{:,.1f}".format(
|
||||
os.path.getsize(os.path.join(path, file)) / float(1 << 20),
|
||||
),
|
||||
os.path.getsize(os.path.join(path, file)),
|
||||
)
|
||||
for file in files
|
||||
]
|
||||
)
|
||||
return files_list
|
||||
return {"status": result.status, "msg": result.msg, "files": files}
|
||||
|
||||
|
||||
def list_config_files():
|
||||
@ -47,24 +36,30 @@ def list_config_files():
|
||||
return files_list
|
||||
|
||||
|
||||
def create_new_image(file_name, type, size):
|
||||
if file_name == "":
|
||||
file_name = "new_image." + str(int(time.time())) + "." + type
|
||||
else:
|
||||
file_name = file_name + "." + type
|
||||
def create_new_image(file_name, file_type, size):
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.CREATE_IMAGE
|
||||
|
||||
return subprocess.run(
|
||||
["truncate", "--size", f"{size}m", f"{base_dir}{file_name}"],
|
||||
capture_output=True,
|
||||
)
|
||||
command.params["file"] = file_name + "." + file_type
|
||||
command.params["size"] = str(size)
|
||||
command.params["read_only"] = "false"
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def delete_file(file_name):
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DELETE_IMAGE
|
||||
|
||||
command.params["file"] = file_name
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def unzip_file(file_name):
|
||||
@ -77,6 +72,8 @@ def unzip_file(file_name):
|
||||
|
||||
def download_file_to_iso(scsi_id, url):
|
||||
import urllib.request
|
||||
import urllib.error as error
|
||||
import time
|
||||
|
||||
file_name = url.split("/")[-1]
|
||||
tmp_ts = int(time.time())
|
||||
@ -87,8 +84,10 @@ def download_file_to_iso(scsi_id, url):
|
||||
|
||||
try:
|
||||
urllib.request.urlretrieve(url, tmp_full_path)
|
||||
except (error.URLError, error.HTTPError, error.ContentTooShortError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
# TODO: Capture a more descriptive error message
|
||||
return {"status": False, "msg": "Error loading the URL"}
|
||||
|
||||
# iso_filename = make_cd(tmp_full_path, None, None) # not working yet
|
||||
@ -102,6 +101,7 @@ def download_file_to_iso(scsi_id, url):
|
||||
|
||||
def download_image(url):
|
||||
import urllib.request
|
||||
import urllib.error as error
|
||||
|
||||
file_name = url.split("/")[-1]
|
||||
full_path = base_dir + file_name
|
||||
@ -109,16 +109,19 @@ def download_image(url):
|
||||
try:
|
||||
urllib.request.urlretrieve(url, full_path)
|
||||
return {"status": True, "msg": "Downloaded the URL"}
|
||||
except (error.URLError, error.HTTPError, error.ContentTooShortError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
# TODO: Capture a more descriptive error message
|
||||
return {"status": False, "msg": "Error loading the URL"}
|
||||
|
||||
|
||||
def write_config(file_name):
|
||||
from json import dump
|
||||
file_name = base_dir + file_name
|
||||
try:
|
||||
with open(file_name, "w") as json_file:
|
||||
devices = list_devices()[0]
|
||||
devices = list_devices()["device_list"]
|
||||
for device in devices:
|
||||
# Remove keys that we don't want to store in the file
|
||||
del device["status"]
|
||||
@ -136,7 +139,9 @@ def write_config(file_name):
|
||||
device["params"] = list(device["params"])
|
||||
dump(devices, json_file, indent=4)
|
||||
return {"status": True, "msg": f"Successfully wrote to file: {file_name}"}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
except (IOError, ValueError, EOFError, TypeError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
logging.error(f"Could not write to file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not write to file: {file_name}"}
|
||||
@ -144,31 +149,61 @@ def write_config(file_name):
|
||||
|
||||
def read_config(file_name):
|
||||
from json import load
|
||||
file_name = base_dir + file_name
|
||||
try:
|
||||
with open(file_name) as json_file:
|
||||
detach_all()
|
||||
devices = load(json_file)
|
||||
for row in devices:
|
||||
process = attach_image(row["id"], device_type=row["device_type"], image=row["image"], unit=int(row["un"]), \
|
||||
process = attach_image(row["id"], device_type=row["device_type"], \
|
||||
image=row["image"], unit=int(row["un"]), \
|
||||
params=row["params"], vendor=row["vendor"], product=row["product"], \
|
||||
revision=row["revision"], block_size=row["block_size"])
|
||||
if process["status"] == True:
|
||||
return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"}
|
||||
else:
|
||||
return {"status": process["status"], "msg": process["msg"]}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
except (IOError, ValueError, EOFError, TypeError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
logging.error(f"Could not read file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not read file: {file_name}"}
|
||||
|
||||
|
||||
def read_device_config(file_name):
|
||||
def write_drive_properties(file_name, conf):
|
||||
"""
|
||||
Writes a drive property configuration file to the images dir.
|
||||
Takes file name base (str) and conf (list of dicts) as arguments
|
||||
"""
|
||||
from json import dump
|
||||
try:
|
||||
with open(base_dir + file_name, "w") as json_file:
|
||||
dump(conf, json_file, indent=4)
|
||||
return {"status": True, "msg": f"Successfully wrote to file: {file_name}"}
|
||||
except (IOError, ValueError, EOFError, TypeError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
logging.error(f"Could not write to file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not write to file: {file_name}"}
|
||||
|
||||
|
||||
|
||||
def read_drive_properties(path_name):
|
||||
"""
|
||||
Reads drive properties to any dir.
|
||||
Either ones deployed to the images dir, or the canonical database.
|
||||
Takes file path and bas (str) as argument
|
||||
"""
|
||||
from json import load
|
||||
try:
|
||||
with open(file_name) as json_file:
|
||||
with open(path_name) as json_file:
|
||||
conf = load(json_file)
|
||||
return {"status": True, "msg": f"Read data from file: {file_name}", "conf": conf}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
return {"status": True, "msg": f"Read data from file: {path_name}", "conf": conf}
|
||||
except (IOError, ValueError, EOFError, TypeError) as e:
|
||||
logging.error(str(e))
|
||||
return {"status": False, "msg": str(e)}
|
||||
except:
|
||||
logging.error(f"Could not read file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not read file: {file_name}"}
|
||||
return {"status": False, "msg": f"Could not read file: {path_name}"}
|
||||
|
@ -10,15 +10,17 @@ def rascsi_service(action):
|
||||
|
||||
|
||||
def reboot_pi():
|
||||
return subprocess.run(["sudo", "reboot"]).returncode == 0
|
||||
subprocess.Popen(["sudo", "reboot"])
|
||||
return True
|
||||
|
||||
|
||||
def shutdown_pi():
|
||||
return subprocess.run(["sudo", "shutdown", "-h", "now"]).returncode == 0
|
||||
subprocess.Popen(["sudo", "shutdown", "-h", "now"])
|
||||
return True
|
||||
|
||||
|
||||
def running_version():
|
||||
ra_web_version = (
|
||||
def running_env():
|
||||
ra_git_version = (
|
||||
subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
@ -28,13 +30,12 @@ def running_version():
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
return ra_web_version + " " + pi_version
|
||||
return {"git": ra_git_version, "env": pi_version}
|
||||
|
||||
|
||||
def is_bridge_setup():
|
||||
from subprocess import run
|
||||
process = run(["brctl", "show"], capture_output=True)
|
||||
process = subprocess.run(["brctl", "show"], capture_output=True)
|
||||
output = process.stdout.decode("utf-8")
|
||||
if "rascsi_bridge" in output:
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
@ -16,7 +16,19 @@ def get_server_info():
|
||||
str(result.server_info.patch_version)
|
||||
log_levels = result.server_info.log_levels
|
||||
current_log_level = result.server_info.current_log_level
|
||||
return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level}
|
||||
reserved_ids = list(result.server_info.reserved_ids)
|
||||
return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level, "reserved_ids": reserved_ids}
|
||||
|
||||
|
||||
def get_network_info():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.NETWORK_INTERFACES_INFO
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
ifs = result.network_interfaces_info.name
|
||||
return {"status": result.status, "ifs": ifs}
|
||||
|
||||
|
||||
def validate_scsi_id(scsi_id):
|
||||
@ -27,18 +39,24 @@ def validate_scsi_id(scsi_id):
|
||||
return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"}
|
||||
|
||||
|
||||
def get_valid_scsi_ids(devices, invalid_list, occupied_ids):
|
||||
for device in devices:
|
||||
def get_valid_scsi_ids(devices, reserved_ids):
|
||||
occupied_ids = []
|
||||
for d in devices:
|
||||
# Make it possible to insert images on top of a
|
||||
# removable media device currently without an image attached
|
||||
if "No Media" in device["status"]:
|
||||
occupied_ids.remove(device["id"])
|
||||
if d["device_type"] != "-" and "No Media" not in d["status"]:
|
||||
occupied_ids.append(d["id"])
|
||||
|
||||
# Combine lists and remove duplicates
|
||||
invalid_ids = list(set(invalid_list + occupied_ids))
|
||||
invalid_ids = list(set(reserved_ids + occupied_ids))
|
||||
valid_ids = list(range(8))
|
||||
for id in invalid_ids:
|
||||
valid_ids.remove(int(id))
|
||||
try:
|
||||
valid_ids.remove(int(id))
|
||||
except:
|
||||
# May reach this state if the RaSCSI Web UI thinks an ID
|
||||
# is reserved but RaSCSI has not actually reserved it.
|
||||
logging.warning(f"SCSI ID {id} flagged as both valid and invalid. Try restarting the RaSCSI Web UI.")
|
||||
valid_ids.reverse()
|
||||
return valid_ids
|
||||
|
||||
@ -48,7 +66,7 @@ def get_type(scsi_id):
|
||||
device.id = int(scsi_id)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICE_INFO
|
||||
command.operation = proto.PbOperation.DEVICES_INFO
|
||||
command.devices.append(device)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
@ -63,6 +81,17 @@ def get_type(scsi_id):
|
||||
|
||||
|
||||
def attach_image(scsi_id, **kwargs):
|
||||
command = proto.PbCommand()
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
|
||||
if "device_type" in kwargs.keys():
|
||||
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
|
||||
if "unit" in kwargs.keys():
|
||||
devices.unit = kwargs["unit"]
|
||||
if "image" in kwargs.keys():
|
||||
if kwargs["image"] not in [None, ""]:
|
||||
devices.params["file"] = kwargs["image"]
|
||||
|
||||
# Handling the inserting of media into an attached removable type device
|
||||
currently_attached = get_type(scsi_id)["device_type"]
|
||||
@ -72,22 +101,13 @@ def attach_image(scsi_id, **kwargs):
|
||||
if currently_attached != device_type:
|
||||
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
|
||||
else:
|
||||
return insert(scsi_id, kwargs.get("image", ""))
|
||||
command.operation = proto.PbOperation.INSERT
|
||||
# Handling attaching a new device
|
||||
else:
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
if "device_type" in kwargs.keys():
|
||||
logging.warning(kwargs["device_type"])
|
||||
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
|
||||
if "unit" in kwargs.keys():
|
||||
devices.unit = kwargs["unit"]
|
||||
if "image" in kwargs.keys():
|
||||
if kwargs["image"] not in [None, ""]:
|
||||
devices.params.append(kwargs["image"])
|
||||
if "params" in kwargs.keys():
|
||||
for p in kwargs["params"]:
|
||||
devices.params.append(p)
|
||||
command.operation = proto.PbOperation.ATTACH
|
||||
if "interfaces" in kwargs.keys():
|
||||
if kwargs["interfaces"] not in [None, ""]:
|
||||
devices.params["interfaces"] = kwargs["interfaces"]
|
||||
if "vendor" in kwargs.keys():
|
||||
if kwargs["vendor"] not in [None, ""]:
|
||||
devices.vendor = kwargs["vendor"]
|
||||
@ -101,14 +121,12 @@ def attach_image(scsi_id, **kwargs):
|
||||
if kwargs["block_size"] not in [None, ""]:
|
||||
devices.block_size = int(kwargs["block_size"])
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.ATTACH
|
||||
command.devices.append(devices)
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def detach_by_id(scsi_id):
|
||||
@ -149,40 +167,10 @@ def eject_by_id(scsi_id):
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def insert(scsi_id, image):
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
devices.params.append(image)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.INSERT
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def attach_daynaport(scsi_id):
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
devices.type = proto.PbDeviceType.SCDP
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.ATTACH
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def list_devices(scsi_id=None):
|
||||
from os import path
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICE_INFO
|
||||
command.operation = proto.PbOperation.DEVICES_INFO
|
||||
|
||||
# If method is called with scsi_id parameter, return the info on those devices
|
||||
# Otherwise, return the info on all attached devices
|
||||
@ -196,8 +184,11 @@ def list_devices(scsi_id=None):
|
||||
result.ParseFromString(data)
|
||||
|
||||
device_list = []
|
||||
occupied_ids = []
|
||||
n = 0
|
||||
|
||||
if len(result.device_info.devices) == 0:
|
||||
return {"status": False, "device_list": []}
|
||||
|
||||
while n < len(result.device_info.devices):
|
||||
did = result.device_info.devices[n].id
|
||||
dun = result.device_info.devices[n].unit
|
||||
@ -228,27 +219,34 @@ def list_devices(scsi_id=None):
|
||||
device_list.append({"id": did, "un": dun, "device_type": dtype, \
|
||||
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
|
||||
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
|
||||
occupied_ids.append(did)
|
||||
n += 1
|
||||
return device_list, occupied_ids
|
||||
|
||||
return {"status": True, "device_list": device_list}
|
||||
|
||||
|
||||
def sort_and_format_devices(device_list, occupied_ids):
|
||||
def sort_and_format_devices(devices):
|
||||
occupied_ids = []
|
||||
for d in devices:
|
||||
occupied_ids.append(d["id"])
|
||||
|
||||
formatted_devices = devices
|
||||
|
||||
# Add padding devices and sort the list
|
||||
for id in range(8):
|
||||
if id not in occupied_ids:
|
||||
device_list.append({"id": id, "type": "-", \
|
||||
formatted_devices.append({"id": id, "device_type": "-", \
|
||||
"status": "-", "file": "-", "product": "-"})
|
||||
# Sort list of devices by id
|
||||
device_list.sort(key=lambda dic: str(dic["id"]))
|
||||
formatted_devices.sort(key=lambda dic: str(dic["id"]))
|
||||
|
||||
return device_list
|
||||
return formatted_devices
|
||||
|
||||
|
||||
def reserve_scsi_ids(reserved_scsi_ids):
|
||||
'''Sends a command to the server to reserve SCSI IDs. Takes a list of strings as argument.'''
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.RESERVE
|
||||
command.params.append(reserved_scsi_ids)
|
||||
command.params["ids"] = ",".join(reserved_scsi_ids)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -257,10 +255,10 @@ def reserve_scsi_ids(reserved_scsi_ids):
|
||||
|
||||
|
||||
def set_log_level(log_level):
|
||||
'''Sends a command to the server to change the log level. Takes target log level as an argument'''
|
||||
'''Sends a command to the server to change the log level. Takes target log level as an argument.'''
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.LOG_LEVEL
|
||||
command.params.append(str(log_level))
|
||||
command.params["level"] = str(log_level)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
|
@ -1,7 +1,9 @@
|
||||
from os import getenv
|
||||
from os import getenv, getcwd
|
||||
|
||||
base_dir = getenv("BASE_DIR", "/home/pi/images/")
|
||||
DEFAULT_CONFIG = base_dir + "default.json"
|
||||
home_dir = getcwd()
|
||||
|
||||
DEFAULT_CONFIG = "default.json"
|
||||
MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
|
||||
|
||||
HARDDRIVE_FILE_SUFFIX = ("hda", "hdn", "hdi", "nhd", "hdf", "hds")
|
||||
@ -11,4 +13,9 @@ ARCHIVE_FILE_SUFFIX = ("zip",)
|
||||
VALID_FILE_SUFFIX = HARDDRIVE_FILE_SUFFIX + REMOVABLE_FILE_SUFFIX + \
|
||||
CDROM_FILE_SUFFIX + ARCHIVE_FILE_SUFFIX
|
||||
|
||||
# File containing canonical drive properties
|
||||
DRIVE_PROPERTIES_FILE = home_dir + "/drive_properties.json"
|
||||
# File ending used for drive properties files
|
||||
PROPERTIES_SUFFIX = "properties"
|
||||
|
||||
REMOVABLE_DEVICE_TYPES = ("SCCD", "SCRM", "SCMO")
|
||||
|
@ -24,7 +24,18 @@
|
||||
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
{% block header %}{% endblock %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Running</span>
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr style="background-color: black;">
|
||||
<td style="background-color: black;">
|
||||
<a href="http://github.com/akuker/RASCSI">
|
||||
<h1>RaSCSI - 68kmla Edition</h1>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flash">
|
||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||
@ -39,6 +50,9 @@
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{% block footer %}{% endblock %}
|
||||
<center><tt>RaSCSI version: <strong>{{server_info["version"]}}</strong></tt></center>
|
||||
<center><tt>RaSCSI git revision: <strong><a href="https://github.com/akuker/RASCSI/commit/{{running_env["git"]}}">{{running_env["git"][:7]}}</a></strong></tt></center>
|
||||
<center><tt>Server log level: <strong>{{server_info["current_log_level"]}}</strong></tt></center>
|
||||
<center><tt>Pi environment: {{running_env["env"]}}</tt></center>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
139
src/web/templates/drives.html
Normal file
139
src/web/templates/drives.html
Normal file
@ -0,0 +1,139 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p><a href="javascript:history.back()">Cancel</a></p>
|
||||
<h2>Disclaimer</h2>
|
||||
<p>These device profiles are provided as-is with no guarantee to work on the systems mentioned. You may need appropirate device drivers and/or configuration parameters. If you have improvement suggestions or success stories to share we would love to hear from you, so please connect with us at <a href="https://github.com/akuker/RASCSI">GitHub</a> or <a href="https://discord.gg/PyS58u6">Discord</a>!</p>
|
||||
<h2>Hard Drives</h2>
|
||||
|
||||
<table cellpadding="3" border="black">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Name</b></td>
|
||||
<td><b>Size (MB)</b></td>
|
||||
<td><b>Description</b></td>
|
||||
<td><b>Ref.</b></td>
|
||||
<td><b>Action</b></td>
|
||||
</tr>
|
||||
{% for hd in hd_conf %}
|
||||
<tr>
|
||||
<td style="text-align:center">{{hd.name}}</td>
|
||||
<td style="text-align:center">{{hd.size_mb}}</td>
|
||||
<td style="text-align:left">{{hd.description}}</td>
|
||||
<td style="text-align:left">
|
||||
{% if hd.url != "" %}
|
||||
<a href="{{hd.url}}">Link</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align:left">
|
||||
<form action="/drive/create" method="post">
|
||||
<input type="hidden" name="vendor" value="{{hd.vendor}}">
|
||||
<input type="hidden" name="product" value="{{hd.product}}">
|
||||
<input type="hidden" name="revision" value="{{hd.revision}}">
|
||||
<input type="hidden" name="blocks" value="{{hd.blocks}}">
|
||||
<input type="hidden" name="block_size" value="{{hd.block_size}}">
|
||||
<input type="hidden" name="size" value="{{hd.size}}">
|
||||
<input type="hidden" name="file_type" value="{{hd.file_type}}">
|
||||
<label for="file_name">Save as:</label>
|
||||
<input type="text" name="file_name" value="{{hd.name}}" />.{{hd.file_type}}
|
||||
<input type="submit" value="Create" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>CD-ROM Drives</h2>
|
||||
<p><em>This will create a properties file for the given CD-ROM image. No new image file will be created.</em></p>
|
||||
<table cellpadding="3" border="black">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Name</b></td>
|
||||
<td><b>Size (MB)</b></td>
|
||||
<td><b>Description</b></td>
|
||||
<td><b>Ref.</b></td>
|
||||
<td><b>Action</b></td>
|
||||
</tr>
|
||||
{% for cd in cd_conf %}
|
||||
<tr>
|
||||
<td style="text-align:center">{{cd.name}}</td>
|
||||
<td style="text-align:center">{{cd.size_mb}}</td>
|
||||
<td style="text-align:left">{{cd.description}}</td>
|
||||
<td style="text-align:left">
|
||||
{% if cd.url != "" %}
|
||||
<a href="{{cd.url}}">Link</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align:left">
|
||||
<form action="/drive/cdrom" method="post">
|
||||
<input type="hidden" name="vendor" value="{{cd.vendor}}">
|
||||
<input type="hidden" name="product" value="{{cd.product}}">
|
||||
<input type="hidden" name="revision" value="{{cd.revision}}">
|
||||
<input type="hidden" name="block_size" value="{{cd.block_size}}">
|
||||
<label for="file_name">Create for:</label>
|
||||
<select type="select" name="file_name">
|
||||
{% for f in files %}
|
||||
{% if f["name"].lower().endswith(cdrom_file_suffix) %}
|
||||
<option value="{{f["name"]}}">{{f["name"].replace(base_dir, '')}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Create" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Removable Drives</h2>
|
||||
<table cellpadding="3" border="black">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Name</b></td>
|
||||
<td><b>Size (MB)</b></td>
|
||||
<td><b>Description</b></td>
|
||||
<td><b>Ref.</b></td>
|
||||
<td><b>Action</b></td>
|
||||
</tr>
|
||||
{% for rm in rm_conf %}
|
||||
<tr>
|
||||
<td style="text-align:center">{{rm.name}}</td>
|
||||
<td style="text-align:center">{{rm.size_mb}}</td>
|
||||
<td style="text-align:left">{{rm.description}}</td>
|
||||
<td style="text-align:left">
|
||||
{% if rm.url != "" %}
|
||||
<a href="{{rm.url}}">Link</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align:left">
|
||||
<form action="/drive/create" method="post">
|
||||
<input type="hidden" name="vendor" value="{{rm.vendor}}">
|
||||
<input type="hidden" name="product" value="{{rm.product}}">
|
||||
<input type="hidden" name="revision" value="{{rm.revision}}">
|
||||
<input type="hidden" name="blocks" value="{{rm.blocks}}">
|
||||
<input type="hidden" name="block_size" value="{{rm.block_size}}">
|
||||
<input type="hidden" name="size" value="{{rm.size}}">
|
||||
<input type="hidden" name="file_type" value="{{rm.file_type}}">
|
||||
<label for="file_name">Save as:</label>
|
||||
<input type="text" name="file_name" value="{{rm.name}}" />.{{rm.file_type}}
|
||||
<input type="submit" value="Create" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -1,24 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block header %}
|
||||
{% if server_info["status"] == True %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Running</span>
|
||||
{% else %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: red; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Stopped</span>
|
||||
{% endif %}
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr style="background-color: black;">
|
||||
<td style="background-color: black;">
|
||||
<a href="http://github.com/akuker/RASCSI">
|
||||
<h1>RaSCSI - 68kmla Edition</h1>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Current RaSCSI Configuration</h2>
|
||||
<p>
|
||||
@ -55,7 +36,7 @@
|
||||
</tr>
|
||||
{% for device in devices %}
|
||||
<tr>
|
||||
{% if device["id"]|string() not in reserved_scsi_ids %}
|
||||
{% if device["id"] not in reserved_scsi_ids %}
|
||||
<td style="text-align:center">{{device.id}}</td>
|
||||
<td style="text-align:center">{{device.device_type}}</td>
|
||||
<td style="text-align:center">{{device.status}}</td>
|
||||
@ -75,8 +56,6 @@
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
<input type="submit" value="Info" />
|
||||
</form>
|
||||
{% elif device.device_type in ["-"] %}
|
||||
<div>-</div>
|
||||
{% else %}
|
||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
@ -112,33 +91,33 @@
|
||||
</tr>
|
||||
{% for file in files %}
|
||||
<tr>
|
||||
<td>{{file[0].replace(base_dir, '')}}</td>
|
||||
<td>{{file["name"].replace(base_dir, '')}}</td>
|
||||
<td style="text-align:center">
|
||||
<form action="/files/download" method="post">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="submit" value="{{file[1]}} MB ↓" />
|
||||
<input type="hidden" name="image" value="{{file["name"].replace(base_dir, '')}}">
|
||||
<input type="submit" value="{{file["size_mb"]}} MB ↓" />
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/scsi/attach" method="post">
|
||||
<input type="hidden" name="file_name" value="{{file[0]}}">
|
||||
<input type="hidden" name="file_size" value="{{file[2]}}">
|
||||
<input type="hidden" name="file_name" value="{{file["name"]}}">
|
||||
<input type="hidden" name="file_size" value="{{file["size"]}}">
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
<option value="{{id}}">{{id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if not file[0].lower().endswith(archive_file_suffix) %}
|
||||
{% if not file["name"].lower().endswith(archive_file_suffix) %}
|
||||
<input type="submit" value="Attach" />
|
||||
{% endif %}
|
||||
</form>
|
||||
<form action="/files/delete" method="post" onsubmit="return confirm('Delete file?')">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="hidden" name="image" value="{{file["name"].replace(base_dir, '')}}">
|
||||
<input type="submit" value="Delete" />
|
||||
</form>
|
||||
{% if file[0].lower().endswith(archive_file_suffix) %}
|
||||
{% if file["name"].lower().endswith(archive_file_suffix) %}
|
||||
<form action="/files/unzip" method="post">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="hidden" name="image" value="{{file["name"].replace(base_dir, '')}}">
|
||||
<input type="submit" value="Unzip" />
|
||||
</form>
|
||||
{% endif %}
|
||||
@ -151,11 +130,22 @@
|
||||
|
||||
<hr/>
|
||||
<h2>Attach Ethernet Adapter</h2>
|
||||
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link">Host drivers required.</a></p>
|
||||
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#-macintosh-setup-instructions">Host drivers required.</a></p>
|
||||
<p>Leave Interface and Static IP blank for default settings, and input only Interface for DHCP setups.</p>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/daynaport/attach" method="post">
|
||||
<label for="if">Interface:</label>
|
||||
<select name = "if">
|
||||
{% for if in netinfo["ifs"] %}
|
||||
<option value="{{if}}">{{if}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="ip">Static IP (optional):</label>
|
||||
<input type="text" name="ip" size="15" placeholder="10.10.20.1" />/
|
||||
<input type="text" name="mask" size="2" placeholder="24">
|
||||
<label for="scsi_id">ID:</label>
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
<option value="{{id}}">{{id}}</option>
|
||||
@ -236,6 +226,7 @@
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Create Empty Disk Image File</h2>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
@ -245,14 +236,13 @@
|
||||
<input type="text" placeholder="File name" name="file_name"/>
|
||||
<label for="type">Type:</label>
|
||||
<select name="type">
|
||||
<option value="hda">SCSI Hard Disk image (APPLE GENUINE) [.hda]</option>
|
||||
<option value="hds">SCSI Hard Disk image (Generic - recommended for most systems) [.hds]</option>
|
||||
<option value="hdn">SCSI Hard Disk image (NEC GENUINE) [.hdn]</option>
|
||||
<!-- Disabling due to https://github.com/akuker/RASCSI/issues/232
|
||||
<option value="hdi">SCSI Hard Disk image (Anex86 HD image) [.hdi]</option>
|
||||
<option value="nhd">SCSI Hard Disk image (T98Next HD image) [.nhd]</option> -->
|
||||
<option value="hds">SCSI Hard Disk image (Generic - recommended for Atari computers) [.hds]</option>
|
||||
<option value="hdr">SCSI Removable Media Disk image (Generic) [.hdr]</option>
|
||||
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000) [.hdf]</option>
|
||||
<option value="hdf">SASI Hard Disk image (legacy format) [.hdf]</option>
|
||||
</select>
|
||||
<label for="size">Size(MB):</label>
|
||||
<input type="number" placeholder="Size(MB)" name="size"/>
|
||||
@ -264,6 +254,11 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Create Named Drive</h2>
|
||||
<p><a href="/drive/list">Ceate a named disk image with a companion properties file that mimics real-life drives</a></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Logging</h2>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
@ -327,9 +322,3 @@
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<center><tt>RaSCSI version: <strong>{{server_info["version"]}}</strong></tt></center>
|
||||
<center><tt>Server log level: <strong>{{server_info["current_log_level"]}}</strong></tt></center>
|
||||
<center><tt>{{version}}</tt></center>
|
||||
{% endblock %}
|
||||
|
331
src/web/web.py
331
src/web/web.py
@ -10,12 +10,13 @@ from file_cmds import (
|
||||
download_image,
|
||||
write_config,
|
||||
read_config,
|
||||
read_device_config,
|
||||
write_drive_properties,
|
||||
read_drive_properties,
|
||||
)
|
||||
from pi_cmds import (
|
||||
shutdown_pi,
|
||||
reboot_pi,
|
||||
running_version,
|
||||
shutdown_pi,
|
||||
reboot_pi,
|
||||
running_env,
|
||||
rascsi_service,
|
||||
is_bridge_setup,
|
||||
)
|
||||
@ -26,10 +27,10 @@ from ractl_cmds import (
|
||||
detach_by_id,
|
||||
eject_by_id,
|
||||
get_valid_scsi_ids,
|
||||
attach_daynaport,
|
||||
detach_all,
|
||||
reserve_scsi_ids,
|
||||
get_server_info,
|
||||
get_network_info,
|
||||
validate_scsi_id,
|
||||
set_log_level,
|
||||
)
|
||||
@ -40,22 +41,30 @@ app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS")
|
||||
unsorted_devices, occupied_ids = list_devices()
|
||||
devices = sort_and_format_devices(unsorted_devices, occupied_ids)
|
||||
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids), occupied_ids)
|
||||
server_info = get_server_info()
|
||||
devices = list_devices()
|
||||
files=list_files()
|
||||
config_files=list_config_files()
|
||||
|
||||
sorted_image_files = sorted(files["files"], key = lambda x: x["name"].lower())
|
||||
sorted_config_files = sorted(config_files, key = lambda x: x.lower())
|
||||
|
||||
reserved_scsi_ids = server_info["reserved_ids"]
|
||||
formatted_devices = sort_and_format_devices(devices["device_list"])
|
||||
scsi_ids = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids)
|
||||
return render_template(
|
||||
"index.html",
|
||||
bridge_configured=is_bridge_setup(),
|
||||
devices=devices,
|
||||
files=list_files(),
|
||||
config_files=list_config_files(),
|
||||
devices=formatted_devices,
|
||||
files=sorted_image_files,
|
||||
config_files=sorted_config_files,
|
||||
base_dir=base_dir,
|
||||
scsi_ids=scsi_ids,
|
||||
reserved_scsi_ids=[reserved_scsi_ids],
|
||||
reserved_scsi_ids=reserved_scsi_ids,
|
||||
max_file_size=MAX_FILE_SIZE,
|
||||
version=running_version(),
|
||||
server_info=get_server_info(),
|
||||
running_env=running_env(),
|
||||
server_info=server_info,
|
||||
netinfo=get_network_info(),
|
||||
valid_file_suffix=VALID_FILE_SUFFIX,
|
||||
removable_device_types=REMOVABLE_DEVICE_TYPES,
|
||||
harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX,
|
||||
@ -64,44 +73,165 @@ def index():
|
||||
archive_file_suffix=ARCHIVE_FILE_SUFFIX,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/drive/list", methods=["GET"])
|
||||
def drive_list():
|
||||
"""
|
||||
Sets up the data structures and kicks off the rendering of the drive list page
|
||||
"""
|
||||
server_info = get_server_info()
|
||||
|
||||
# Reads the canonical drive properties into a dict
|
||||
# The file resides in the current dir of the web ui process
|
||||
from pathlib import Path
|
||||
drive_properties = Path(DRIVE_PROPERTIES_FILE)
|
||||
if drive_properties.is_file():
|
||||
process = read_drive_properties(str(drive_properties))
|
||||
if process["status"] == False:
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
conf = process["conf"]
|
||||
else:
|
||||
flash("Could not read drive properties from " + str(drive_properties), "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
hd_conf = []
|
||||
cd_conf = []
|
||||
rm_conf = []
|
||||
|
||||
for d in conf:
|
||||
if d["device_type"] == "SCHD":
|
||||
d["size"] = d["block_size"] * d["blocks"]
|
||||
|