New Web Interface (#69)

* gitignore

* New Web Interface
Fixed spacing/tabs in easy install
Added migration check
Fix update web not actually updating

Migrating from https://github.com/erichelgeson/RaSCSI-web

* Allow user to select multiple types when creating images

* Show all devices even if nothing is attached.

* If attaching an iso to a cd device, dont detach, just insert

* UI feedback and restart rascsi service

* Check for any non-0 exit code for apache2 detection

* Pretty/informative 502

* Add confirms to some actions. Works in netscape 4.7

* Fix order of params for create_new_image

* Move non-route method to service

* Add method for getting logs

* Move settings to single file
add ability to mock commands for local dev
This commit is contained in:
Eric Helgeson 2020-12-30 19:39:32 -06:00 committed by GitHub
parent 3536e8aa02
commit 37f1166fd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 894 additions and 914 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
venv

View File

@ -34,7 +34,7 @@ function initialChecks() {
fi
if [ ! -d ~/RASCSI ]; then
echo "You must checkout RASCSI repo into /user/pi/RASCSI"
echo "You must checkout RASCSI repo into /home/pi/RASCSI"
echo "$ git clone git@github.com:akuker/RASCSI.git"
exit 2
fi
@ -46,9 +46,9 @@ function initialChecks() {
function installRaScsi() {
sudo apt-get update && sudo apt-get install --yes git libspdlog-dev
cd ~/RASCSI/src/raspberrypi
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
cd ~/RASCSI/src/raspberrypi
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
sudoIsReady=$(sudo grep -c "rascsi" /etc/sudoers)
@ -61,35 +61,28 @@ www-data ALL=NOPASSWD: /bin/systemctl stop rascsi.service
www-data ALL=NOPASSWD: /sbin/shutdown, /sbin/reboot
" >> /etc/sudoers'
fi
sudo systemctl restart rsyslog
sudo systemctl enable rascsi # optional - start rascsi at boot
sudo systemctl start rascsi
sudo systemctl restart rsyslog
sudo systemctl enable rascsi # optional - start rascsi at boot
sudo systemctl start rascsi
}
# install everything required to run an HTTP server (Apache+PHP)
# configure PHP
# install
function stopOldWebInterface() {
APACHE_STATUS=$(sudo systemctl status apache2 &> /dev/null; echo $?)
if [ $APACHE_STATUS -eq 0 ] ; then
echo "Stopping old Apache2 RaSCSI Web..."
sudo systemctl disable apache2
sudo systemctl stop apache2
fi
}
# install everything required to run an HTTP server (Nginx + Python Flask App)
function installRaScsiWebInterface() {
sudo apt install apache2 php libapache2-mod-php -y
sudo cp ~/RASCSI/src/php/* /var/www/html
stopOldWebInterface
sudo apt install genisoimage python3 python3-venv nginx -y
PHP_CONFIG_FILE=/etc/php/7.3/apache2/php.ini
#Comment out any current configuration
sudo sed -i.bak 's/^post_max_size/#post_max_size/g' $PHP_CONFIG_FILE
sudo sed -i.bak 's/^upload_max_filesize/#upload_max_filesize/g' $PHP_CONFIG_FILE
sudo bash -c 'PHP_CONFIG_FILE=/etc/php/7.3/apache2/php.ini && echo "
# RaSCSI high upload limits
upload_max_filesize = 1200M
post_max_size = 1200M
" >> $PHP_CONFIG_FILE'
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
mkdir -p $VIRTUAL_DRIVER_PATH
chmod -R 775 $VIRTUAL_DRIVER_PATH
@ -97,30 +90,38 @@ post_max_size = 1200M
sudo usermod -a -G pi www-data
groups www-data
sudo /etc/init.d/apache2 restart
sudo systemctl reload nginx
sudo cp ~/RASCSI/src/web/service-infra/rascsi-web.service /etc/systemd/system/rascsi-web.service
sudo systemctl daemon-reload
sudo systemctl enable rascsi-web
sudo systemctl start rascsi-web
}
function updateRaScsiGit() {
cd ~/RASCSI
git pull
}
function updateRaScsi() {
updateRaScsiGit
sudo systemctl stop rascsi
cd ~/RASCSI/src/raspberrypi
make clean
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
sudo systemctl start rascsi
cd ~/RASCSI/src/raspberrypi
make clean
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
sudo systemctl start rascsi
}
function updateRaScsiWebInterface() {
sudo /etc/init.d/apache2 stop
cd ~/RASCSI
git fetch --all
cd ~/RASCSI/src/raspberrypi
sudo cp ~/RASCSI/src/php/* /var/www/html
sudo /etc/init.d/apache2 start
stopOldWebInterface
updateRaScsiGit
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
sudo systemctl restart rascsi-web
sudo systemctl restart nginx
}
function showRaScsiStatus() {

View File

@ -1,8 +0,0 @@
root = true
[*.{html,php,htm}]
indent_style = space
indent_size = 3
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,31 +0,0 @@
BSD 3-Clause License
Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
Copyright (C) 2014-2020 GIMONS
Copyright (c) 2020, akuker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

View File

@ -1,260 +0,0 @@
<!-- PHP source code for controlling the RaSCSI - 68kmla edition with a web interface. -->
<!-- Copyright (c) 2020 akuker -->
<!-- Distributed under the BSD-3 Clause License -->
<!-- Note: the origina rascsi-php project was under MIT license.-->
<?php
$DEBUG_ENABLE=0;
$FILE_PATH='/home/pi/images';
// Limit the maximum upload file size to 1GB
$MAX_UPLOAD_FILE_SIZE=1000000000;
$ALLOWED_FILE_TYPES=array('iso','hda');
function html_generate_header(){
echo ' <table width="100%" >'. PHP_EOL;
echo ' <tr style="background-color: black;">'. PHP_EOL;
echo ' <td style="background-color: black;"><a href=http://github.com/akuker/RASCSI><h1>RaSCSI - 68kmla Edition</h1></a></td>'. PHP_EOL;
echo ' <td style="background-color: black;">'. PHP_EOL;
echo ' <form action="rascsi.php">'. PHP_EOL;
echo ' <input type="submit" value="Go Home"/>'. PHP_EOL;
if($GLOBALS['DEBUG_ENABLE']){
echo ' <p style="color:#595959">Debug Timestamp: '.time().'</p>'. PHP_EOL;
}
echo ' </form>'. PHP_EOL;
echo ' </td>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
echo ' </table>'. PHP_EOL;
//echo(exec('whoami'));
}
function html_generate_image_file_select_list(){
$all_files = get_all_files();
foreach(explode(PHP_EOL, $all_files) as $this_file){
if(strpos($this_file, 'total') === 0){
continue;
}
$file_name = file_name_from_ls($this_file);
if(strlen($file_name) === 0){
continue;
}
// Ignore files that start with a .
if(strpos($file_name, '.') === 0){
continue;
}
echo '<option value="'.$file_name.'">'.$file_name.'</option>'.PHP_EOL;
}
}
function html_generate_scsi_id_select_list(){
echo '<select>'. PHP_EOL;
foreach(range(0,7) as $id){
echo '<option value="'.$id.'">'.$id.'</option>'. PHP_EOL;
}
echo '</select>'. PHP_EOL;
}
function html_generate_scsi_type_select_list(){
echo '<select name=type>'. PHP_EOL;
$options = array("Hard Disk", "CD-ROM", "Zip Drive", "Ethernet Tap", "Filesystem Bridge");
foreach($options as $type){
echo '<option value="'.$type.'">'.$type.'</option>'. PHP_EOL;
}
echo '</select>'. PHP_EOL;
}
function html_generate_warning($message){
echo ' <table width="100%" >'. PHP_EOL;
echo ' <tr style="background-color: red;">'. PHP_EOL;
echo ' <td style="background-color: red;">'. PHP_EOL;
echo ' <font size=+2>'.$message.'</font>'. PHP_EOL;
echo ' </td>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
echo ' </table>'. PHP_EOL;
}
function html_generate_success_message($message){
echo ' <table width="100%" >'. PHP_EOL;
echo ' <tr style="background-color: green;">'. PHP_EOL;
echo ' <td style="background-color: green;">'. PHP_EOL;
echo ' <font size=+2>Success</font>'. PHP_EOL;
echo ' </td>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
if(strlen($message) > 0){
echo ' <tr style="background-color: green;">'. PHP_EOL;
echo ' <td style="background-color: green;">'. PHP_EOL;
echo ' '.$message.PHP_EOL;
echo ' </td>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
}
echo ' </table>'. PHP_EOL;
}
function html_generate_ok_to_go_home(){
echo ' <form action="rascsi.php">'. PHP_EOL;
echo ' <input type="submit" value="OK"/>'. PHP_EOL;
echo ' </form>'. PHP_EOL;
}
function current_rascsi_config() {
$raw_output = shell_exec("/usr/local/bin/rasctl -l");
$rasctl_lines = explode(PHP_EOL, $raw_output);
echo ' <br>'. PHP_EOL;
echo ' <h2>Current RaSCSI Configuration</h2>'. PHP_EOL;
echo ' <table border="black" cellpadding="3">'. PHP_EOL;
echo ' <tr>'. PHP_EOL;
echo ' <td><b>SCSI ID</b></td>'. PHP_EOL;
echo ' <td><b>Type</b></td>'. PHP_EOL;
echo ' <td><b>File</b></td>'. PHP_EOL;
echo ' <td><b>File Ops</b></td>'. PHP_EOL;
echo ' <td><b>Device Ops</b></td>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
$scsi_ids = array();
foreach ($rasctl_lines as $current_line)
{
if(strlen($current_line) === 0){
continue;
}
if(strpos($current_line, '+----') === 0){
continue;
}
if(strpos($current_line, '| ID | UN') === 0){
continue;
}
$segments = explode("|", $current_line);
$id_config = array();
$id_config['id'] = trim($segments[1]);
$id_config['type'] = trim($segments[3]);
$id_config['file'] = trim($segments[4]);
$scsi_ids[$id_config['id']] = $id_config;
}
foreach (range(0,7) as $id){
echo ' <tr>'. PHP_EOL;
echo ' <td style="text-align:center">'.$id.'</td>'. PHP_EOL;
if(isset($scsi_ids[$id]))
{
echo ' <td style="text-align:center">'.$scsi_ids[$id]['type'].'</td>'. PHP_EOL;
if(strtolower($scsi_ids[$id]['file']) == "no media"){
echo ' <td>'.PHP_EOL;
echo ' <form action="rascsi_action.php" method="post">'. PHP_EOL;
echo ' <select name="file_name">'.PHP_EOL;
echo ' <option value="None">None</option>'.PHP_EOL;
html_generate_image_file_select_list();
echo ' </select>'.PHP_EOL;
echo ' <input type="hidden" name="command" value="insert_disk" />'. PHP_EOL;
echo ' <input type="hidden" name="id" value="'.$id.'" />'. PHP_EOL;
echo ' <input type="hidden" name="file" value="'.$scsi_ids[$id]['file'].'" />'. PHP_EOL;
echo ' </td><td>'.PHP_EOL;
echo ' <input type="submit" name="insert_disk" value="Insert" />'. PHP_EOL;
echo ' </form>'. PHP_EOL;
echo ' </td>'.PHP_EOL;
}
else{
// rascsi inserts "WRITEPROTECT" for the read-only drives. We want to display that differently.
echo ' <form action="rascsi_action.php" method="post">'. PHP_EOL;
echo ' <td>'.str_replace('(WRITEPROTECT)', '', $scsi_ids[$id]['file']). PHP_EOL;
echo ' </td><td>'.PHP_EOL;
if(strtolower($scsi_ids[$id]['type']) == 'sccd'){
echo ' <input type="hidden" name="command" value="eject_disk" />'. PHP_EOL;
echo ' <input type="hidden" name="id" value="'.$id.'" />'. PHP_EOL;
echo ' <input type="hidden" name="file" value="'.$scsi_ids[$id]['file'].'" />'. PHP_EOL;
echo ' <input type="submit" name="eject_disk" value="Eject" />'. PHP_EOL;
}
echo ' </td>'.PHP_EOL;
echo ' </form>'. PHP_EOL;
}
echo ' <td>'. PHP_EOL;
echo ' <form action="rascsi_action.php" method="post">'. PHP_EOL;
echo ' <input type="hidden" name="command" value="remove_device" />'. PHP_EOL;
echo ' <input type="hidden" name="id" value="'.$id.'" />'. PHP_EOL;
echo ' <input type="submit" name="remove_device" value="Disconnect" />'. PHP_EOL;
echo ' </form>'. PHP_EOL;
echo ' </td>'. PHP_EOL;
}
else
{
echo ' <td style="text-align:center">-</td>'. PHP_EOL;
echo ' <td>-</td>'. PHP_EOL;
echo ' <td></td>'. PHP_EOL;
echo ' <td>'. PHP_EOL;
echo ' <form action="rascsi_action.php" method="post">'. PHP_EOL;
echo ' <input type="hidden" name="command" value="connect_new_device" />'. PHP_EOL;
echo ' <input type="hidden" name="id" value="'.$id.'" />'. PHP_EOL;
echo ' <input type="submit" name="connect_new_device" value="Connect New" />'. PHP_EOL;
echo ' </form>'. PHP_EOL;
echo ' </td>'. PHP_EOL;
}
echo ' </form>'. PHP_EOL;
echo ' </tr>'. PHP_EOL;
}
echo '</table>'. PHP_EOL;
}
function get_all_files()
{
$raw_ls_output = shell_exec('ls --time-style="+\"%Y-%m-%d %H:%M:%S\"" -alh --quoting-style=c '.$GLOBALS['FILE_PATH']);
return $raw_ls_output;
}
function mod_date_from_ls($value){
$ls_pieces = explode("\"", $value);
if(count($ls_pieces)<1){
return "";
}
return $ls_pieces[1];
}
function file_name_from_ls($value){
$ls_pieces = explode("\"", $value);
if(count($ls_pieces) < 4){
return "";
}
return $ls_pieces[3];
}
function file_size_from_ls($value){
$ls_pieces = explode("\"", $value);
$file_props = preg_split("/\s+/", $ls_pieces[0]);
return $file_props[4];
}
function file_category_from_file_name($value){
if(strpos($value,".iso") || strpos($value,".cdr") > 0){
return "CD-ROM Image";
}
if(strpos($value,".hda") > 0){
return "Hard Disk Image";
}
return "Unknown type: " . $value;
}
function type_string_to_rasctl_type($typestr){
if(strcasecmp($typestr,"Hard Disk") == 0){
return "hd";
}
if(strcasecmp($typestr,"CD-ROM") == 0){
return "cd";
}
if(strcasecmp($typestr,"Zip Drive") == 0){
}
if(strcasecmp($typestr,"Filesystem bridge") == 0){
return "bridge";
}
return "";
}
?>

View File

@ -1,9 +0,0 @@
<HTML>
<HEAD> <TITLE>RaSCSI Control Page</TITLE></HEAD>
<FRAMESET ROWS="20px,*" BORDER=0>
<FRAME SRC="status.php" NAME="rascsi_status">
<FRAME SRC="rascsi.php" NAME="rascsi_control">
<NOFRAMES>You must use a browser that can display frames
to see this page. </NOFRAMES>
</FRAMESET>
</HTML>

View File

@ -1,135 +0,0 @@
<!-- PHP source code for controlling the RaSCSI - 68kmla edition with a web interface. -->
<!-- Copyright (c) 2020 akuker -->
<!-- Distributed under the BSD-3 Clause License -->
<!-- Note: the origina rascsi-php project was under MIT license.-->
<!DOCTYPE html>
<html>
<head>
<title>RaSCSI Main Control Page</title>
<link rel="stylesheet" href="rascsi_styles.css">
</head>
<body>
<?php
include 'lib_rascsi.php';
html_generate_header();
current_rascsi_config();
?>
<br>
<h2>Image File Management</h2>
<table border="black" cellpadding="3">
<tr>
<td><b>Location</b></td>
<td><b>Filename</b></td>
<td><b>Size</b></td>
<td><b>Type</b></td>
<td><b>Date Modified</b></td>
<td><b>Actions</b></td>
</tr>
<tr>
<?php
// Generate the table with all of the file names in it.....
$all_files = get_all_files();
foreach(explode(PHP_EOL, $all_files) as $this_file){
if(strpos($this_file, 'total') === 0){
continue;
}
$file_name = file_name_from_ls($this_file);
if(strlen($file_name) === 0){
continue;
}
// Ignore file names that start with .
if(strpos($file_name,".") === 0){
continue;
}
echo '<tr>'.PHP_EOL;
echo ' <td>SD Card</td>'.PHP_EOL;
echo ' <td>'.$file_name.'</td>'.PHP_EOL;
echo ' <td>'.file_size_from_ls($this_file).'</td>'.PHP_EOL;
echo ' <td>'.file_category_from_file_name($file_name).'</td>'.PHP_EOL;
echo ' <td>'.mod_date_from_ls($this_file).'</td>'.PHP_EOL;
echo ' <td>'.PHP_EOL;
echo ' <form action="rascsi_action.php" method="post">'.PHP_EOL;
echo ' <input type="hidden" name="command" value="delete_file"/>'.PHP_EOL;
echo ' <input type="hidden" name="file_name" value="'.$file_name.'"/>'.PHP_EOL;
echo ' <input type="submit" value="Delete">'.PHP_EOL;
echo ' </form>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo '</tr>'.PHP_EOL;
}
?>
</table>
<br>
<br>
<h2>Upload New Image File</h2>
<form action="rascsi_upload.php" method="post" enctype="multipart/form-data">
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<input type="file" id="filename" name="file_name"><br><br>
</td>
<td style="border: none; vertical-align:top;">
<input type="submit" value="Upload" name="submit">
</td>
</tr>
</table>
</form>
<br>
<h2>Create New Empty HD Image</h2>
<form action="rascsi_action.php" method="post">
<input type="hidden" name="command" value="create_new_image" />
<input type="submit" value="Create New">
</form>
<br>
<h2>RaSCSI Service Status</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="rascsi_action.php" method="post">
<input type="hidden" name="command" value="restart_rascsi_service" />
<input type="submit" name="restart_rascsi_service" value="Restart RaSCSI service" />
</form>
</td>
<td style="border: none; vertical-align:top;">
<form action="rascsi_action.php" method="post">
<input type="hidden" name="command" value="stop_rascsi_service" />
<input type="submit" name="stop_rascsi_service" value="Stop RaSCSI service" />
</form>
</td>
</tr>
</table>
<br>
<h2>Raspberry Pi Operations</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="rascsi_action.php" method="post">
<input type="hidden" name="command" value="reboot_raspberry_pi" />
<input type="submit" name="reboot_rasbperry_pi" value="Reboot Raspberry Pi" />
</form>
</td>
<td style="border: none; vertical-align:top;">
<form action="rascsi_action.php" method="post">
<input type="hidden" name="command" value="shutdown_raspberry_pi" />
<input type="submit" name="shutdown_raspberry_pi" value="Shut Down Raspberry Pi" />
</form>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,297 +0,0 @@
<!-- PHP source code for controlling the RaSCSI - 68kmla edition with a web interface. -->
<!-- Copyright (c) 2020 akuker -->
<!-- Distributed under the BSD-3 Clause License -->
<!DOCTYPE html>
<html>
<head>
<title>RaSCSI Action Page</title>
<link rel="stylesheet" href="rascsi_styles.css">
</head>
<body>
<?php
include 'lib_rascsi.php';
html_generate_header();
echo '<br>';
if($GLOBALS['DEBUG_ENABLE']){
echo '<table>'.PHP_EOL;
echo ' <tr><td><p style="color:gray">Debug stuff</p></td></tr>'.PHP_EOL;
echo ' <tr><td>';
echo '<p style="color:gray">Post values......................'.PHP_EOL;
echo '<br> '.PHP_EOL;
var_dump($_POST);
echo '<br><br>Running command.... '.$_POST['command'].PHP_EOL;
echo '<br></p>'.PHP_EOL;
echo '</td></tr></table>';
}
if(isset($_POST['command']))
{
switch(strtolower($_POST['command'])){
case "eject_disk":
action_eject_disk();
break;
case "remove_device":
action_remove_device();
break;
case "connect_new_device":
action_connect_new_device();
break;
case "insert_disk":
action_insert_disk();
break;
case "delete_file":
action_delete_file();
break;
case "create_new_image":
action_create_new_image();
break;
case "restart_rascsi_service":
action_restart_rascsi_service();
break;
case "stop_rascsi_service":
action_stop_rascsi_service();
break;
case "reboot_raspberry_pi":
action_reboot_raspberry_pi();
break;
case "shutdown_raspberry_pi":
action_shutdown_raspberry_pi();
break;
default:
action_unknown_command();
break;
}
}
else{
html_generate_warning("HTTP command was missing POST information. Are you trying to access this page directly? That won't work");
echo "<br>".PHP_EOL;
html_generate_ok_to_go_home();
}
function action_eject_disk(){
$command = 'rasctl -i '.$_POST['id'].' -c eject 2>&1'.PHP_EOL;
exec($command, $retArray, $result);
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
function action_remove_device(){
// Check to see if the user has confirmed
if(isset($_POST['confirmed'])){
$command = 'rasctl -i '.$_POST['id'].' -c disconnect 2>&1'.PHP_EOL;
exec($command, $retArray, $result);
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
else{
check_are_you_sure('Are you sure you want to disconnect SCSI ID ' . $_POST['id'].'? If the host is running, this could cause undesirable behavior.');
}
}
// function action_connect_new_device(){}
function action_insert_disk(){
$command = 'rasctl -i '.$_POST['id'].' -c insert -f '.$GLOBALS['FILE_PATH'].'/'.$_POST['file_name'].' 2>&1'.PHP_EOL;
exec($command, $retArray, $result);
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
function action_create_new_image(){
// If we already know the size & filename, we can go create the image...
if(isset($_POST['size']) && isset($_POST['file_name'])){
$command = 'dd if=/dev/zero of='.$GLOBALS['FILE_PATH'].'/'.$_POST['file_name'].' bs=1M count='.$_POST['size'];
exec($command, $retArray, $result);
check_result($result, $command, $retArray);
html_generate_ok_to_go_home();
}
else{
echo '<h2>Create a new empty file</h2>'.PHP_EOL;
echo '<form action=rascsi_action.php method="post">'.PHP_EOL;
echo ' <input type="hidden" name="command" value="'.$_POST['command'].'"/>'.PHP_EOL;
echo ' <table style="border: none">'.PHP_EOL;
echo ' <tr style="border: none">'.PHP_EOL;
echo ' <td style="border: none">File Name:</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <input type="text" name=file_name value="'.get_new_filename().'"/>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none"> Size:</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <input type="number" name=size value="10""/>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none">MB</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <input type="submit" name="create" value="Create New" />'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' </table>'.PHP_EOL;
echo '</form>'.PHP_EOL;
echo '<br><i>Note: Creating a large file may take a long time!</i>'.PHP_EOL;
echo '<br><br>'.PHP_EOL;
echo '<form action="rascsi.php" method="post">'.PHP_EOL;
echo ' <input type="submit" name="cancel" value="Cancel" />'.PHP_EOL;
echo '</form>'.PHP_EOL;
}
}
function action_delete_file(){
// Check to see if the user has confirmed
if(isset($_POST['confirmed'])){
$command = 'rm '.$GLOBALS['FILE_PATH'].'/'.$_POST['file_name'];
exec($command, $retArray, $result);
check_result($result, $command, $retArray);
html_generate_ok_to_go_home();
}
else{
check_are_you_sure('Are you sure you want to PERMANENTLY delete '.$_POST['file_name'].'?');
}
}
function action_restart_rascsi_service(){
// Restart the RaSCSI service
$command = "sudo /bin/systemctl restart rascsi.service 2>&1";
exec($command, $retArray, $result);
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
function action_stop_rascsi_service(){
// Stop the RaSCSI service
$command = "sudo /bin/systemctl stop rascsi.service 2>&1";
exec($command, $retArray, $result);
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
function action_reboot_raspberry_pi(){
// Check to see if the user has confirmed
if(isset($_POST['confirmed'])){
$command = "sleep 2 && sudo reboot 2>&1";
exec($command, $retArray, $result);
// The unit should reboot at this point. Doesn't matter what we do now...
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
else{
check_are_you_sure("Are you sure you want to reboot the Raspberry Pi?");
}
}
function action_shutdown_raspberry_pi(){
// Check to see if the user has confirmed
if(isset($_POST['confirmed'])){
$command = "sleep 2 && sudo shutdown -h now 2>&1";
exec($command, $retArray, $result);
// The unit should shutdown at this point. Doesn't matter what we do now...
check_result($result, $command,$retArray);
html_generate_ok_to_go_home();
}
else{
check_are_you_sure("Are you sure you want to shut down the Raspberry Pi?");
}
}
function action_unknown_command(){
html_generate_warning('<br><h2>Unknown command: '.$_POST['command'].'</h2>');
html_generate_ok_to_go_home();
}
function check_result($result,$command,$output){
if(!$result){
html_generate_success_message('Command succeeded!');
}
else{
html_generate_warning('Command failed!');
}
echo '<br><code>'.$command.'</code><br>'.PHP_EOL;
if(count($output) > 0){
echo '<br>Output:<code>'.PHP_EOL;
foreach($output as $line){
echo '<br> Error message: '.$line.PHP_EOL;
}
echo '</code>'.PHP_EOL;
}
}
function check_are_you_sure($prompt){
echo '<br><h2>'.$prompt.'</h2>'.PHP_EOL;
echo ' <table style="border: none">'.PHP_EOL;
echo ' <tr style="border: none">'.PHP_EOL;
echo ' <td style="border: none; vertical-align:top;">'.PHP_EOL;
echo ' <form action="rascsi_action.php" method="post">'.PHP_EOL;
foreach($_POST as $key => $value){
echo '<input type="hidden" name="'.$key.'" value="'.$value.'"/>'.PHP_EOL;
}
echo ' <input type="hidden" name="confirmed" value="yes" />'.PHP_EOL;
echo ' <input type="submit" name="do_it" value="Yes" />'.PHP_EOL;
echo ' </form>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none; vertical-align:top;">'.PHP_EOL;
echo ' <form action="rascsi.php" method="post">'.PHP_EOL;
echo ' <input type="submit" name="cancel" value="Cancel" />'.PHP_EOL;
echo ' </form>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo '</table>'.PHP_EOL;
}
function action_connect_new_device(){
// If we already know the type & filename, we can go connect the device...
if(isset($_POST['type']) && isset($_POST['file_name'])){
$command = 'rasctl -i '.$_POST['id'].' -c attach -t '.type_string_to_rasctl_type($_POST['type']);
if($_POST['file_name'] != "None"){
$command = $command.' -f '.$GLOBALS['FILE_PATH'].'/'.$_POST['file_name'];
}
exec($command, $retArray, $result);
check_result($result, $command, $retArray);
html_generate_ok_to_go_home();
}
else{
$id = $_POST['id'];
echo '<h2>Add New Device</h2>'.PHP_EOL;
echo '<form action=rascsi_action.php method="post">'.PHP_EOL;
echo ' <input type="hidden" name="command" value="'.$_POST['command'].'"/>'.PHP_EOL;
echo ' <table style="border: none">'.PHP_EOL;
echo ' <tr style="border: none">'.PHP_EOL;
echo ' <td style="border: none">SCSI ID:</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <input type="hidden" name=id value="'.$id.'"/>'.PHP_EOL;
echo $id;
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none">Device:</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
html_generate_scsi_type_select_list();
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none">File:</td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <select name="file_name">'.PHP_EOL;
echo ' <option value="None">None</option>'.PHP_EOL;
html_generate_image_file_select_list();
echo ' </select>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' <td style="border: none">'.PHP_EOL;
echo ' <INPUT type="submit" value="Add"/>'.PHP_EOL;
echo ' </td>'.PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' </table>'.PHP_EOL;
}
}
function get_new_filename(){
// Try to find a new file name that doesn't exist.
$i=1;
while(file_exists($GLOBALS['FILE_PATH'].'/'.'new_file'.$i.'.hda'))
{
$i = $i+1;
}
return 'new_file'.$i.'.hda';
}
?>
</body>
</html>

View File

@ -1,86 +0,0 @@
<!-- PHP source code for controlling the RaSCSI - 68kmla edition with a web interface. -->
<!-- Copyright (c) 2020 akuker -->
<!-- Distributed under the BSD-3 Clause License -->
<!DOCTYPE html>
<html>
<head>
<title>RaSCSI Upload Page</title>
<link rel="stylesheet" href="rascsi_styles.css">
</head>
<body>
<?php
include 'lib_rascsi.php';
html_generate_header();
echo '<br>';
if($GLOBALS['DEBUG_ENABLE']){
echo '<table>'.PHP_EOL;
echo ' <tr><td><p style="color:gray">Debug stuff</p></td></tr>'.PHP_EOL;
echo ' <tr><td>';
echo '<p style="color:gray">Post values......................'.PHP_EOL;
echo '<br> '.PHP_EOL;
var_dump($_POST);
echo '<br><br>'.PHP_EOL;
var_dump($_FILES);
echo '<br></p>'.PHP_EOL;
echo '</td></tr></table>';
}
$target_dir = $GLOBALS['FILE_PATH'].'/';
$target_file = $target_dir.basename($_FILES['file_name']['name']);
$upload_ok=1;
$file_type = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
if(isset($_POST['submit']))
{
// Check if file already exists
if ($upload_ok && (file_exists($target_file))) {
html_generate_warning('Error: File '.$target_file.' already exists.');
$upload_ok = 0;
}
// Check file size. Limit is specified in lib_rascsi.php
if ($upload_ok && ($_FILES["file_name"]["size"] > $GLOBALS['MAX_UPLOAD_FILE_SIZE'])) {
html_generate_warning("Error: your file is larger than the maximum size of " . $GLOBALS['MAX_UPLOAD_FILE_SIZE'] . "bytes");
$upload_ok = 0;
}
// Allow certain file formats, also specified in lib_rascsi.php
if($upload_ok && (!in_array(strtolower($file_type),$GLOBALS['ALLOWED_FILE_TYPES']))){
$error_string = 'File type "'. $file_type. '" is not currently allowed.'.
'Only the following file types are allowed: <br>'.
'<ul>'.PHP_EOL;
foreach($GLOBALS['ALLOWED_FILE_TYPES'] as $ft){
$error_string = $error_string. '<li>'.$ft.'</li>'.PHP_EOL;
}
$error_string = $error_string.'</ul>';
$error_string = $error_string.'<br>';
html_generate_warning($error_string);
$upload_ok = 0;
}
//Check if $upload_ok is set to 0 by an error
if ($upload_ok != 0) {
if (move_uploaded_file($_FILES["file_name"]["tmp_name"], $target_file)) {
html_generate_success_message(basename( $_FILES["file_name"]["name"]). " has been uploaded.");
} else {
html_generate_warning("There was an unknown error uploading your file.");
}
}
}
else
{
html_generate_warning('The Submit POST information was not populated. Something went wrong');
}
echo '<br>';
html_generate_ok_to_go_home();
?>
</body>
</html>

View File

@ -1,44 +0,0 @@
<!-- Simple file for showing the status of the RaSCSI process. Intended to be loaded as a frame in a larger page -->
<!-- Copyright (c) 2020 akuker -->
<!-- Distributed under the BSD-3 Clause License -->
<!-- Note: the origina rascsi-php project was under MIT license.-->
<!DOCTYPE html>
<html>
<script type="text/javascript">
window.onload = setupRefresh;
function setupRefresh() {
setTimeout("refreshPage();", 30000); // milliseconds
}
function refreshPage() {
window.location = location.href;
}
</script>
<?php
// Blatently copied from stack overflow:
// https://stackoverflow.com/questions/53695187/php-function-that-shows-status-of-systemctl-service
$output = shell_exec("systemctl is-active rascsi");
if (trim($output) == "active") {
$color='green';
$text='Running';
}
else{
$color='red';
$text='Stopped';
}
echo '<body style="background-color: '.$color.';">';
echo '<table width="100%" height=100% style="position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color:'.$color.'">';
echo '<tr style:"height: 100%">';
echo '<td style="color: white; background-color: '.$color.'; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">'.$text.' '.date("h:i:sa").'</td>';
echo '</tr>';
echo '</table>';
?>
</body>
</html>

12
src/web/README.md Normal file
View File

@ -0,0 +1,12 @@
# RaSCSI Web
## Mocking for local development
Set a few env vars to point to the mock scripts and base dir
```
BASE_DIR=/tmp/images/
PATH=$PATH:`pwd`/mock/bin/
```
Edit response to commands in `mock/bin/*`

45
src/web/create_disk.py Normal file
View File

@ -0,0 +1,45 @@
from machfs import Volume, Folder, File
from settings import *
# Build a cd and attempt to fix resource forks if known
def make_cd(file_path, file_type, file_creator):
with open(file_path, "rb") as f:
file_bytes = f.read()
file_name = file_path.split('/')[-1]
file_suffix = file_name.split('.')[-1]
if file_type is None and file_creator is None:
if file_suffix.lower() == 'sea':
file_type = '.sea'
file_creator = 'APPL'
v = Volume()
v.name = "TestName"
v['Folder'] = Folder()
v['Folder'][file_name] = File()
v['Folder'][file_name].data = file_bytes
v['Folder'][file_name].rsrc = b''
if not (file_type is None and file_creator is None):
v['Folder'][file_name].type = bytearray(file_type)
v['Folder'][file_name].creator = bytearray(file_creator)
padding = (len(file_bytes) % 512) + (512 * 50)
print("mod", str(len(file_bytes) % 512))
print("padding " + str(padding))
print("len " + str(len(file_bytes)))
print("total " + str(len(file_bytes) + padding))
with open(base_dir + 'test.hda', 'wb') as f:
flat = v.write(
size=len(file_bytes) + padding,
align=512, # Allocation block alignment modulus (2048 for CDs)
desktopdb=True, # Create a dummy Desktop Database to prevent a rebuild on boot
bootable=False, # This requires a folder with a ZSYS and a FNDR file
startapp=('Folder', file_name), # Path (as tuple) to an app to open at boot
)
f.write(flat)
return base_dir + 'test.hda'

64
src/web/file_cmds.py Normal file
View File

@ -0,0 +1,64 @@
import fnmatch
import os
import subprocess
import time
from ractl_cmds import attach_image
from settings import *
valid_file_types = ['*.hda', '*.iso', '*.cdr']
valid_file_types = r'|'.join([fnmatch.translate(x) for x in valid_file_types])
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
return subprocess.run(["dd", "if=/dev/zero", "of=" + base_dir + file_name, "bs=1M", "count=" + size],
capture_output=True)
def delete_image(file_name):
full_path = base_dir + file_name
if os.path.exists(full_path):
os.remove(full_path)
return True
else:
return False
def unzip_file(file_name):
import zipfile
with zipfile.ZipFile(base_dir + file_name, 'r') as zip_ref:
zip_ref.extractall(base_dir)
return True
def rascsi_service(action):
# start/stop/restart
return subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode == 0
def download_file_to_iso(scsi_id, url):
import urllib.request
file_name = url.split('/')[-1]
tmp_ts = int(time.time())
tmp_dir = "/tmp/" + str(tmp_ts) + "/"
os.mkdir(tmp_dir)
tmp_full_path = tmp_dir + file_name
iso_filename = base_dir + file_name + ".iso"
urllib.request.urlretrieve(url, tmp_full_path)
# iso_filename = make_cd(tmp_full_path, None, None) # not working yet
iso_proc = subprocess.run(["genisoimage", "-hfs", "-o", iso_filename, tmp_full_path], capture_output=True)
if iso_proc.returncode != 0:
return iso_proc
return attach_image(scsi_id, iso_filename, "cd")
def download_image(url):
import urllib.request
file_name = url.split('/')[-1]
full_path = base_dir + file_name
urllib.request.urlretrieve(url, full_path)

12
src/web/mock/bin/journalctl Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Mock responses to rascsi-web
case $1 in
-n)
echo "logs $*"
;;
**)
echo "default"
;;
esac

12
src/web/mock/bin/rasctl Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Mock responses to rascsi-web
case $1 in
-f)
echo "logs here"
;;
**)
echo "default"
;;
esac

12
src/web/mock/bin/systemctl Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Mock responses to rascsi-web
case $1 in
is-active)
echo "is-active"
;;
**)
echo "default"
;;
esac

20
src/web/pi_cmds.py Normal file
View File

@ -0,0 +1,20 @@
import subprocess
def rascsi_service(action):
# start/stop/restart
return subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode == 0
def reboot_pi():
return subprocess.run(["sudo", "reboot"]).returncode == 0
def shutdown_pi():
return subprocess.run(["sudo", "shutdown", "-h", "now"]).returncode == 0
def running_version():
ra_web_version = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True).stdout.decode("utf-8").strip()
pi_version = subprocess.run(["uname", "-a"], capture_output=True).stdout.decode("utf-8").strip()
return ra_web_version + " " + pi_version

98
src/web/ractl_cmds.py Normal file
View File

@ -0,0 +1,98 @@
import fnmatch
import os
import subprocess
import re
from settings import *
valid_file_types = ['*.hda', '*.iso', '*.cdr', '*.zip']
valid_file_types = r'|'.join([fnmatch.translate(x) for x in valid_file_types])
# List of SCSI ID's you'd like to exclude - eg if you are on a Mac, the System is usually 7
EXCLUDE_SCSI_IDS = [7]
def is_active():
process = subprocess.run(["systemctl", "is-active", "rascsi"], capture_output=True)
return process.stdout.decode("utf-8").strip() == "active"
def list_files():
files_list = []
for path, dirs, files in os.walk(base_dir):
# Only list valid file types
files = [f for f in files if re.match(valid_file_types, f)]
files_list.extend([
(os.path.join(path, file),
# TODO: move formatting to template
'{:,.0f}'.format(os.path.getsize(os.path.join(path, file)) / float(1 << 20)) + " MB")
for file in files])
return files_list
def get_valid_scsi_ids(devices):
invalid_list = EXCLUDE_SCSI_IDS.copy()
for device in devices:
if device['file'] != "NO MEDIA" and device['file'] != "-":
invalid_list.append(int(device['id']))
valid_list = list(range(8))
for id in invalid_list:
valid_list.remove(id)
valid_list.reverse()
return valid_list
def get_type(scsi_id):
return list_devices()[int(scsi_id)]["type"]
def attach_image(scsi_id, image, type):
if type == "cd" and get_type(scsi_id) == "SCCD":
return insert(scsi_id, image)
else:
return subprocess.run(["rasctl", "-c", "attach", "-t", type, "-i", scsi_id, "-f", image], capture_output=True)
def detach_by_id(scsi_id):
return subprocess.run(["rasctl", "-c" "detach", "-i", scsi_id], capture_output=True)
def disconnect_by_id(scsi_id):
return subprocess.run(["rasctl", "-c", "disconnect", "-i", scsi_id], capture_output=True)
def eject_by_id(scsi_id):
return subprocess.run(["rasctl", "-i", scsi_id, "-c", "eject"])
def insert(scsi_id, image):
return subprocess.run(["rasctl", "-i", scsi_id, "-c", "insert", "-f", image], capture_output=True)
def rascsi_service(action):
# start/stop/restart
return subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode == 0
def list_devices():
device_list = []
for id in range(8):
device_list.append({"id": str(id), "un": "-", "type": "-", "file": "-"})
output = subprocess.run(["rasctl", "-l"], capture_output=True).stdout.decode("utf-8")
for line in output.splitlines():
# Valid line to process, continue
if not line.startswith("+") and \
not line.startswith("| ID |") and \
(not line.startswith("No device is installed.") or line.startswith("No images currently attached.")) \
and len(line) > 0:
line.rstrip()
device = {}
segments = line.split("|")
if len(segments) > 4:
idx = int(segments[1].strip())
device_list[idx]["id"] = str(idx)
device_list[idx]['un'] = segments[2].strip()
device_list[idx]['type'] = segments[3].strip()
device_list[idx]['file'] = segments[4].strip()
return device_list

12
src/web/requirements.txt Normal file
View File

@ -0,0 +1,12 @@
click==7.1.2
Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
machfs==1.2.4
macresources==1.2
MarkupSafe==1.1.1
rsrcfork==1.8.0
waitress==1.4.4
Werkzeug==1.0.1
zope.event==4.5.0
zope.interface==5.1.2

View File

@ -0,0 +1,14 @@
<html>
<head>
<title>RaSCSI-web is Starting</title>
<meta http-equiv="refresh" content="2">
</head>
</html>
<body>
<center>
<h1>RaSCSI Web is starting....</h1>
<h2>This page will automatically refresh.</h2>
<p>First boot and upgrades can take a second to resolve dependancies.</p>
<p>If you're seeing this page for over a minute please check the logs at <tt>sudo journalctl -f</tt></p>
</center>
</body>

View File

@ -0,0 +1,12 @@
# /etc/nginx/sites-available/default
# Simple proxy_pass for RaSCSI-web
server {
location / {
proxy_pass http://localhost:8080;
}
error_page 502 /502.html;
location = /502.html {
root /var/www/html/;
}
}

View File

@ -0,0 +1,14 @@
[Unit]
Description=RaSCSI-Web service
After=network.target
[Service]
Type=simple
Restart=always
ExecStart=/home/pi/RASCSI/src/web/start.sh
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=RASCSIWEB
[Install]
WantedBy=multi-user.target

4
src/web/settings.py Normal file
View File

@ -0,0 +1,4 @@
import os
base_dir = os.getenv('BASE_DIR', "/home/pi/images/")
MAX_FILE_SIZE = os.getenv('MAX_FILE_SIZE', 1024 * 1024 * 1024 * 2) # 2gb

58
src/web/start.sh Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -e
# set -x # Uncomment to Debug
cd $(dirname $0)
# verify packages installed
ERROR=0
if ! command -v genisoimage &> /dev/null ; then
echo "genisoimage could not be found"
echo "Run 'sudo apt install genisoimage' to fix."
ERROR=1
fi
if ! command -v python3 &> /dev/null ; then
echo "python3 could not be found"
echo "Run 'sudo apt install python3' to fix."
ERROR=1
fi
if ! python3 -m venv --help &> /dev/null ; then
echo "venv could not be found"
echo "Run 'sudo apt install python3-venv' to fix."
ERROR=1
fi
if ! command -v unzip &> /dev/null ; then
echo "unzip could not be found"
echo "Run 'sudo apt install unzip' to fix."
ERROR=1
fi
if [ $ERROR = 1 ] ; then
echo
echo "Fix errors and re-run ./start.sh"
exit 1
fi
if ! test -e venv; then
echo "Creating python venv for web server"
python3 -m venv venv
echo "Activating venv"
source venv/bin/activate
echo "Installing requirements.txt"
pip install -r requirements.txt
git rev-parse HEAD > current
fi
source venv/bin/activate
# Detect if someone updates - we need to re-run pip install.
if ! test -e current; then
git rev-parse > current
else
if [ "$(cat current)" != "$(git rev-parse HEAD)" ]; then
echo "New version detected, updating requirements.txt"
pip install -r requirements.txt
git rev-parse HEAD > current
fi
fi
echo "Starting web server..."
python3 web.py

View File

@ -21,8 +21,24 @@ a {
text-decoration: none;
}
form {
display: inline;
}
table, tr, td {
border: 1px solid black;
border-collapse:collapse;
margin: none;
}
.error {
color: white;
font-size:20px;
background-color:red;
}
.message {
color: white;
font-size:20px;
background-color:green;
}

View File

@ -0,0 +1,24 @@
<!doctype html>
<title>RaSCSI Control Page</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<div class="content">
<div class="header">
{% block header %}{% endblock %}
</div>
<div class="flash">
{% for category, message in get_flashed_messages(with_categories=true) %}
{% if category == "stdout" or category == "stderr" %}
<pre>{{message}}</pre>
{% else %}
<div class="{{category}}">{{ message }}</div>
{% endif %}
{% endfor %}
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
<div class="footer">
{% block footer %}{% endblock %}
</div>
</div>

View File

@ -0,0 +1,213 @@
{% extends "base.html" %}
{% block header %}
{% if active %}
<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>
<table cellpadding="3" border="black">
<tbody>
<tr>
<td><b>ID</b></td>
<td><b>Type</b></td>
<td><b>File</b></td>
<td><b>Action</b></td>
</tr>
{% for device in devices %}
<tr>
{% if device.id != "7" %}
<td style="text-align:center">{{device.id}}</td>
<td style="text-align:center">{{device.type}}</td>
<td>{{device.file}}</td>
<td>
{% if device.type == "SCCD" and device.file != "NO MEDIA" %}
<form action="/scsi/eject" method="post" onsubmit="return confirm('Eject Disk?')">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Eject" />
</form>
{% else %}
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Detach" />
</form>
{% endif %}
</td>
{% else %}
<td style="text-align:center">{{device.id}}</td>
<td style="text-align:center">-</td>
<td>Host Machine</td>
<td>-</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<h2>Image File Management</h2>
<table cellpadding="3" border="black">
<tbody>
<tr>
<td><b>File</b></td>
<td><b>Size</b></td>
<td><b>Actions</b></td>
</tr>
{% for file in files %}
<tr>
<td>{{file[0].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]}} &#8595;" />
</form>
</td>
<td>
<form action="/scsi/attach" method="post">
<input type="hidden" name="file_name" value="{{file[0]}}">
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{id}}">{{id}}</option>
{% endfor %}
</select>
<input type="submit" value="Attach" />
</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="submit" value="Delete" />
</form>
{% if file[0].endswith('.zip') or file[0].endswith('.ZIP') %}
<form action="/files/unzip" method="post">
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
<input type="submit" value="Unzip" />
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<hr/>
<h2>Upload File</h2>
<p>Uploads file to <tt>{{base_dir}}</tt>. Max file size is set to {{max_file_size / 1024 /1024 }}MB</p>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/files/upload" method="post" enctype="multipart/form-data">
<label for="file">File:</label>
<input type="file" name="file"/>
<input type="submit" value="Upload" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Download File from Web</h2>
<p>Given a URL, download that file to the <tt>{{base_dir}}</tt></p>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/files/download_image" method="post">
<label for="url">URL:</label>
<input type="text" placeholder="URL" name="url" />
<input type="submit" value="Download" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Download File from web and create HFS CD</h2>
<p>Given a URL this will download a file, create a HFS iso, and mount it on the device id given.</p>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<label for="scsi_id">SCSI ID:</label>
<form action="/files/download_to_iso" method="post">
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{id}}">{{id}}</option>
{% endfor %}
</select>
<label for="url">URL:</label>
<input type="text" placeholder="URL" name="url" />
<input type="submit" value="Download and Mount ISO" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Create Empty Disk Image File</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/files/create" method="post">
<label for="file_name">File Name:</label>
<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)</option>
<option value="hdn">SCSI Hard Disk image (NEC GENUINE)</option>
<option value="hdi">SCSI Hard Disk image (Anex86 HD image)</option>
<option value="nhd">SCSI Hard Disk image (T98Next HD image)</option>
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000)</option>
<option value="hds">SCSI Hard Disk image (XM6 SCSI HD image - typically only used with X68000)</option>
<option value="mos">SCSI Magneto-optical image (XM6 SCSI MO image - typically only used with X68000)</option>
</select>
<label for="size">Size(MB):</label>
<input type="number" placeholder="Size(MB)" name="size"/>
<input type="submit" value="Create" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Raspberry Pi Operations</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/pi/reboot" method="post" onsubmit="return confirm('Reboot Pi?')">
<input type="submit" value="Reboot Raspberry Pi" />
</form>
</td>
<td style="border: none; vertical-align:top;">
<form action="/pi/shutdown" method="post" onsubmit="return confirm('Shutdown Pi?')">
<input type="submit" value="Shut Down Raspberry Pi" />
</form>
</td>
<td style="border: none; vertical-align:top;">
<form action="/rascsi/restart" method="post" onsubmit="return confirm('Restart RaSCSI?')">
<input type="submit" value="Restart RaSCSI Service" />
</form>
</td>
</tr>
</table>
{% endblock %}
{% block footer %}
<center><tt>{{version}}</tt></center>
<center><a href="/logs">Logs</a></center>
{% endblock %}

206
src/web/web.py Normal file
View File

@ -0,0 +1,206 @@
import os
from flask import Flask, render_template, request, flash, url_for, redirect, send_file
from werkzeug.utils import secure_filename
from file_cmds import create_new_image, download_file_to_iso, delete_image, unzip_file, download_image
from pi_cmds import shutdown_pi, reboot_pi, running_version, rascsi_service
from ractl_cmds import attach_image, list_devices, is_active, list_files, detach_by_id, eject_by_id, get_valid_scsi_ids
from settings import *
app = Flask(__name__)
@app.route('/')
def index():
devices = list_devices()
scsi_ids = get_valid_scsi_ids(devices)
return render_template('index.html',
devices=devices,
active=is_active(),
files=list_files(),
base_dir=base_dir,
scsi_ids=scsi_ids,
max_file_size=MAX_FILE_SIZE,
version=running_version())
@app.route('/logs')
def logs():
import subprocess
lines = request.args.get('lines') or "100"
process = subprocess.run(["journalctl", "-n", lines], capture_output=True)
if process.returncode == 0:
headers = { 'content-type':'text/plain' }
return process.stdout.decode("utf-8"), 200, headers
else:
flash(u'Failed to get logs')
flash(process.stdout.decode("utf-8"), 'stdout')
flash(process.stderr.decode("utf-8"), 'stderr')
return redirect(url_for('index'))
@app.route('/scsi/attach', methods=['POST'])
def attach():
file_name = request.form.get('file_name')
scsi_id = request.form.get('scsi_id')
# Validate image type by suffix
if file_name.lower().endswith('.iso') or file_name.lower().endswith('iso'):
image_type = "cd"
elif file_name.lower().endswith('.hda'):
image_type = "hd"
else:
flash(u'Unknown file type. Valid files are .iso, .hda, .cdr', 'error')
return redirect(url_for('index'))
process = attach_image(scsi_id, file_name, image_type)
if process.returncode == 0:
flash('Attached '+ file_name + " to scsi id " + scsi_id + "!")
return redirect(url_for('index'))
else:
flash(u'Failed to attach '+ file_name + " to scsi id " + scsi_id + "!", 'error')
flash(process.stdout.decode("utf-8"), 'stdout')
flash(process.stderr.decode("utf-8"), 'stderr')
return redirect(url_for('index'))
@app.route('/scsi/detach', methods=['POST'])
def detach():
scsi_id = request.form.get('scsi_id')
process = detach_by_id(scsi_id)
if process.returncode == 0:
flash("Detached scsi id " + scsi_id + "!")
return redirect(url_for('index'))
else:
flash(u"Failed to detach scsi id " + scsi_id + "!", 'error')
flash(process.stdout, 'stdout')
flash(process.stderr, 'stderr')
return redirect(url_for('index'))
@app.route('/scsi/eject', methods=['POST'])
def eject():
scsi_id = request.form.get('scsi_id')
process = eject_by_id(scsi_id)
if process.returncode == 0:
flash("Ejected scsi id " + scsi_id + "!")
return redirect(url_for('index'))
else:
flash(u"Failed to eject scsi id " + scsi_id + "!", 'error')
flash(process.stdout, 'stdout')
flash(process.stderr, 'stderr')
return redirect(url_for('index'))
@app.route('/pi/reboot', methods=['POST'])
def restart():
reboot_pi()
flash("Restarting...")
return redirect(url_for('index'))
@app.route('/rascsi/restart', methods=['POST'])
def rascsi_restart():
rascsi_service("restart")
flash("Restarting RaSCSI Service...")
return redirect(url_for('index'))
@app.route('/pi/shutdown', methods=['POST'])
def shutdown():
shutdown_pi()
flash("Shutting down...")
return redirect(url_for('index'))
@app.route('/files/download_to_iso', methods=['POST'])
def download_file():
scsi_id = request.form.get('scsi_id')
url = request.form.get('url')
process = download_file_to_iso(scsi_id, url)
if process.returncode == 0:
flash("File Downloaded")
return redirect(url_for('index'))
else:
flash(u"Failed to download file", 'error')
flash(process.stdout, 'stdout')
flash(process.stderr, 'stderr')
return redirect(url_for('index'))
@app.route('/files/download_image', methods=['POST'])
def download_img():
url = request.form.get('url')
# TODO: error handling
download_image(url)
flash("File Downloaded")
return redirect(url_for('index'))
@app.route('/files/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
flash('No file part', 'error')
return redirect(url_for('index'))
file = request.files['file']
if file:
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('index', filename=filename))
@app.route('/files/create', methods=['POST'])
def create_file():
file_name = request.form.get('file_name')
size = request.form.get('size')
type = request.form.get('type')
process = create_new_image(file_name, type, size)
if process.returncode == 0:
flash("Drive created")
return redirect(url_for('index'))
else:
flash(u"Failed to create file", 'error')
flash(process.stdout, 'stdout')
flash(process.stderr, 'stderr')
return redirect(url_for('index'))
@app.route('/files/download', methods=['POST'])
def download():
image = request.form.get('image')
return send_file(base_dir + image, as_attachment=True)
@app.route('/files/delete', methods=['POST'])
def delete():
image = request.form.get('image')
if delete_image(image):
flash("File " + image + " deleted")
return redirect(url_for('index'))
else:
flash(u"Failed to Delete " + image, 'error')
return redirect(url_for('index'))
@app.route('/files/unzip', methods=['POST'])
def unzip():
image = request.form.get('image')
if unzip_file(image):
flash("Unzipped file " + image)
return redirect(url_for('index'))
else:
flash(u"Failed to unzip " + image, 'error')
return redirect(url_for('index'))
if __name__ == "__main__":
app.secret_key = 'rascsi_is_awesome_insecure_secret_key'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['UPLOAD_FOLDER'] = base_dir
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE
from waitress import serve
serve(app, host="0.0.0.0", port=8080)