mirror of
https://github.com/akuker/RASCSI.git
synced 2024-06-25 16:29:48 +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
|
driveSize=$1
|
||||||
driveName=$2
|
driveName=$2
|
||||||
mkdir -p $VIRTUAL_DRIVER_PATH
|
mkdir -p $VIRTUAL_DRIVER_PATH
|
||||||
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hda"
|
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hds"
|
||||||
|
|
||||||
if [ ! -f $drivePath ]; then
|
if [ ! -f $drivePath ]; then
|
||||||
echo "Creating a ${driveSize}MB Drive"
|
echo "Creating a ${driveSize}MB Drive"
|
||||||
|
@ -415,19 +415,6 @@ function reserveScsiIds() {
|
||||||
|
|
||||||
function runChoice() {
|
function runChoice() {
|
||||||
case $1 in
|
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)
|
1)
|
||||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface"
|
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface"
|
||||||
stopOldWebInterface
|
stopOldWebInterface
|
||||||
|
@ -501,11 +488,11 @@ function showMenu() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Choose among the following options:"
|
echo "Choose among the following options:"
|
||||||
echo "INSTALL/UPDATE RASCSI (${CONNECT_TYPE-FULLSPEC} version)"
|
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 " 2) install or update RaSCSI Service"
|
||||||
echo "CREATE EMPTY DRIVE IMAGE"
|
echo "CREATE HFS FORMATTED (MAC) IMAGE WITH LIDO DRIVERS"
|
||||||
echo " 3) 600MB drive (recommended)"
|
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 " 4) custom drive size (up to 4000MB)"
|
||||||
echo "NETWORK ASSISTANT"
|
echo "NETWORK ASSISTANT"
|
||||||
echo " 5) configure network forwarding over Ethernet (DHCP)"
|
echo " 5) configure network forwarding over Ethernet (DHCP)"
|
||||||
|
@ -531,7 +518,7 @@ while [ "$1" != "" ]; do
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
case $VALUE in
|
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\""
|
echo "ERROR: unknown option \"$VALUE\""
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||||
#include <spdlog/async.h>
|
#include <spdlog/async.h>
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#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 os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ractl_cmds import (
|
from ractl_cmds import (
|
||||||
attach_image,
|
attach_image,
|
||||||
detach_all,
|
detach_all,
|
||||||
list_devices,
|
list_devices,
|
||||||
|
send_pb_command,
|
||||||
)
|
)
|
||||||
from settings import *
|
from settings import *
|
||||||
|
import rascsi_interface_pb2 as proto
|
||||||
|
|
||||||
|
|
||||||
def list_files():
|
def list_files():
|
||||||
from fnmatch import translate
|
command = proto.PbCommand()
|
||||||
valid_file_types = list(VALID_FILE_SUFFIX)
|
command.operation = proto.PbOperation.IMAGE_FILES_INFO
|
||||||
valid_file_types = ["*." + s for s in valid_file_types]
|
|
||||||
valid_file_types = r"|".join([translate(x) for x in valid_file_types])
|
|
||||||
|
|
||||||
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 = []
|
return {"status": result.status, "msg": result.msg, "files": files}
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def list_config_files():
|
def list_config_files():
|
||||||
|
@ -47,24 +36,30 @@ def list_config_files():
|
||||||
return files_list
|
return files_list
|
||||||
|
|
||||||
|
|
||||||
def create_new_image(file_name, type, size):
|
def create_new_image(file_name, file_type, size):
|
||||||
if file_name == "":
|
command = proto.PbCommand()
|
||||||
file_name = "new_image." + str(int(time.time())) + "." + type
|
command.operation = proto.PbOperation.CREATE_IMAGE
|
||||||
else:
|
|
||||||
file_name = file_name + "." + type
|
|
||||||
|
|
||||||
return subprocess.run(
|
command.params["file"] = file_name + "." + file_type
|
||||||
["truncate", "--size", f"{size}m", f"{base_dir}{file_name}"],
|
command.params["size"] = str(size)
|
||||||
capture_output=True,
|
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):
|
def delete_file(file_name):
|
||||||
if os.path.exists(file_name):
|
command = proto.PbCommand()
|
||||||
os.remove(file_name)
|
command.operation = proto.PbOperation.DELETE_IMAGE
|
||||||
return True
|
|
||||||
else:
|
command.params["file"] = file_name
|
||||||
return False
|
|
||||||
|
data = send_pb_command(command.SerializeToString())
|
||||||
|
result = proto.PbResult()
|
||||||
|
result.ParseFromString(data)
|
||||||
|
return {"status": result.status, "msg": result.msg}
|
||||||
|
|
||||||
|
|
||||||
def unzip_file(file_name):
|
def unzip_file(file_name):
|
||||||
|
@ -77,6 +72,8 @@ def unzip_file(file_name):
|
||||||
|
|
||||||
def download_file_to_iso(scsi_id, url):
|
def download_file_to_iso(scsi_id, url):
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import urllib.error as error
|
||||||
|
import time
|
||||||
|
|
||||||
file_name = url.split("/")[-1]
|
file_name = url.split("/")[-1]
|
||||||
tmp_ts = int(time.time())
|
tmp_ts = int(time.time())
|
||||||
|
@ -87,8 +84,10 @@ def download_file_to_iso(scsi_id, url):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
urllib.request.urlretrieve(url, tmp_full_path)
|
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:
|
except:
|
||||||
# TODO: Capture a more descriptive error message
|
|
||||||
return {"status": False, "msg": "Error loading the URL"}
|
return {"status": False, "msg": "Error loading the URL"}
|
||||||
|
|
||||||
# iso_filename = make_cd(tmp_full_path, None, None) # not working yet
|
# 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):
|
def download_image(url):
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import urllib.error as error
|
||||||
|
|
||||||
file_name = url.split("/")[-1]
|
file_name = url.split("/")[-1]
|
||||||
full_path = base_dir + file_name
|
full_path = base_dir + file_name
|
||||||
|
@ -109,16 +109,19 @@ def download_image(url):
|
||||||
try:
|
try:
|
||||||
urllib.request.urlretrieve(url, full_path)
|
urllib.request.urlretrieve(url, full_path)
|
||||||
return {"status": True, "msg": "Downloaded the URL"}
|
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:
|
except:
|
||||||
# TODO: Capture a more descriptive error message
|
|
||||||
return {"status": False, "msg": "Error loading the URL"}
|
return {"status": False, "msg": "Error loading the URL"}
|
||||||
|
|
||||||
|
|
||||||
def write_config(file_name):
|
def write_config(file_name):
|
||||||
from json import dump
|
from json import dump
|
||||||
|
file_name = base_dir + file_name
|
||||||
try:
|
try:
|
||||||
with open(file_name, "w") as json_file:
|
with open(file_name, "w") as json_file:
|
||||||
devices = list_devices()[0]
|
devices = list_devices()["device_list"]
|
||||||
for device in devices:
|
for device in devices:
|
||||||
# Remove keys that we don't want to store in the file
|
# Remove keys that we don't want to store in the file
|
||||||
del device["status"]
|
del device["status"]
|
||||||
|
@ -136,7 +139,9 @@ def write_config(file_name):
|
||||||
device["params"] = list(device["params"])
|
device["params"] = list(device["params"])
|
||||||
dump(devices, json_file, indent=4)
|
dump(devices, json_file, indent=4)
|
||||||
return {"status": True, "msg": f"Successfully wrote to file: {file_name}"}
|
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:
|
except:
|
||||||
logging.error(f"Could not write to file: {file_name}")
|
logging.error(f"Could not write to file: {file_name}")
|
||||||
return {"status": False, "msg": 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):
|
def read_config(file_name):
|
||||||
from json import load
|
from json import load
|
||||||
|
file_name = base_dir + file_name
|
||||||
try:
|
try:
|
||||||
with open(file_name) as json_file:
|
with open(file_name) as json_file:
|
||||||
detach_all()
|
detach_all()
|
||||||
devices = load(json_file)
|
devices = load(json_file)
|
||||||
for row in devices:
|
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"], \
|
params=row["params"], vendor=row["vendor"], product=row["product"], \
|
||||||
revision=row["revision"], block_size=row["block_size"])
|
revision=row["revision"], block_size=row["block_size"])
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"}
|
return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"}
|
||||||
else:
|
else:
|
||||||
return {"status": process["status"], "msg": process["msg"]}
|
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:
|
except:
|
||||||
logging.error(f"Could not read file: {file_name}")
|
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: {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
|
from json import load
|
||||||
try:
|
try:
|
||||||
with open(file_name) as json_file:
|
with open(path_name) as json_file:
|
||||||
conf = load(json_file)
|
conf = load(json_file)
|
||||||
return {"status": True, "msg": f"Read data from file: {file_name}", "conf": conf}
|
return {"status": True, "msg": f"Read data from file: {path_name}", "conf": conf}
|
||||||
#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:
|
except:
|
||||||
logging.error(f"Could not read file: {file_name}")
|
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():
|
def reboot_pi():
|
||||||
return subprocess.run(["sudo", "reboot"]).returncode == 0
|
subprocess.Popen(["sudo", "reboot"])
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def shutdown_pi():
|
def shutdown_pi():
|
||||||
return subprocess.run(["sudo", "shutdown", "-h", "now"]).returncode == 0
|
subprocess.Popen(["sudo", "shutdown", "-h", "now"])
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def running_version():
|
def running_env():
|
||||||
ra_web_version = (
|
ra_git_version = (
|
||||||
subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True)
|
subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True)
|
||||||
.stdout.decode("utf-8")
|
.stdout.decode("utf-8")
|
||||||
.strip()
|
.strip()
|
||||||
|
@ -28,12 +30,11 @@ def running_version():
|
||||||
.stdout.decode("utf-8")
|
.stdout.decode("utf-8")
|
||||||
.strip()
|
.strip()
|
||||||
)
|
)
|
||||||
return ra_web_version + " " + pi_version
|
return {"git": ra_git_version, "env": pi_version}
|
||||||
|
|
||||||
|
|
||||||
def is_bridge_setup():
|
def is_bridge_setup():
|
||||||
from subprocess import run
|
process = subprocess.run(["brctl", "show"], capture_output=True)
|
||||||
process = run(["brctl", "show"], capture_output=True)
|
|
||||||
output = process.stdout.decode("utf-8")
|
output = process.stdout.decode("utf-8")
|
||||||
if "rascsi_bridge" in output:
|
if "rascsi_bridge" in output:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -16,7 +16,19 @@ def get_server_info():
|
||||||
str(result.server_info.patch_version)
|
str(result.server_info.patch_version)
|
||||||
log_levels = result.server_info.log_levels
|
log_levels = result.server_info.log_levels
|
||||||
current_log_level = result.server_info.current_log_level
|
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):
|
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"}
|
return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"}
|
||||||
|
|
||||||
|
|
||||||
def get_valid_scsi_ids(devices, invalid_list, occupied_ids):
|
def get_valid_scsi_ids(devices, reserved_ids):
|
||||||
for device in devices:
|
occupied_ids = []
|
||||||
|
for d in devices:
|
||||||
# Make it possible to insert images on top of a
|
# Make it possible to insert images on top of a
|
||||||
# removable media device currently without an image attached
|
# removable media device currently without an image attached
|
||||||
if "No Media" in device["status"]:
|
if d["device_type"] != "-" and "No Media" not in d["status"]:
|
||||||
occupied_ids.remove(device["id"])
|
occupied_ids.append(d["id"])
|
||||||
|
|
||||||
# Combine lists and remove duplicates
|
# 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))
|
valid_ids = list(range(8))
|
||||||
for id in invalid_ids:
|
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()
|
valid_ids.reverse()
|
||||||
return valid_ids
|
return valid_ids
|
||||||
|
|
||||||
|
@ -48,7 +66,7 @@ def get_type(scsi_id):
|
||||||
device.id = int(scsi_id)
|
device.id = int(scsi_id)
|
||||||
|
|
||||||
command = proto.PbCommand()
|
command = proto.PbCommand()
|
||||||
command.operation = proto.PbOperation.DEVICE_INFO
|
command.operation = proto.PbOperation.DEVICES_INFO
|
||||||
command.devices.append(device)
|
command.devices.append(device)
|
||||||
|
|
||||||
data = send_pb_command(command.SerializeToString())
|
data = send_pb_command(command.SerializeToString())
|
||||||
|
@ -63,6 +81,17 @@ def get_type(scsi_id):
|
||||||
|
|
||||||
|
|
||||||
def attach_image(scsi_id, **kwargs):
|
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
|
# Handling the inserting of media into an attached removable type device
|
||||||
currently_attached = get_type(scsi_id)["device_type"]
|
currently_attached = get_type(scsi_id)["device_type"]
|
||||||
|
@ -72,22 +101,13 @@ def attach_image(scsi_id, **kwargs):
|
||||||
if currently_attached != device_type:
|
if currently_attached != device_type:
|
||||||
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
|
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
|
||||||
else:
|
else:
|
||||||
return insert(scsi_id, kwargs.get("image", ""))
|
command.operation = proto.PbOperation.INSERT
|
||||||
# Handling attaching a new device
|
# Handling attaching a new device
|
||||||
else:
|
else:
|
||||||
devices = proto.PbDeviceDefinition()
|
command.operation = proto.PbOperation.ATTACH
|
||||||
devices.id = int(scsi_id)
|
if "interfaces" in kwargs.keys():
|
||||||
if "device_type" in kwargs.keys():
|
if kwargs["interfaces"] not in [None, ""]:
|
||||||
logging.warning(kwargs["device_type"])
|
devices.params["interfaces"] = kwargs["interfaces"]
|
||||||
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)
|
|
||||||
if "vendor" in kwargs.keys():
|
if "vendor" in kwargs.keys():
|
||||||
if kwargs["vendor"] not in [None, ""]:
|
if kwargs["vendor"] not in [None, ""]:
|
||||||
devices.vendor = kwargs["vendor"]
|
devices.vendor = kwargs["vendor"]
|
||||||
|
@ -101,14 +121,12 @@ def attach_image(scsi_id, **kwargs):
|
||||||
if kwargs["block_size"] not in [None, ""]:
|
if kwargs["block_size"] not in [None, ""]:
|
||||||
devices.block_size = int(kwargs["block_size"])
|
devices.block_size = int(kwargs["block_size"])
|
||||||
|
|
||||||
command = proto.PbCommand()
|
command.devices.append(devices)
|
||||||
command.operation = proto.PbOperation.ATTACH
|
|
||||||
command.devices.append(devices)
|
|
||||||
|
|
||||||
data = send_pb_command(command.SerializeToString())
|
data = send_pb_command(command.SerializeToString())
|
||||||
result = proto.PbResult()
|
result = proto.PbResult()
|
||||||
result.ParseFromString(data)
|
result.ParseFromString(data)
|
||||||
return {"status": result.status, "msg": result.msg}
|
return {"status": result.status, "msg": result.msg}
|
||||||
|
|
||||||
|
|
||||||
def detach_by_id(scsi_id):
|
def detach_by_id(scsi_id):
|
||||||
|
@ -149,40 +167,10 @@ def eject_by_id(scsi_id):
|
||||||
return {"status": result.status, "msg": result.msg}
|
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):
|
def list_devices(scsi_id=None):
|
||||||
from os import path
|
from os import path
|
||||||
command = proto.PbCommand()
|
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
|
# If method is called with scsi_id parameter, return the info on those devices
|
||||||
# Otherwise, return the info on all attached devices
|
# Otherwise, return the info on all attached devices
|
||||||
|
@ -196,8 +184,11 @@ def list_devices(scsi_id=None):
|
||||||
result.ParseFromString(data)
|
result.ParseFromString(data)
|
||||||
|
|
||||||
device_list = []
|
device_list = []
|
||||||
occupied_ids = []
|
|
||||||
n = 0
|
n = 0
|
||||||
|
|
||||||
|
if len(result.device_info.devices) == 0:
|
||||||
|
return {"status": False, "device_list": []}
|
||||||
|
|
||||||
while n < len(result.device_info.devices):
|
while n < len(result.device_info.devices):
|
||||||
did = result.device_info.devices[n].id
|
did = result.device_info.devices[n].id
|
||||||
dun = result.device_info.devices[n].unit
|
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, \
|
device_list.append({"id": did, "un": dun, "device_type": dtype, \
|
||||||
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
|
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
|
||||||
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
|
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
|
||||||
occupied_ids.append(did)
|
|
||||||
n += 1
|
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
|
# Add padding devices and sort the list
|
||||||
for id in range(8):
|
for id in range(8):
|
||||||
if id not in occupied_ids:
|
if id not in occupied_ids:
|
||||||
device_list.append({"id": id, "type": "-", \
|
formatted_devices.append({"id": id, "device_type": "-", \
|
||||||
"status": "-", "file": "-", "product": "-"})
|
"status": "-", "file": "-", "product": "-"})
|
||||||
# Sort list of devices by id
|
# 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):
|
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 = proto.PbCommand()
|
||||||
command.operation = proto.PbOperation.RESERVE
|
command.operation = proto.PbOperation.RESERVE
|
||||||
command.params.append(reserved_scsi_ids)
|
command.params["ids"] = ",".join(reserved_scsi_ids)
|
||||||
|
|
||||||
data = send_pb_command(command.SerializeToString())
|
data = send_pb_command(command.SerializeToString())
|
||||||
result = proto.PbResult()
|
result = proto.PbResult()
|
||||||
|
@ -257,10 +255,10 @@ def reserve_scsi_ids(reserved_scsi_ids):
|
||||||
|
|
||||||
|
|
||||||
def set_log_level(log_level):
|
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 = proto.PbCommand()
|
||||||
command.operation = proto.PbOperation.LOG_LEVEL
|
command.operation = proto.PbOperation.LOG_LEVEL
|
||||||
command.params.append(str(log_level))
|
command.params["level"] = str(log_level)
|
||||||
|
|
||||||
data = send_pb_command(command.SerializeToString())
|
data = send_pb_command(command.SerializeToString())
|
||||||
result = proto.PbResult()
|
result = proto.PbResult()
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from os import getenv
|
from os import getenv, getcwd
|
||||||
|
|
||||||
base_dir = getenv("BASE_DIR", "/home/pi/images/")
|
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
|
MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
|
||||||
|
|
||||||
HARDDRIVE_FILE_SUFFIX = ("hda", "hdn", "hdi", "nhd", "hdf", "hds")
|
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 + \
|
VALID_FILE_SUFFIX = HARDDRIVE_FILE_SUFFIX + REMOVABLE_FILE_SUFFIX + \
|
||||||
CDROM_FILE_SUFFIX + ARCHIVE_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")
|
REMOVABLE_DEVICE_TYPES = ("SCCD", "SCRM", "SCMO")
|
||||||
|
|
|
@ -24,7 +24,18 @@
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">
|
<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>
|
||||||
<div class="flash">
|
<div class="flash">
|
||||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||||
|
@ -39,6 +50,9 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<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>
|
</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" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<h2>Current RaSCSI Configuration</h2>
|
<h2>Current RaSCSI Configuration</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -55,7 +36,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% for device in devices %}
|
{% for device in devices %}
|
||||||
<tr>
|
<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.id}}</td>
|
||||||
<td style="text-align:center">{{device.device_type}}</td>
|
<td style="text-align:center">{{device.device_type}}</td>
|
||||||
<td style="text-align:center">{{device.status}}</td>
|
<td style="text-align:center">{{device.status}}</td>
|
||||||
|
@ -75,8 +56,6 @@
|
||||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||||
<input type="submit" value="Info" />
|
<input type="submit" value="Info" />
|
||||||
</form>
|
</form>
|
||||||
{% elif device.device_type in ["-"] %}
|
|
||||||
<div>-</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
|
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
|
||||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||||
|
@ -112,33 +91,33 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% for file in files %}
|
{% for file in files %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{file[0].replace(base_dir, '')}}</td>
|
<td>{{file["name"].replace(base_dir, '')}}</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<form action="/files/download" method="post">
|
<form action="/files/download" 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="{{file[1]}} MB ↓" />
|
<input type="submit" value="{{file["size_mb"]}} MB ↓" />
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="/scsi/attach" method="post">
|
<form action="/scsi/attach" method="post">
|
||||||
<input type="hidden" name="file_name" value="{{file[0]}}">
|
<input type="hidden" name="file_name" value="{{file["name"]}}">
|
||||||
<input type="hidden" name="file_size" value="{{file[2]}}">
|
<input type="hidden" name="file_size" value="{{file["size"]}}">
|
||||||
<select name="scsi_id">
|
<select name="scsi_id">
|
||||||
{% for id in scsi_ids %}
|
{% for id in scsi_ids %}
|
||||||
<option value="{{id}}">{{id}}</option>
|
<option value="{{id}}">{{id}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if not file[0].lower().endswith(archive_file_suffix) %}
|
{% if not file["name"].lower().endswith(archive_file_suffix) %}
|
||||||
<input type="submit" value="Attach" />
|
<input type="submit" value="Attach" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
<form action="/files/delete" method="post" onsubmit="return confirm('Delete file?')">
|
<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" />
|
<input type="submit" value="Delete" />
|
||||||
</form>
|
</form>
|
||||||
{% if file[0].lower().endswith(archive_file_suffix) %}
|
{% if file["name"].lower().endswith(archive_file_suffix) %}
|
||||||
<form action="/files/unzip" method="post">
|
<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" />
|
<input type="submit" value="Unzip" />
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -151,11 +130,22 @@
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<h2>Attach Ethernet Adapter</h2>
|
<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">
|
<table style="border: none">
|
||||||
<tr style="border: none">
|
<tr style="border: none">
|
||||||
<td style="border: none; vertical-align:top;">
|
<td style="border: none; vertical-align:top;">
|
||||||
<form action="/daynaport/attach" method="post">
|
<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">
|
<select name="scsi_id">
|
||||||
{% for id in scsi_ids %}
|
{% for id in scsi_ids %}
|
||||||
<option value="{{id}}">{{id}}</option>
|
<option value="{{id}}">{{id}}</option>
|
||||||
|
@ -236,6 +226,7 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<h2>Create Empty Disk Image File</h2>
|
<h2>Create Empty Disk Image File</h2>
|
||||||
<table style="border: none">
|
<table style="border: none">
|
||||||
<tr style="border: none">
|
<tr style="border: none">
|
||||||
|
@ -245,14 +236,13 @@
|
||||||
<input type="text" placeholder="File name" name="file_name"/>
|
<input type="text" placeholder="File name" name="file_name"/>
|
||||||
<label for="type">Type:</label>
|
<label for="type">Type:</label>
|
||||||
<select name="type">
|
<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>
|
<option value="hdn">SCSI Hard Disk image (NEC GENUINE) [.hdn]</option>
|
||||||
<!-- Disabling due to https://github.com/akuker/RASCSI/issues/232
|
<!-- Disabling due to https://github.com/akuker/RASCSI/issues/232
|
||||||
<option value="hdi">SCSI Hard Disk image (Anex86 HD image) [.hdi]</option>
|
<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="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="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>
|
</select>
|
||||||
<label for="size">Size(MB):</label>
|
<label for="size">Size(MB):</label>
|
||||||
<input type="number" placeholder="Size(MB)" name="size"/>
|
<input type="number" placeholder="Size(MB)" name="size"/>
|
||||||
|
@ -264,6 +254,11 @@
|
||||||
|
|
||||||
<hr/>
|
<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>
|
<h2>Logging</h2>
|
||||||
<table style="border: none">
|
<table style="border: none">
|
||||||
<tr style="border: none">
|
<tr style="border: none">
|
||||||
|
@ -327,9 +322,3 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% 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 %}
|
|
||||||
|
|
327
src/web/web.py
327
src/web/web.py
|
@ -10,12 +10,13 @@ from file_cmds import (
|
||||||
download_image,
|
download_image,
|
||||||
write_config,
|
write_config,
|
||||||
read_config,
|
read_config,
|
||||||
read_device_config,
|
write_drive_properties,
|
||||||
|
read_drive_properties,
|
||||||
)
|
)
|
||||||
from pi_cmds import (
|
from pi_cmds import (
|
||||||
shutdown_pi,
|
shutdown_pi,
|
||||||
reboot_pi,
|
reboot_pi,
|
||||||
running_version,
|
running_env,
|
||||||
rascsi_service,
|
rascsi_service,
|
||||||
is_bridge_setup,
|
is_bridge_setup,
|
||||||
)
|
)
|
||||||
|
@ -26,10 +27,10 @@ from ractl_cmds import (
|
||||||
detach_by_id,
|
detach_by_id,
|
||||||
eject_by_id,
|
eject_by_id,
|
||||||
get_valid_scsi_ids,
|
get_valid_scsi_ids,
|
||||||
attach_daynaport,
|
|
||||||
detach_all,
|
detach_all,
|
||||||
reserve_scsi_ids,
|
reserve_scsi_ids,
|
||||||
get_server_info,
|
get_server_info,
|
||||||
|
get_network_info,
|
||||||
validate_scsi_id,
|
validate_scsi_id,
|
||||||
set_log_level,
|
set_log_level,
|
||||||
)
|
)
|
||||||
|
@ -40,22 +41,30 @@ app = Flask(__name__)
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS")
|
server_info = get_server_info()
|
||||||
unsorted_devices, occupied_ids = list_devices()
|
devices = list_devices()
|
||||||
devices = sort_and_format_devices(unsorted_devices, occupied_ids)
|
files=list_files()
|
||||||
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids), occupied_ids)
|
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(
|
return render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
bridge_configured=is_bridge_setup(),
|
bridge_configured=is_bridge_setup(),
|
||||||
devices=devices,
|
devices=formatted_devices,
|
||||||
files=list_files(),
|
files=sorted_image_files,
|
||||||
config_files=list_config_files(),
|
config_files=sorted_config_files,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
scsi_ids=scsi_ids,
|
scsi_ids=scsi_ids,
|
||||||
reserved_scsi_ids=[reserved_scsi_ids],
|
reserved_scsi_ids=reserved_scsi_ids,
|
||||||
max_file_size=MAX_FILE_SIZE,
|
max_file_size=MAX_FILE_SIZE,
|
||||||
version=running_version(),
|
running_env=running_env(),
|
||||||
server_info=get_server_info(),
|
server_info=server_info,
|
||||||
|
netinfo=get_network_info(),
|
||||||
valid_file_suffix=VALID_FILE_SUFFIX,
|
valid_file_suffix=VALID_FILE_SUFFIX,
|
||||||
removable_device_types=REMOVABLE_DEVICE_TYPES,
|
removable_device_types=REMOVABLE_DEVICE_TYPES,
|
||||||
harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX,
|
harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX,
|
||||||
|
@ -64,44 +73,165 @@ def index():
|
||||||
archive_file_suffix=ARCHIVE_FILE_SUFFIX,
|
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"]
|
||||||
|
d["size_mb"] = "{:,.2f}".format(d["size"] / 1024 / 1024)
|
||||||
|
hd_conf.append(d)
|
||||||
|
elif d["device_type"] == "SCCD":
|
||||||
|
d["size_mb"] = "N/A"
|
||||||
|
cd_conf.append(d)
|
||||||
|
elif d["device_type"] == "SCRM":
|
||||||
|
d["size"] = d["block_size"] * d["blocks"]
|
||||||
|
d["size_mb"] = "{:,.2f}".format(d["size"] / 1024 / 1024)
|
||||||
|
rm_conf.append(d)
|
||||||
|
|
||||||
|
files=list_files()
|
||||||
|
sorted_image_files = sorted(files["files"], key = lambda x: x["name"].lower())
|
||||||
|
hd_conf = sorted(hd_conf, key = lambda x: x["name"].lower())
|
||||||
|
cd_conf = sorted(cd_conf, key = lambda x: x["name"].lower())
|
||||||
|
rm_conf = sorted(rm_conf, key = lambda x: x["name"].lower())
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"drives.html",
|
||||||
|
files=sorted_image_files,
|
||||||
|
base_dir=base_dir,
|
||||||
|
hd_conf=hd_conf,
|
||||||
|
cd_conf=cd_conf,
|
||||||
|
rm_conf=rm_conf,
|
||||||
|
running_env=running_env(),
|
||||||
|
server_info=server_info,
|
||||||
|
cdrom_file_suffix=CDROM_FILE_SUFFIX,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/pwa/<path:path>')
|
@app.route('/pwa/<path:path>')
|
||||||
def send_pwa_files(path):
|
def send_pwa_files(path):
|
||||||
return send_from_directory('pwa', path)
|
return send_from_directory('pwa', path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/drive/create", methods=["POST"])
|
||||||
|
def drive_create():
|
||||||
|
vendor = request.form.get("vendor")
|
||||||
|
product = request.form.get("product")
|
||||||
|
revision = request.form.get("revision")
|
||||||
|
blocks = request.form.get("blocks")
|
||||||
|
block_size = request.form.get("block_size")
|
||||||
|
size = request.form.get("size")
|
||||||
|
file_type = request.form.get("file_type")
|
||||||
|
file_name = request.form.get("file_name")
|
||||||
|
|
||||||
|
# Creating the image file
|
||||||
|
process = create_new_image(file_name, file_type, size)
|
||||||
|
if process["status"] == True:
|
||||||
|
flash(f"Drive image file {file_name}.{file_type} created")
|
||||||
|
flash(process["msg"])
|
||||||
|
else:
|
||||||
|
flash(f"Failed to create file {file_name}.{file_type}", "error")
|
||||||
|
flash(process["msg"], "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
# Creating the drive properties file
|
||||||
|
from pathlib import Path
|
||||||
|
file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX
|
||||||
|
properties = {"vendor": vendor, "product": product, "revision": revision, \
|
||||||
|
"blocks": blocks, "block_size": block_size}
|
||||||
|
process = write_drive_properties(file_name, properties)
|
||||||
|
if process["status"] == True:
|
||||||
|
flash(f"Drive properties file {file_name} created")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
else:
|
||||||
|
flash(f"Failed to create drive properties file {file_name}", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/drive/cdrom", methods=["POST"])
|
||||||
|
def drive_cdrom():
|
||||||
|
vendor = request.form.get("vendor")
|
||||||
|
product = request.form.get("product")
|
||||||
|
revision = request.form.get("revision")
|
||||||
|
block_size = request.form.get("block_size")
|
||||||
|
file_name = request.form.get("file_name")
|
||||||
|
|
||||||
|
# Creating the drive properties file
|
||||||
|
from pathlib import Path
|
||||||
|
file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX
|
||||||
|
properties = {"vendor": vendor, "product": product, "revision": revision, "block_size": block_size}
|
||||||
|
process = write_drive_properties(file_name, properties)
|
||||||
|
if process["status"] == True:
|
||||||
|
flash(f"Drive properties file {file_name} created")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
else:
|
||||||
|
flash(f"Failed to create drive properties file {file_name}", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/config/save", methods=["POST"])
|
@app.route("/config/save", methods=["POST"])
|
||||||
def config_save():
|
def config_save():
|
||||||
file_name = request.form.get("name") or "default"
|
file_name = request.form.get("name") or "default"
|
||||||
file_name = f"{base_dir}{file_name}.json"
|
file_name = f"{file_name}.json"
|
||||||
|
|
||||||
process = write_config(file_name)
|
process = write_config(file_name)
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
flash(f"Saved config to {file_name}!")
|
flash(f"Saved config to {file_name}!")
|
||||||
|
flash(process["msg"])
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
else:
|
else:
|
||||||
flash(f"Failed to saved config to {file_name}!", "error")
|
flash(f"Failed to saved config to {file_name}!", "error")
|
||||||
flash(f"{process['msg']}", "error")
|
flash(process['msg'], "error")
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/config/load", methods=["POST"])
|
@app.route("/config/load", methods=["POST"])
|
||||||
def config_load():
|
def config_load():
|
||||||
file_name = request.form.get("name")
|
file_name = request.form.get("name")
|
||||||
file_name = f"{base_dir}{file_name}"
|
|
||||||
|
|
||||||
if "load" in request.form:
|
if "load" in request.form:
|
||||||
process = read_config(file_name)
|
process = read_config(file_name)
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
flash(f"Loaded config from {file_name}!")
|
flash(f"Loaded config from {file_name}!")
|
||||||
|
flash(process["msg"])
|
||||||
|
return redirect(url_for("index"))
|
||||||
else:
|
else:
|
||||||
flash(f"Failed to load {file_name}!", "error")
|
flash(f"Failed to load {file_name}!", "error")
|
||||||
flash(f"{process['msg']}", "error")
|
flash(process['msg'], "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
elif "delete" in request.form:
|
elif "delete" in request.form:
|
||||||
if delete_file(file_name):
|
process = delete_file(file_name)
|
||||||
flash(f"Deleted config {file_name}!")
|
if process["status"] == True:
|
||||||
|
flash(f"Deleted config {file_name}!")
|
||||||
|
flash(process["msg"])
|
||||||
|
return redirect(url_for("index"))
|
||||||
else:
|
else:
|
||||||
flash(f"Failed to delete {file_name}!", "error")
|
flash(f"Failed to delete {file_name}!", "error")
|
||||||
|
flash(process['msg'], "error")
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/logs/show", methods=["POST"])
|
@app.route("/logs/show", methods=["POST"])
|
||||||
|
@ -142,13 +272,23 @@ def log_level():
|
||||||
@app.route("/daynaport/attach", methods=["POST"])
|
@app.route("/daynaport/attach", methods=["POST"])
|
||||||
def daynaport_attach():
|
def daynaport_attach():
|
||||||
scsi_id = request.form.get("scsi_id")
|
scsi_id = request.form.get("scsi_id")
|
||||||
|
interface = request.form.get("if")
|
||||||
|
ip = request.form.get("ip")
|
||||||
|
mask = request.form.get("mask")
|
||||||
|
|
||||||
|
kwargs = {"device_type": "SCDP"}
|
||||||
|
if interface != "":
|
||||||
|
arg = interface
|
||||||
|
if "" not in (ip, mask):
|
||||||
|
arg += (":" + ip + "/" + mask)
|
||||||
|
kwargs["interfaces"] = arg
|
||||||
|
|
||||||
validate = validate_scsi_id(scsi_id)
|
validate = validate_scsi_id(scsi_id)
|
||||||
if validate["status"] == False:
|
if validate["status"] == False:
|
||||||
flash(validate["msg"], "error")
|
flash(validate["msg"], "error")
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
process = attach_daynaport(scsi_id)
|
process = attach_image(scsi_id, **kwargs)
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
flash(f"Attached DaynaPORT to SCSI id {scsi_id}!")
|
flash(f"Attached DaynaPORT to SCSI id {scsi_id}!")
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
@ -171,34 +311,38 @@ def attach():
|
||||||
|
|
||||||
kwargs = {"image": file_name}
|
kwargs = {"image": file_name}
|
||||||
|
|
||||||
# Attempt to load the device config sidecar file:
|
# Validate image type by file name suffix
|
||||||
# same base path but .rascsi instead of the original suffix.
|
if file_name.lower().endswith(CDROM_FILE_SUFFIX):
|
||||||
from pathlib import Path
|
|
||||||
device_config = Path(base_dir + str(Path(file_name).stem) + ".rascsi")
|
|
||||||
if device_config.is_file():
|
|
||||||
process = read_device_config(device_config)
|
|
||||||
if process["status"] == False:
|
|
||||||
flash(process["msg"], "error")
|
|
||||||
return redirect(url_for("index"))
|
|
||||||
conf = process["conf"]
|
|
||||||
conf_file_size = conf["blocks"] * conf["block_size"]
|
|
||||||
if conf_file_size != 0 and conf_file_size > int(file_size):
|
|
||||||
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
|
|
||||||
flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error")
|
|
||||||
return redirect(url_for("index"))
|
|
||||||
kwargs["device_type"] = conf["device_type"]
|
|
||||||
kwargs["vendor"] = conf["vendor"]
|
|
||||||
kwargs["product"] = conf["product"]
|
|
||||||
kwargs["revision"] = conf["revision"]
|
|
||||||
kwargs["block_size"] = conf["block_size"]
|
|
||||||
# Validate image type by file name suffix as fallback
|
|
||||||
elif file_name.lower().endswith(CDROM_FILE_SUFFIX):
|
|
||||||
kwargs["device_type"] = "SCCD"
|
kwargs["device_type"] = "SCCD"
|
||||||
elif file_name.lower().endswith(REMOVABLE_FILE_SUFFIX):
|
elif file_name.lower().endswith(REMOVABLE_FILE_SUFFIX):
|
||||||
kwargs["device_type"] = "SCRM"
|
kwargs["device_type"] = "SCRM"
|
||||||
elif file_name.lower().endswith(HARDDRIVE_FILE_SUFFIX):
|
elif file_name.lower().endswith(HARDDRIVE_FILE_SUFFIX):
|
||||||
kwargs["device_type"] = "SCHD"
|
kwargs["device_type"] = "SCHD"
|
||||||
|
|
||||||
|
# Attempt to load the device properties file:
|
||||||
|
# same base path but PROPERTIES_SUFFIX instead of the original suffix.
|
||||||
|
from pathlib import Path
|
||||||
|
file_name_base = str(Path(file_name).stem)
|
||||||
|
drive_properties = Path(base_dir + file_name_base + "." + PROPERTIES_SUFFIX)
|
||||||
|
if drive_properties.is_file():
|
||||||
|
process = read_drive_properties(str(drive_properties))
|
||||||
|
if process["status"] == False:
|
||||||
|
flash(f"Failed to load the device properties file {file_name_base}.{PROPERTIES_SUFFIX}", "error")
|
||||||
|
flash(process["msg"], "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
conf = process["conf"]
|
||||||
|
# CD-ROM drives have no inherent size, so bypass the size check
|
||||||
|
if kwargs["device_type"] != "SCCD":
|
||||||
|
conf_file_size = int(conf["blocks"]) * int(conf["block_size"])
|
||||||
|
if conf_file_size != 0 and conf_file_size > int(file_size):
|
||||||
|
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
|
||||||
|
flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
kwargs["vendor"] = conf["vendor"]
|
||||||
|
kwargs["product"] = conf["product"]
|
||||||
|
kwargs["revision"] = conf["revision"]
|
||||||
|
kwargs["block_size"] = conf["block_size"]
|
||||||
|
|
||||||
process = attach_image(scsi_id, **kwargs)
|
process = attach_image(scsi_id, **kwargs)
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
flash(f"Attached {file_name} to SCSI id {scsi_id}!")
|
flash(f"Attached {file_name} to SCSI id {scsi_id}!")
|
||||||
|
@ -249,8 +393,16 @@ def eject():
|
||||||
@app.route("/scsi/info", methods=["POST"])
|
@app.route("/scsi/info", methods=["POST"])
|
||||||
def device_info():
|
def device_info():
|
||||||
scsi_id = request.form.get("scsi_id")
|
scsi_id = request.form.get("scsi_id")
|
||||||
# Extracting the 0th dictionary in list index 0
|
|
||||||
device = list_devices(scsi_id)[0][0]
|
devices = list_devices(scsi_id)
|
||||||
|
|
||||||
|
# First check if any device at all was returned
|
||||||
|
if devices["status"] == False:
|
||||||
|
flash(f"No device attached to SCSI id {scsi_id}!", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
# Looking at the first dict in list to get
|
||||||
|
# the one and only device that should have been returned
|
||||||
|
device = devices["device_list"][0]
|
||||||
if str(device["id"]) == scsi_id:
|
if str(device["id"]) == scsi_id:
|
||||||
flash("=== DEVICE INFO ===")
|
flash("=== DEVICE INFO ===")
|
||||||
flash(f"SCSI ID: {device['id']}")
|
flash(f"SCSI ID: {device['id']}")
|
||||||
|
@ -270,31 +422,36 @@ def device_info():
|
||||||
|
|
||||||
@app.route("/pi/reboot", methods=["POST"])
|
@app.route("/pi/reboot", methods=["POST"])
|
||||||
def restart():
|
def restart():
|
||||||
|
flash("Restarting the Pi momentarily...")
|
||||||
reboot_pi()
|
reboot_pi()
|
||||||
flash("Restarting...")
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/rascsi/restart", methods=["POST"])
|
@app.route("/rascsi/restart", methods=["POST"])
|
||||||
def rascsi_restart():
|
def rascsi_restart():
|
||||||
|
server_info = get_server_info()
|
||||||
rascsi_service("restart")
|
rascsi_service("restart")
|
||||||
flash("Restarting RaSCSI Service...")
|
flash("Restarting RaSCSI Service...")
|
||||||
reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS")
|
# Need to turn this into a list of strings from a list of ints
|
||||||
if reserved_scsi_ids != "":
|
reserve_scsi_ids([str(e) for e in server_info["reserved_ids"]])
|
||||||
reserve_scsi_ids(reserved_scsi_ids)
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/pi/shutdown", methods=["POST"])
|
@app.route("/pi/shutdown", methods=["POST"])
|
||||||
def shutdown():
|
def shutdown():
|
||||||
|
flash("Shutting down the Pi momentarily...")
|
||||||
shutdown_pi()
|
shutdown_pi()
|
||||||
flash("Shutting down...")
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/files/download_to_iso", methods=["POST"])
|
@app.route("/files/download_to_iso", methods=["POST"])
|
||||||
def download_file():
|
def download_file():
|
||||||
scsi_id = request.form.get("scsi_id")
|
scsi_id = request.form.get("scsi_id")
|
||||||
|
validate = validate_scsi_id(scsi_id)
|
||||||
|
if validate["status"] == False:
|
||||||
|
flash(validate["msg"], "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
url = request.form.get("url")
|
url = request.form.get("url")
|
||||||
process = download_file_to_iso(scsi_id, url)
|
process = download_file_to_iso(scsi_id, url)
|
||||||
if process["status"] == True:
|
if process["status"] == True:
|
||||||
|
@ -348,17 +505,17 @@ def upload_file(filename):
|
||||||
@app.route("/files/create", methods=["POST"])
|
@app.route("/files/create", methods=["POST"])
|
||||||
def create_file():
|
def create_file():
|
||||||
file_name = request.form.get("file_name")
|
file_name = request.form.get("file_name")
|
||||||
size = request.form.get("size")
|
size = (int(request.form.get("size")) * 1024 * 1024)
|
||||||
file_type = request.form.get("type")
|
file_type = request.form.get("type")
|
||||||
|
|
||||||
process = create_new_image(file_name, file_type, size)
|
process = create_new_image(file_name, file_type, size)
|
||||||
if process.returncode == 0:
|
if process["status"] == True:
|
||||||
flash("Drive created")
|
flash(f"Drive image created as {file_name}.{file_type}")
|
||||||
|
flash(process["msg"])
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
else:
|
else:
|
||||||
flash("Failed to create file", "error")
|
flash(f"Failed to create file {file_name}.{file_type}", "error")
|
||||||
flash(process.stdout, "stdout")
|
flash(process["msg"], "error")
|
||||||
flash(process.stderr, "stderr")
|
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -370,14 +527,35 @@ def download():
|
||||||
|
|
||||||
@app.route("/files/delete", methods=["POST"])
|
@app.route("/files/delete", methods=["POST"])
|
||||||
def delete():
|
def delete():
|
||||||
image = request.form.get("image")
|
file_name = request.form.get("image")
|
||||||
if delete_file(base_dir + image):
|
|
||||||
flash("File " + image + " deleted")
|
process = delete_file(file_name)
|
||||||
return redirect(url_for("index"))
|
if process["status"] == True:
|
||||||
|
flash(f"File {file_name} deleted!")
|
||||||
|
flash(process["msg"])
|
||||||
else:
|
else:
|
||||||
flash("Failed to Delete " + image, "error")
|
flash(f"Failed to delete file {file_name}!", "error")
|
||||||
|
flash(process["msg"], "error")
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
# Delete the drive properties file, if it exists
|
||||||
|
from pathlib import Path
|
||||||
|
file_name = str(Path(file_name).stem) + "." + PROPERTIES_SUFFIX
|
||||||
|
file_path = Path(base_dir + file_name)
|
||||||
|
if file_path.is_file():
|
||||||
|
process = delete_file(file_name)
|
||||||
|
if process["status"] == True:
|
||||||
|
flash(f"File {file_name} deleted!")
|
||||||
|
flash(process["msg"])
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
else:
|
||||||
|
flash(f"Failed to delete file {file_name}!", "error")
|
||||||
|
flash(process["msg"], "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/files/unzip", methods=["POST"])
|
@app.route("/files/unzip", methods=["POST"])
|
||||||
def unzip():
|
def unzip():
|
||||||
|
@ -402,18 +580,15 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
from sys import argv
|
from sys import argv
|
||||||
if len(argv) >= 2:
|
if len(argv) >= 2:
|
||||||
# Reserved ids format is a string of digits such as '017'
|
|
||||||
app.config["RESERVED_SCSI_IDS"] = str(argv[1])
|
|
||||||
# Reserve SCSI IDs on the backend side to prevent use
|
# Reserve SCSI IDs on the backend side to prevent use
|
||||||
reserve_scsi_ids(app.config.get("RESERVED_SCSI_IDS"))
|
# Expecting argv as a string of digits such as '017'
|
||||||
else:
|
reserve_scsi_ids(list(argv[1]))
|
||||||
app.config["RESERVED_SCSI_IDS"] = ""
|
|
||||||
|
|
||||||
# Load the default configuration file, if found
|
# Load the default configuration file, if found
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
default_config = Path(DEFAULT_CONFIG)
|
default_config_path = Path(base_dir + DEFAULT_CONFIG)
|
||||||
if default_config.is_file():
|
if default_config_path.is_file():
|
||||||
read_config(default_config)
|
read_config(DEFAULT_CONFIG)
|
||||||
|
|
||||||
import bjoern
|
import bjoern
|
||||||
print("Serving rascsi-web...")
|
print("Serving rascsi-web...")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user