mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-21 23:29:39 +00:00
Web Interface i18n (#564)
* Add flask_babel 2.0.0 to requirements * Partial i18n * Use current locale for protobuf requests * Don't store generated messages.pot in git * Internationalize all python code * Formatting fixes * Partial internationalization of html * Iterate on html i18n * Completed i18n of code * Improve i18n of strings * Blurb about i18n in the readme * Improve i18n strings * Add the compiled messages.mo files to .gitignore * Add complete Swedish localization * Generate localizations in start.sh * Only compile messages.mo in start.sh; better sequence * Fix bug
This commit is contained in:
parent
c19c814863
commit
ab82d6e4eb
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,3 +11,5 @@ src/oled_monitor/current
|
||||
src/oled_monitor/rascsi_interface_pb2.py
|
||||
src/raspberrypi/hfdisk/
|
||||
*~
|
||||
messages.pot
|
||||
messages.mo
|
||||
|
@ -46,3 +46,15 @@ $ cd ~/source/RASCSI
|
||||
$ git remote add pi ssh://pi@rascsi/home/pi/dev.git
|
||||
$ git push pi master
|
||||
```
|
||||
|
||||
## Localizing the Web Interface
|
||||
|
||||
We use the Flask-Babel library and Flask/Jinja2 extension for i18n.
|
||||
|
||||
To create a new localization, it needs to be added to accept_languages in
|
||||
the get_locale() method, and also to localizer.cpp in the RaSCSI C++ code.
|
||||
|
||||
Once this is done, follow the steps in the [Flask-Babel documentation](https://flask-babel.tkte.ch/#translating-applications)
|
||||
to generate the messages.po for the new language.
|
||||
|
||||
Updating an existing messages.po is also covered above.
|
||||
|
3
src/web/babel.cfg
Normal file
3
src/web/babel.cfg
Normal file
@ -0,0 +1,3 @@
|
||||
[python: **.py]
|
||||
[jinja2: **/templates/**.html]
|
||||
extensions=jinja2.ext.autoescape,jinja2.ext.with_
|
@ -6,6 +6,7 @@ import os
|
||||
import logging
|
||||
from pathlib import PurePath
|
||||
from flask import current_app
|
||||
from flask_babel import _
|
||||
|
||||
from ractl_cmds import (
|
||||
get_server_info,
|
||||
@ -65,6 +66,7 @@ def list_images():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -121,6 +123,7 @@ def create_new_image(file_name, file_type, size):
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.CREATE_IMAGE
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
command.params["file"] = file_name + "." + file_type
|
||||
command.params["size"] = str(size)
|
||||
@ -141,6 +144,7 @@ def delete_image(file_name):
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DELETE_IMAGE
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
command.params["file"] = file_name
|
||||
|
||||
@ -159,6 +163,7 @@ def rename_image(file_name, new_file_name):
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.RENAME_IMAGE
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
command.params["from"] = file_name
|
||||
command.params["to"] = new_file_name
|
||||
@ -176,8 +181,14 @@ def delete_file(file_path):
|
||||
"""
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
return {"status": True, "msg": f"File deleted: {file_path}"}
|
||||
return {"status": False, "msg": f"File to delete not found: {file_path}"}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(u"File deleted: %(file_path)s", file_path=file_path),
|
||||
}
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"File to delete not found: %(file_path)s", file_path=file_path),
|
||||
}
|
||||
|
||||
|
||||
def rename_file(file_path, target_path):
|
||||
@ -187,8 +198,14 @@ def rename_file(file_path, target_path):
|
||||
"""
|
||||
if os.path.exists(PurePath(target_path).parent):
|
||||
os.rename(file_path, target_path)
|
||||
return {"status": True, "msg": f"File moved to: {target_path}"}
|
||||
return {"status": False, "msg": f"Unable to move to: {target_path}"}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(u"File moved to: %(target_path)s", target_path=target_path),
|
||||
}
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"Unable to move file to: %(target_path)s", target_path=target_path),
|
||||
}
|
||||
|
||||
|
||||
def unzip_file(file_name, member=False, members=False):
|
||||
@ -303,7 +320,7 @@ def download_file_to_iso(url, *iso_args):
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"msg": f"Created CD-ROM ISO image with arguments \"" + " ".join(iso_args) + "\"",
|
||||
"msg": _(u"Created CD-ROM ISO image with arguments \"%(value)s\"", value=" ".join(iso_args)),
|
||||
"file_name": iso_filename,
|
||||
}
|
||||
|
||||
@ -331,7 +348,14 @@ def download_to_dir(url, save_dir):
|
||||
logging.info("Response content-type: %s", req.headers["content-type"])
|
||||
logging.info("Response status code: %s", req.status_code)
|
||||
|
||||
return {"status": True, "msg": f"{file_name} downloaded to {save_dir}"}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(
|
||||
u"%(file_name)s downloaded to %(save_dir)s",
|
||||
file_name=file_name,
|
||||
save_dir=save_dir,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def write_config(file_name):
|
||||
@ -369,7 +393,7 @@ def write_config(file_name):
|
||||
json_file,
|
||||
indent=4
|
||||
)
|
||||
return {"status": True, "msg": f"Saved config to {file_name}"}
|
||||
return {"status": True, "msg": _(u"Saved configuration file to %(file_name)s", file_name=file_name)}
|
||||
except (IOError, ValueError, EOFError, TypeError) as error:
|
||||
logging.error(str(error))
|
||||
delete_file(file_name)
|
||||
@ -377,7 +401,10 @@ def write_config(file_name):
|
||||
except:
|
||||
logging.error("Could not write to file: %s", file_name)
|
||||
delete_file(file_name)
|
||||
return {"status": False, "msg": f"Could not write to file: {file_name}"}
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"Could not write to file: %(file_name)s", file_name=file_name),
|
||||
}
|
||||
|
||||
|
||||
def read_config(file_name):
|
||||
@ -434,14 +461,20 @@ def read_config(file_name):
|
||||
kwargs[param] = params[param]
|
||||
attach_image(row["id"], **kwargs)
|
||||
else:
|
||||
return {"status": False, "msg": "Invalid config file format."}
|
||||
return {"status": True, "msg": f"Loaded config from: {file_name}"}
|
||||
return {"status": False, "msg": _(u"Invalid configuration file format")}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(u"Loaded configurations from: %(file_name)s", file_name=file_name),
|
||||
}
|
||||
except (IOError, ValueError, EOFError, TypeError) as error:
|
||||
logging.error(str(error))
|
||||
return {"status": False, "msg": str(error)}
|
||||
except:
|
||||
logging.error("Could not read file: %s", file_name)
|
||||
return {"status": False, "msg": f"Could not read file: {file_name}"}
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"Could not read configuration file: %(file_name)s", file_name=file_name),
|
||||
}
|
||||
|
||||
|
||||
def write_drive_properties(file_name, conf):
|
||||
@ -455,7 +488,10 @@ def write_drive_properties(file_name, conf):
|
||||
try:
|
||||
with open(file_path, "w") as json_file:
|
||||
dump(conf, json_file, indent=4)
|
||||
return {"status": True, "msg": f"Created file: {file_path}"}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(u"Created properties file: %(file_path)s", file_path=file_path),
|
||||
}
|
||||
except (IOError, ValueError, EOFError, TypeError) as error:
|
||||
logging.error(str(error))
|
||||
delete_file(file_path)
|
||||
@ -463,23 +499,33 @@ def write_drive_properties(file_name, conf):
|
||||
except:
|
||||
logging.error("Could not write to file: %s", file_path)
|
||||
delete_file(file_path)
|
||||
return {"status": False, "msg": f"Could not write to file: {file_path}"}
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"Could not write to properties file: %(file_path)s", file_path=file_path),
|
||||
}
|
||||
|
||||
|
||||
def read_drive_properties(path_name):
|
||||
def read_drive_properties(file_path):
|
||||
"""
|
||||
Reads drive properties from json formatted file.
|
||||
Takes (str) path_name as argument.
|
||||
Takes (str) file_path as argument.
|
||||
Returns (dict) with (bool) status, (str) msg, (dict) conf
|
||||
"""
|
||||
from json import load
|
||||
try:
|
||||
with open(path_name) as json_file:
|
||||
with open(file_path) as json_file:
|
||||
conf = load(json_file)
|
||||
return {"status": True, "msg": f"Read from file: {path_name}", "conf": conf}
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _(u"Read properties from file: %(file_path)s", file_path=file_path),
|
||||
"conf": conf,
|
||||
}
|
||||
except (IOError, ValueError, EOFError, TypeError) as error:
|
||||
logging.error(str(error))
|
||||
return {"status": False, "msg": str(error)}
|
||||
except:
|
||||
logging.error("Could not read file: %s", path_name)
|
||||
return {"status": False, "msg": f"Could not read file: {path_name}"}
|
||||
logging.error("Could not read file: %s", file_path)
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _(u"Could not read properties from file: %(file_path)s", file_path=file_path),
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ Module for methods controlling and getting information about the Pi's Linux syst
|
||||
import subprocess
|
||||
import asyncio
|
||||
import logging
|
||||
from flask_babel import _
|
||||
from settings import AUTH_GROUP
|
||||
|
||||
|
||||
@ -175,6 +176,6 @@ def auth_active():
|
||||
if AUTH_GROUP in groups:
|
||||
return {
|
||||
"status": True,
|
||||
"msg": "You must log in to use this function!",
|
||||
"msg": _(u"You must log in to use this function"),
|
||||
}
|
||||
return {"status": False, "msg": ""}
|
||||
|
@ -5,6 +5,7 @@ Module for commands sent to the RaSCSI backend service.
|
||||
from settings import REMOVABLE_DEVICE_TYPES
|
||||
from socket_cmds import send_pb_command
|
||||
from flask import current_app
|
||||
from flask_babel import _
|
||||
import rascsi_interface_pb2 as proto
|
||||
|
||||
|
||||
@ -24,6 +25,7 @@ def get_server_info():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.SERVER_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -82,6 +84,7 @@ def get_reserved_ids():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.RESERVED_IDS_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -103,6 +106,7 @@ def get_network_info():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.NETWORK_INTERFACES_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -121,6 +125,7 @@ def get_device_types():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICE_TYPES_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -142,6 +147,8 @@ def get_image_files_info():
|
||||
"""
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -170,6 +177,7 @@ def attach_image(scsi_id, **kwargs):
|
||||
"""
|
||||
command = proto.PbCommand()
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
|
||||
@ -195,8 +203,12 @@ def attach_image(scsi_id, **kwargs):
|
||||
if current_type != device_type:
|
||||
return {
|
||||
"status": False,
|
||||
"msg": "Cannot insert an image for " + device_type + \
|
||||
" into a " + current_type + " device."
|
||||
"msg": _(
|
||||
u"Cannot insert an image for %(device_type)s into a "
|
||||
u"%(current_device_type)s device",
|
||||
device_type=device_type,
|
||||
current_device_type=current_type
|
||||
),
|
||||
}
|
||||
command.operation = proto.PbOperation.INSERT
|
||||
# Handling attaching a new device
|
||||
@ -241,6 +253,7 @@ def detach_by_id(scsi_id, unit=None):
|
||||
command.operation = proto.PbOperation.DETACH
|
||||
command.devices.append(devices)
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -256,6 +269,7 @@ def detach_all():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DETACH_ALL
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -278,6 +292,7 @@ def eject_by_id(scsi_id, unit=None):
|
||||
command.operation = proto.PbOperation.EJECT
|
||||
command.devices.append(devices)
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -297,6 +312,7 @@ def list_devices(scsi_id=None, unit=None):
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICES_INFO
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
# If method is called with scsi_id parameter, return the info on those devices
|
||||
# Otherwise, return the info on all attached devices
|
||||
@ -374,6 +390,7 @@ def reserve_scsi_ids(reserved_scsi_ids):
|
||||
command.operation = proto.PbOperation.RESERVE_IDS
|
||||
command.params["ids"] = ",".join(reserved_scsi_ids)
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -391,6 +408,7 @@ def set_log_level(log_level):
|
||||
command.operation = proto.PbOperation.LOG_LEVEL
|
||||
command.params["level"] = str(log_level)
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -408,6 +426,7 @@ def shutdown_pi(mode):
|
||||
command.operation = proto.PbOperation.SHUT_DOWN
|
||||
command.params["mode"] = str(mode)
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
@ -424,6 +443,7 @@ def is_token_auth():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.CHECK_AUTHENTICATION
|
||||
command.params["token"] = current_app.config["TOKEN"]
|
||||
command.params["locale"] = current_app.config["LOCALE"]
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
|
@ -7,3 +7,4 @@ MarkupSafe==2.0.1
|
||||
protobuf==3.17.3
|
||||
requests==2.26.0
|
||||
simplepam==0.1.5
|
||||
flask_babel==2.0.0
|
||||
|
@ -4,6 +4,7 @@ Module for sending and receiving data over a socket connection with the RaSCSI b
|
||||
|
||||
import logging
|
||||
from flask import abort
|
||||
from flask_babel import _
|
||||
from time import sleep
|
||||
|
||||
def send_pb_command(payload):
|
||||
@ -35,9 +36,12 @@ def send_pb_command(payload):
|
||||
logging.error(error_msg)
|
||||
|
||||
# After failing all attempts, throw a 404 error
|
||||
abort(404, "The RaSCSI Web Interface failed to connect to RaSCSI at " + str(host) + \
|
||||
":" + str(port) + " with error: " + error_msg + \
|
||||
". The RaSCSI service is not running or may have crashed.")
|
||||
abort(404, _(
|
||||
u"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s "
|
||||
u"with error: %(error_msg)s. The RaSCSI process is not running or may have crashed.",
|
||||
host=host, port=port, error_msg=error_msg,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def send_over_socket(sock, payload):
|
||||
@ -72,9 +76,11 @@ def send_over_socket(sock, payload):
|
||||
"RaSCSI may have crashed."
|
||||
)
|
||||
abort(
|
||||
503, "The RaSCSI Web Interface lost connection to RaSCSI. "
|
||||
"Please go back and try again. "
|
||||
"If the issue persists, please report a bug."
|
||||
503, _(
|
||||
u"The RaSCSI Web Interface lost connection to RaSCSI. "
|
||||
u"Please go back and try again. "
|
||||
u"If the issue persists, please report a bug."
|
||||
)
|
||||
)
|
||||
chunks.append(chunk)
|
||||
bytes_recvd = bytes_recvd + len(chunk)
|
||||
@ -86,8 +92,9 @@ def send_over_socket(sock, payload):
|
||||
"RaSCSI may have crashed."
|
||||
)
|
||||
abort(
|
||||
500,
|
||||
"The RaSCSI Web Interface did not get a valid response from RaSCSI. "
|
||||
"Please go back and try again. "
|
||||
"If the issue persists, please report a bug."
|
||||
500, _(
|
||||
u"The RaSCSI Web Interface did not get a valid response from RaSCSI. "
|
||||
u"Please go back and try again. "
|
||||
u"If the issue persists, please report a bug."
|
||||
)
|
||||
)
|
||||
|
@ -74,6 +74,8 @@ else
|
||||
fi
|
||||
set -e
|
||||
|
||||
pybabel compile -d translations
|
||||
|
||||
# parse arguments
|
||||
while [ "$1" != "" ]; do
|
||||
PARAM=$(echo "$1" | awk -F= '{print $1}')
|
||||
|
@ -26,12 +26,12 @@
|
||||
|
||||
<script type="application/javascript">
|
||||
var processNotify = function(Notification) {
|
||||
document.getElementById("flash").innerHTML = "<div class='message'>" + Notification + " This process may take a while, and will continue in the background if you navigate away from this page.</div>";
|
||||
document.getElementById("flash").innerHTML = "<div class='message'>" + Notification + "{{ _(" This process may take a while, and will continue in the background if you navigate away from this page.") }}</div>";
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
|
||||
var shutdownNotify = function(Notification) {
|
||||
document.getElementById("flash").innerHTML = "<div class='message'>" + Notification + " The Web Interface will become unresponsive momentarily. Reload this page after the Pi has started up again.</div>";
|
||||
document.getElementById("flash").innerHTML = "<div class='message'>" + Notification + "{{ _(" The Web Interface will become unresponsive momentarily. Reload this page after the Pi has started up again.") }}</div>";
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
</script>
|
||||
@ -45,19 +45,19 @@
|
||||
<div class="header">
|
||||
{% if auth_active %}
|
||||
{% if username %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Logged in as <em>{{ username }}</em> – <a href="/logout">Log Out</a></span>
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">{{ _("Logged in as <em>%(username)s</em>", username=username) }} – <a href="/logout">{{ _("Log Out") }}</a></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;">
|
||||
<form method="POST" action="/login">
|
||||
<div>Log In to Use Web Interface</div>
|
||||
<input type="text" name="username" placeholder="Username">
|
||||
<input type="password" name="password" placeholder="Password">
|
||||
<div>{{ _("Log In to Use Web Interface") }}</div>
|
||||
<input type="text" name="username" placeholder="{{ _("Username") }}">
|
||||
<input type="password" name="password" placeholder="{{ _("Password") }}">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Web Interface Authentication Disabled – See <a href="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication" target="_blank">Wiki</a> for more information</span>
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">{{ _("Web Interface Authentication Disabled") }} – {{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication") }}</span>
|
||||
{% endif %}
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
@ -84,8 +84,8 @@
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<center><tt>RaSCSI version: <strong>{{ version }} <a href="https://github.com/akuker/RASCSI/commit/{{ running_env['git'] }}" target="_blank">{{ running_env["git"][:7] }}</a></strong></tt></center>
|
||||
<center><tt>Pi environment: {{ running_env["env"] }}</tt></center>
|
||||
<center><tt>{{ _("RaSCSI version: ") }}<strong>{{ version }} <a href="https://github.com/akuker/RASCSI/commit/{{ running_env['git'] }}" target="_blank">{{ running_env["git"][:7] }}</a></strong></tt></center>
|
||||
<center><tt>{{ _("Pi environment: ") }}{{ running_env["env"] }}</tt></center>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p><a href="/">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>
|
||||
<p><a href="/">{{ _("Cancel") }}</a></p>
|
||||
<h2>{{ _("Disclaimer") }}</h2>
|
||||
<p>{{ _("These device profiles are provided as-is with no guarantee to work equally to the actual physical device they are named after. You may need to provide appropirate device drivers and/or configuration parameters for them to function properly. If you would like to see data modified, or have additional devices to add to the list, please raise an issue ticket at <a href=\"%(url)s\">GitHub</a>.", url="https://github.com/akuker/RASCSI/issues") }}</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>
|
||||
<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>
|
||||
@ -22,7 +22,7 @@
|
||||
<td style="text-align:left">{{ hd.description }}</td>
|
||||
<td style="text-align:left">
|
||||
{% if hd.url != "" %}
|
||||
<a href="{{ hd.url }}">Link</a>
|
||||
<a href="{{ hd.url }}">{{ _("Link") }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -36,9 +36,9 @@
|
||||
<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>
|
||||
<label for="file_name">{{ _("Save as:") }}</label>
|
||||
<input type="text" name="file_name" value="{{ hd.secure_name }}" required />.{{ hd.file_type }}
|
||||
<input type="submit" value="Create" />
|
||||
<input type="submit" value="{{ _("Create") }}" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -48,16 +48,16 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@ -66,7 +66,7 @@
|
||||
<td style="text-align:left">{{ cd.description }}</td>
|
||||
<td style="text-align:left">
|
||||
{% if cd.url != "" %}
|
||||
<a href="{{ cd.url }}">Link</a>
|
||||
<a href="{{ cd.url }}">{{ _("Link") }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -77,7 +77,7 @@
|
||||
<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>
|
||||
<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) %}
|
||||
@ -85,7 +85,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Create" />
|
||||
<input type="submit" value="{{ _("Create") }}" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -95,15 +95,15 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Removable Drives</h2>
|
||||
<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>
|
||||
<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>
|
||||
@ -112,7 +112,7 @@
|
||||
<td style="text-align:left">{{ rm.description }}</td>
|
||||
<td style="text-align:left">
|
||||
{% if rm.url != "" %}
|
||||
<a href="{{ rm.url }}">Link</a>
|
||||
<a href="{{ rm.url }}">{{ _("Link") }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@ -126,16 +126,16 @@
|
||||
<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>
|
||||
<label for="file_name">{{ _("Save as:") }}</label>
|
||||
<input type="text" name="file_name" value="{{ rm.secure_name }}" required />.{{ rm.file_type }}
|
||||
<input type="submit" value="Create" />
|
||||
<input type="submit" value="{{ _("Create") }}" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><small>Available disk space on the Pi: {{ free_disk }} MB</small></p>
|
||||
<p><a href="/">Cancel</a></p>
|
||||
<p><small>{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}</small></p>
|
||||
<p><a href="/">{{ _("Cancel") }}</a></p>
|
||||
|
||||
{% endblock content %}
|
||||
|
@ -3,12 +3,12 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Current RaSCSI Configuration
|
||||
{{ _("Current RaSCSI Configuration") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Displays the currently attached devices for each available SCSI ID.</li>
|
||||
<li>Save and load device configurations into <tt>{{ CFG_DIR }}</tt></li>
|
||||
<li>The <em>default</em> configuration will be loaded when the Web UI starts up, if available.</li>
|
||||
<li>{{ _("Displays the currently attached devices for each available SCSI ID.") }}</li>
|
||||
<li>{{ _("Save and load device configurations, stored as json files in <tt>%(config_dir)s</tt>", config_dir=CFG_DIR) }}</tt></li>
|
||||
<li>{{ _("To have a particular device configuration load when RaSCSI starts, save it as <em>default</em>.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@ -22,31 +22,31 @@
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option disabled>
|
||||
No saved configs
|
||||
{{ _("No saved configurations") }}
|
||||
</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
<input name="load" type="submit" value="Load" onclick="return confirm('Detach all current device and Load config?')">
|
||||
<input name="delete" type="submit" value="Delete" onclick="return confirm('Delete config file?')">
|
||||
<input name="load" type="submit" value="{{ _("Load") }}" onclick="return confirm('{{ _("Detach all current device and Load configuration?") }}')">
|
||||
<input name="delete" type="submit" value="{{ _("Delete") }}" onclick="return confirm('{{ _("Delete configuration file?") }}')">
|
||||
</form></p>
|
||||
|
||||
<p><form action="/config/save" method="post">
|
||||
<input name="name" placeholder="default" size="20">
|
||||
<input type="submit" value="Save">
|
||||
<input type="submit" value="{{ _("Save") }}">
|
||||
</form></p>
|
||||
|
||||
<table border="black" cellpadding="3">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>ID</b></td>
|
||||
<td><b>{{ _("ID") }}</b></td>
|
||||
{% if units %}
|
||||
<td><b>LUN</b></td>
|
||||
<td><b>{{ _("LUN") }}</b></td>
|
||||
{% endif %}
|
||||
<td><b>Type</b></td>
|
||||
<td><b>Status</b></td>
|
||||
<td><b>File</b></td>
|
||||
<td><b>Product</b></td>
|
||||
<td><b>Actions</b></td>
|
||||
<td><b>{{ _("Type") }}</b></td>
|
||||
<td><b>{{ _("Status") }}</b></td>
|
||||
<td><b>{{ _("File") }}</b></td>
|
||||
<td><b>{{ _("Product") }}</b></td>
|
||||
<td><b>{{ _("Actions") }}</b></td>
|
||||
</tr>
|
||||
{% for device in devices %}
|
||||
<tr>
|
||||
@ -81,7 +81,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Attach">
|
||||
<input type="submit" value="{{ _("Attach") }}">
|
||||
</form>
|
||||
{% else %}
|
||||
{{ device.file }}
|
||||
@ -95,28 +95,28 @@
|
||||
<td style="text-align:center">
|
||||
{% if device.device_type != "-" %}
|
||||
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
|
||||
<form action="/scsi/eject" method="post" onsubmit="return confirm('Eject Disk? WARNING: On Mac OS, eject the Disk in Finder instead!')">
|
||||
<form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="Eject">
|
||||
<input type="submit" value="{{ _("Eject") }}">
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Device?')">
|
||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('{{ _("Detach Device?") }}')">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="Detach">
|
||||
<input type="submit" value="{{ _("Detach") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
<form action="/scsi/info" method="post">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="Info">
|
||||
<input type="submit" value="{{ _("Info") }}">
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/scsi/reserve" method="post" onsubmit="var memo = prompt('Enter a memo for this reservation'); if (memo === null) event.preventDefault(); document.getElementById('memo_{{ device.id }}').value = memo;">
|
||||
<form action="/scsi/reserve" method="post" onsubmit="var memo = prompt('{{ _("Enter a memo for this reservation") }}'); if (memo === null) event.preventDefault(); document.getElementById('memo_{{ device.id }}').value = memo;">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="memo" id="memo_{{ device.id }}" type="hidden" value="">
|
||||
<input type="submit" value="Reserve">
|
||||
<input type="submit" value="{{ _("Reserve") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
@ -126,13 +126,13 @@
|
||||
<td class="inactive"></td>
|
||||
{% endif %}
|
||||
<td class="inactive"></td>
|
||||
<td class="inactive">Reserved ID</td>
|
||||
<td class="inactive">{{ _("Reserved ID") }}</td>
|
||||
<td class="inactive">{{ RESERVATIONS[device.id] }}</td>
|
||||
<td class="inactive"></td>
|
||||
<td class="inactive">
|
||||
<form action="/scsi/unreserve" method="post">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input type="submit" value="Unreserve">
|
||||
<input type="submit" value="{{ _("Unreserve") }}">
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
@ -141,31 +141,31 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><form action="/scsi/detach_all" method="post" onsubmit="return confirm('Detach all SCSI Devices?')">
|
||||
<input type="submit" value="Detach All Devices">
|
||||
<p><form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
|
||||
<input type="submit" value="{{ _("Detach All Devices") }}">
|
||||
</form></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Image File Management
|
||||
{{ _("Image File Management") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Manage image files in the active RaSCSI image directory: <tt>{{ base_dir }}</tt> with a scan depth of {{ scan_depth }}.</li>
|
||||
<li>Select a valid SCSI ID and <a href="https://en.wikipedia.org/wiki/Logical_unit_number">LUN</a> to attach to. Unless you know what you're doing, always use LUN 0.
|
||||
<li>{{ _("Manage image files in the active RaSCSI image directory: <tt>%(directory)s</tt> with a scan depth of %(scan_depth)s.", directory=base_dir, scan_depth=scan_depth) }}</li>
|
||||
<li>{{ _("Select a valid SCSI ID and <a href=\"%(url)s\">LUN</a> to attach to. Unless you know what you're doing, always use LUN 0.", url="https://en.wikipedia.org/wiki/Logical_unit_number") }}
|
||||
</li>
|
||||
<li>If RaSCSI was unable to detect the device type associated with the image, you can choose the type from the dropdown.</li>
|
||||
<li>Types: SAHD = SASI HDD | SCHD = SCSI HDD | SCRM = Removable | SCMO = Magneto-Optical | SCCD = CD-ROM | SCBR = Host Bridge | SCDP = DaynaPORT</li>
|
||||
<li>{{ _("If RaSCSI was unable to detect the device type associated with the image, you can choose the type from the dropdown.") }}</li>
|
||||
<li>{{ _("Types: SAHD = SASI HDD | SCHD = SCSI HDD | SCRM = Removable | SCMO = Magneto-Optical | SCCD = CD-ROM | SCBR = Host Bridge | SCDP = DaynaPORT") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<table border="black" cellpadding="3">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>File</b></td>
|
||||
<td><b>Size</b></td>
|
||||
<td><b>Actions</b></td>
|
||||
<td><b>{{ _("File") }}</b></td>
|
||||
<td><b>{{ _("Size") }}</b></td>
|
||||
<td><b>{{ _("Actions") }}</b></td>
|
||||
</tr>
|
||||
{% for file in files %}
|
||||
<tr>
|
||||
@ -181,7 +181,7 @@
|
||||
{% endfor %}
|
||||
<form action="/files/download" method="post">
|
||||
<input name="file" type="hidden" value="{{ CFG_DIR }}/{{ file['name'].replace(base_dir, '') }}.{{ PROPERTIES_SUFFIX }}">
|
||||
<input type="submit" value="Properties File ↓">
|
||||
<input type="submit" value="{{ _("Properties File") }} ↓">
|
||||
</form>
|
||||
</ul>
|
||||
</details>
|
||||
@ -201,7 +201,7 @@
|
||||
<form action="/files/unzip" method="post">
|
||||
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
|
||||
<input name="zip_member" type="hidden" value="{{ member }}">
|
||||
<input type="submit" value="Unzip" onclick="processNotify('Unzipping a single file...')">
|
||||
<input type="submit" value="{{ _("Unzip") }}" onclick="processNotify('{{ _("Unzipping a single file...") }}')">
|
||||
</form>
|
||||
</summary>
|
||||
<ul style="list-style: none;">
|
||||
@ -215,7 +215,7 @@
|
||||
<form action="/files/unzip" method="post">
|
||||
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
|
||||
<input name="zip_member" type="hidden" value="{{ member }}">
|
||||
<input type="submit" value="Unzip" onclick="processNotify('Unzipping a single file...')">
|
||||
<input type="submit" value="{{ _("Unzip") }}" onclick="processNotify('{{ _("Unzipping a single file...") }}')">
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
@ -230,26 +230,26 @@
|
||||
<td style="text-align:center">
|
||||
<form action="/files/download" method="post">
|
||||
<input name="file" type="hidden" value="{{ base_dir }}/{{ file['name'] }}">
|
||||
<input type="submit" value="{{ file['size_mb'] }} MB ↓">
|
||||
<input type="submit" value="{{ file['size_mb'] }} {{ _("MB") }} ↓">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
{% if file["name"] in attached_images %}
|
||||
<center>
|
||||
Attached!
|
||||
{{ _("Attached!") }}
|
||||
</center>
|
||||
{% else %}
|
||||
{% if file["name"].lower().endswith(ARCHIVE_FILE_SUFFIX) %}
|
||||
<form action="/files/unzip" method="post">
|
||||
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
|
||||
<input name="zip_members" type="hidden" value="{{ file['zip_members'] }}">
|
||||
<input type="submit" value="Unzip All" onclick="processNotify('Unzipping all files...')">
|
||||
<input type="submit" value="{{ _("Unzip All") }}" onclick="processNotify('{{ _("Unzipping all files...") }}')">
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/scsi/attach" method="post">
|
||||
<input name="file_name" type="hidden" value="{{ file['name'] }}">
|
||||
<input name="file_size" type="hidden" value="{{ file['size'] }}">
|
||||
<label for="id">ID</label>
|
||||
<label for="id">{{ _("ID") }}</label>
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
<option name="id" value="{{id}}"{% if id == recommended_id %} selected{% endif %}>
|
||||
@ -257,7 +257,7 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="unit">LUN</label>
|
||||
<label for="unit">{{ _("LUN") }}</label>
|
||||
<input name="unit" type="number" size="2" value="0" min="0" max="31">
|
||||
{% if file["detected_type"] != "UNDEFINED" %}
|
||||
<input name="type" type="hidden" value="{{ file['detected_type'] }}">
|
||||
@ -265,7 +265,7 @@
|
||||
{% else %}
|
||||
<select name="type">
|
||||
<option selected value="">
|
||||
Type
|
||||
{{ _("Type") }}
|
||||
</option>
|
||||
{% for d in device_types %}
|
||||
<option value="{{ d }}">
|
||||
@ -274,17 +274,17 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
<input type="submit" value="Attach">
|
||||
<input type="submit" value="{{ _("Attach") }}">
|
||||
{% endif %}
|
||||
</form>
|
||||
<form action="/files/rename" method="post" onsubmit="var new_file_name = prompt('Enter new file name for \'{{ file["name"] }}\'', '{{ file['name'] }}'); if (new_file_name === null) event.preventDefault(); document.getElementById('new_file_name_{{ loop.index }}').value = new_file_name;">
|
||||
<form action="/files/rename" method="post" onsubmit="var new_file_name = prompt('{{ _("Enter new file name for: %(file_name)s", file_name=file["name"]) }}', '{{ file['name'] }}'); if (new_file_name === null) event.preventDefault(); document.getElementById('new_file_name_{{ loop.index }}').value = new_file_name;">
|
||||
<input name="file_name" type="hidden" value="{{ file['name'] }}">
|
||||
<input name="new_file_name" id="new_file_name_{{ loop.index }}" type="hidden" value="">
|
||||
<input type="submit" value="Rename">
|
||||
<input type="submit" value="{{ _("Rename") }}">
|
||||
</form>
|
||||
<form action="/files/delete" method="post" onsubmit="return confirm('Delete file \'{{ file["name"] }}\'?')">
|
||||
<form action="/files/delete" method="post" onsubmit="return confirm('{{ _("Delete file: %(file_name)s?", file_name=file["name"]) }}')">
|
||||
<input name="file_name" type="hidden" value="{{ file['name'] }}">
|
||||
<input type="submit" value="Delete">
|
||||
<input type="submit" value="{{ _("Delete") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
@ -292,22 +292,22 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><small>Available disk space on the Pi: {{ free_disk }} MB</small></p>
|
||||
<p><small>{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}</small></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Attach Ethernet Adapter
|
||||
{{ _("Attach Ethernet Adapter") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Emulates a SCSI DaynaPORT Ethernet Adapter. <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#-macintosh-setup-instructions">Host drivers and configuration required</a>.
|
||||
<li>{{ _("Emulates a SCSI DaynaPORT Ethernet Adapter. <a href=\"%(url)s\">Host drivers and configuration required</a>.", url="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link") }}
|
||||
</li>
|
||||
<li>If you have a DHCP setup, choose only the interface, and ignore the Static IP fields when attaching.</li>
|
||||
<li>Configure network forwarding by running easyinstall.sh, or follow the <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#manual-setup">manual steps in the wiki</a>.
|
||||
<li>{{ _("If you have a DHCP setup, choose only the interface you have configured the bridge with. You can ignore the Static IP fields when attaching.") }}</li>
|
||||
<li>{{ _("Configure the network bridge by running easyinstall.sh, or follow the <a href=\"%(url)s\">manual steps in the wiki</a>.", url="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#manual-setup") }}
|
||||
</li>
|
||||
<li style="list-style: none">{% if bridge_configured %}</li>
|
||||
<li>The <tt>rascsi_bridge</tt> interface is active and ready to be used by DaynaPORT!</li>
|
||||
<li>{{ _("The <tt>rascsi_bridge</tt> interface is active and ready to be used by DaynaPORT!") }}</li>
|
||||
<li style="list-style: none">{% endif %}</li>
|
||||
</ul>
|
||||
</details>
|
||||
@ -315,7 +315,7 @@
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/daynaport/attach" method="post">
|
||||
<label for="if">Interface:</label>
|
||||
<label for="if">{{ _("Interface:") }}</label>
|
||||
<select name="if">
|
||||
{% for if in netinfo["ifs"] %}
|
||||
<option value="{{ if }}">
|
||||
@ -323,10 +323,10 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="ip">Static IP (optional):</label>
|
||||
<label for="ip">{{ _("Static IP (optional):") }}</label>
|
||||
<input name="ip" type="text" size="15" placeholder="10.10.20.1" minlength="7" maxlength="15" pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
|
||||
<input name="mask" type="number" size="2" placeholder="24" min="16" max="30">
|
||||
<label for="scsi_id">SCSI ID:</label>
|
||||
<label for="scsi_id">{{ _("SCSI ID:") }}</label>
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
<option value="{{ id }}"{% if id == recommended_id %} selected{% endif %}>
|
||||
@ -334,26 +334,26 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Attach">
|
||||
<input type="submit" value="{{ _("Attach") }}">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if macproxy_configured %}
|
||||
<p><small>Macproxy is running at {{ ip_addr }} port 5000</small></p>
|
||||
<p><small>{{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=ip_addr) }}</small></p>
|
||||
{% else %}
|
||||
<p><small>Install <a href="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy">Macproxy</a> to browse the Web with any vintage browser. It's not just for Macs!</small></p>
|
||||
<p><small>{{ _("Install <a href=\"%(url)s\">Macproxy</a> to browse the Web with any vintage browser. It's not just for Macs!", url="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy") }}</small></p>
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Upload File
|
||||
{{ _("Upload File") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Uploads file to <tt>{{ base_dir }}</tt>. The largest file size accepted is {{ max_file_size }} MB.</li>
|
||||
<li>For unrecognized file types, try renaming hard drive images to '.hds' and CD-ROM images to '.iso' before uploading.</li>
|
||||
<li>Recognized file types: {{ valid_file_suffix }}</li>
|
||||
<li>{{ _("Uploads file to <tt>%(directory)s</tt>. The largest file size accepted is %(max_file_size)s MB.", directory=base_dir, max_file_size=max_file_size) }}</li>
|
||||
<li>{{ _("For unrecognized file types, try renaming hard drive images to '.hds', CD-ROM images to '.iso', and removable drive images to '.hdr' before uploading.") }}</li>
|
||||
<li>{{ _("Recognized file types: %(valid_file_suffix)s", valid_file_suffix=valid_file_suffix) }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@ -380,10 +380,10 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Download File to Images
|
||||
{{ _("Download File to Images") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Given a URL, download that file to the <tt>{{ base_dir }}</tt> directory.</li>
|
||||
<li>{{ _("Given a URL, download that file to the <tt>%(directory)s</tt> directory.", directory=base_dir) }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@ -391,9 +391,9 @@
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/files/download_to_images" method="post">
|
||||
<label for="url">URL:</label>
|
||||
<input name="url" placeholder="URL" required="" type="url">
|
||||
<input type="submit" value="Download" onclick="processNotify('Downloading File to Images...')">
|
||||
<label for="url">{{ _("URL:") }}</label>
|
||||
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
|
||||
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File to Images...") }}')">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -403,13 +403,12 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Download File to AppleShare
|
||||
{{ _("Download File to AppleShare") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Given a URL, download that file to the <tt>{{ AFP_DIR }}</tt> directory and share it over AFP.</li>
|
||||
<li>Manage the files you download here through AppleShare on your vintage Mac.</li>
|
||||
<li>Requires <a href="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing">Netatalk</a> to be installed and configured correctly for your network.
|
||||
</li>
|
||||
<li>{{ _("Given a URL, download that file to the <tt>%(directory)s</tt> directory and share it over AFP.", directory=AFP_DIR) }}</li>
|
||||
<li>{{ _("Manage the files you download here through AppleShare on your vintage Mac.") }}</li>
|
||||
<li>{{ _("Requires <a href=\"%(url)s\">Netatalk</a> to be installed and configured correctly for your network.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@ -418,43 +417,42 @@
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/files/download_to_afp" method="post">
|
||||
<label for="url">URL:</label>
|
||||
<input name="url" placeholder="URL" required="" type="url">
|
||||
<input type="submit" value="Download" onclick="processNotify('Downloading File to AppleShare...')">
|
||||
<label for="url">{{ _("URL:") }}</label>
|
||||
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
|
||||
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File to AppleShare...") }}')">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if netatalk_configured == 1 %}
|
||||
<p><small>The AppleShare server is running. No active connections</small></p>
|
||||
<p><small>{{ _("The AppleShare server is running. No active connections.") }}</small></p>
|
||||
{% elif netatalk_configured == 2 %}
|
||||
<p><small>{{ netatalk_configured - 1 }} active AFP connection</small></p>
|
||||
<p><small>{{ _("%(value)d active AFP connection", value=(netatalk_configured - 1)) }}</small></p>
|
||||
{% elif netatalk_configured > 2 %}
|
||||
<p><small>{{ netatalk_configured - 1 }} active AFP connections</small></p>
|
||||
<p><small>{{ _("%(value)d active AFP connections", value=(netatalk_configured - 1)) }}</small></p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>Install <a href="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing">Netatalk</a> to use the AppleShare File Server.</p>
|
||||
<p>{{ _("Install <a href=\"%(url)s\">Netatalk</a> to use the AppleShare File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</p>
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Download File and Create CD-ROM ISO image
|
||||
{{ _("Download File and Create CD-ROM image") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Given a URL this will download a file, create a CD-ROM image with the selected file system, and mount it on the SCSI ID given.</li>
|
||||
<li>HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX.</li>
|
||||
<li>On Mac OS, requires a <a href="https://github.com/akuker/RASCSI/wiki/Drive-Setup#Mounting_CD_ISO_or_MO_images">compatible CD-ROM driver</a> installed on the target system.
|
||||
</li>
|
||||
<li>If the target file is a zip archive, we will attempt to unzip it and store the resulting files only.</li>
|
||||
<li>{{ _("Create an ISO file system CD-ROM image with the downloaded file, and mount it on the given SCSI ID.") }}</li>
|
||||
<li>{{ _("HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX.") }}</li>
|
||||
<li>{{ _("On Mac OS, a <a href=\"%(url)s\">compatible CD-ROM driver</a> is required.", url="https://github.com/akuker/RASCSI/wiki/Drive-Setup#Mounting_CD_ISO_or_MO_images") }}</li>
|
||||
<li>{{ _("If the downloaded file is a zip archive, we will attempt to unzip it and store the resulting files.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<label for="scsi_id">SCSI ID:</label>
|
||||
<label for="scsi_id">{{ _("SCSI ID:") }}</label>
|
||||
<form action="/files/download_to_iso" method="post">
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
@ -463,9 +461,9 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="url">URL:</label>
|
||||
<input name="url" placeholder="URL" required="" type="url">
|
||||
<label for="type">Type:</label>
|
||||
<label for="url">{{ _("URL:") }}</label>
|
||||
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
|
||||
<label for="type">{{ _("Type:") }}</label>
|
||||
<select name="type">
|
||||
<option value="-hfs">
|
||||
HFS
|
||||
@ -486,7 +484,7 @@
|
||||
Rock Ridge
|
||||
</option>
|
||||
</select>
|
||||
<input type="submit" value="Download and Mount ISO" onclick="processNotify('Downloading File as ISO...')">
|
||||
<input type="submit" value="{{ _("Download and Mount CD-ROM image") }}" onclick="processNotify('{{ _("Downloading File and generating CD-ROM image...") }}')">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -496,41 +494,41 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Create Empty Disk Image File
|
||||
{{ _("Create Empty Disk Image File") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>The Generic image type is recommended for most systems</li>
|
||||
<li>APPLE GENUINE and NEC GENUINE image types will make RaSCSI masquerade as a particular drive type that are recognized by Mac and PC98 systems, respectively.</li>
|
||||
<li>SASI images should only be used on early X68000 or UNIX workstation systems that use this pre-SCSI standard.</li>
|
||||
<li>{{ _("The Generic image type is recommended for most computer platforms.") }}</li>
|
||||
<li>{{ _("APPLE GENUINE (.hda) and NEC GENUINE (.hdn) image types will make RaSCSI behave as a particular drive type that are recognized by Mac and PC98 systems, respectively.") }}</li>
|
||||
<li>{{ _("SASI images should only be used on the original Sharp X68000, or other legacy systems that utilize this pre-SCSI standard.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<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 name="file_name" placeholder="File name" required="" type="text">
|
||||
<label for="type">Type:</label>
|
||||
<label for="file_name">{{ _("File Name:") }}</label>
|
||||
<input name="file_name" placeholder="{{ _("File Name") }}" required="" type="text">
|
||||
<label for="type">{{ _("Type:") }}</label>
|
||||
<select name="type">
|
||||
<option value="hds">
|
||||
SCSI Hard Disk image (Generic) [.hds]
|
||||
{{ _("SCSI Hard Disk image (Generic) [.hds]") }}
|
||||
</option>
|
||||
<option value="hda">
|
||||
SCSI Hard Disk image (APPLE GENUINE - use with Mac) [.hda]
|
||||
{{ _("SCSI Hard Disk image (APPLE GENUINE) [.hda]") }}
|
||||
</option>
|
||||
<option value="hdn">
|
||||
SCSI Hard Disk image (NEC GENUINE - use with PC98) [.hdn]
|
||||
{{ _("SCSI Hard Disk image (NEC GENUINE) [.hdn]") }}
|
||||
</option>
|
||||
<option value="hdr">
|
||||
SCSI Removable Media Disk image (Generic) [.hdr]
|
||||
{{ _("SCSI Removable Media Disk image (Generic) [.hdr]") }}
|
||||
</option>
|
||||
<option value="hdf">
|
||||
SASI Hard Disk image (use with X68000) [.hdf]
|
||||
{{ _("SASI Hard Disk image (Legacy) [.hdf]") }}
|
||||
</option>
|
||||
</select>
|
||||
<label for="size">Size:</label>
|
||||
<input name="size" type="number" placeholder="MB" min="1" size="6" required>
|
||||
<input type="submit" value="Create">
|
||||
<label for="size">{{ _("Size:") }}</label>
|
||||
<input name="size" type="number" placeholder="{{ _("MB") }}" min="1" size="6" required>
|
||||
<input type="submit" value="{{ _("Create") }}">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -540,32 +538,32 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Create Named Drive
|
||||
{{ _("Create Named Drive") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Here you can create pairs of images and properties files from a list of real-life drives.</li>
|
||||
<li>This will make RaSCSI use certain vendor strings and block sizes that may improve compatibility with certain systems</li>
|
||||
<li>{{ _("Create pairs of images and properties files from a list of real-life drives.") }}</li>
|
||||
<li>{{ _("This will make RaSCSI use certain vendor strings and block sizes that may improve compatibility with certain systems.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<p><a href="/drive/list">Create a named disk image that mimics real-life drives</a></p>
|
||||
<p><a href="/drive/list">{{ _("Create a named disk image that mimics real-life drives") }}</a></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Logging
|
||||
{{ _("Logging") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Get a certain number of lines of service logs with the given scope.</li>
|
||||
<li>{{ _("Fetch a certain number of lines of system logs with the given scope.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/logs/show" method="post">
|
||||
<label for="lines">Log Lines:</label>
|
||||
<label for="lines">{{ _("Log Lines:") }}</label>
|
||||
<input name="lines" type="number" placeholder="200" min="1" size="4">
|
||||
<label for="scope">Scope:</label>
|
||||
<label for="scope">{{ _("Scope:") }}</label>
|
||||
<select name="scope">
|
||||
<option value="default">
|
||||
default
|
||||
@ -577,7 +575,7 @@
|
||||
rascsi-web.service
|
||||
</option>
|
||||
</select>
|
||||
<input type="submit" value="Show Logs">
|
||||
<input type="submit" value="{{ _("Show Logs") }}">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -587,18 +585,18 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Server Log Level
|
||||
{{ _("Server Log Level") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Change the log level of the RaSCSI backend service.</li>
|
||||
<li>The dropdown will indicate the current log level.</li>
|
||||
<li>{{ _("Change the log level of the RaSCSI backend process.") }}</li>
|
||||
<li>{{ _("The current dropdown selection indicates the active log level.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/logs/level" method="post">
|
||||
<label for="level">Log Level:</label>
|
||||
<label for="level">{{ _("Log Level:") }}</label>
|
||||
<select name="level">
|
||||
{% for level in log_levels %}
|
||||
<option value="{{ level }}"{% if level == current_log_level %} selected{% endif %}>
|
||||
@ -606,7 +604,7 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Set Log Level">
|
||||
<input type="submit" value="{{ _("Set Log Level") }}">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@ -616,23 +614,23 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
Raspberry Pi Operations
|
||||
{{ _("Raspberry Pi Operations") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>Reboot or shut down the Raspberry Pi that RaSCSI is running on.</li>
|
||||
<li>IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data corruption.</li>
|
||||
<li>{{ _("Reboot or shut down the Raspberry Pi that RaSCSI is running on.") }}</li>
|
||||
<li>{{ _("IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data loss.") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/pi/reboot" method="post" onclick="if (confirm('Reboot the Raspberry Pi?')) shutdownNotify('Rebooting the Raspberry Pi...'); else event.preventDefault();">
|
||||
<input type="submit" value="Reboot Raspberry Pi">
|
||||
<form action="/pi/reboot" method="post" onclick="if (confirm('{{ _("Reboot the Raspberry Pi?") }}')) shutdownNotify('{{ _("Rebooting the Raspberry Pi...") }}'); else event.preventDefault();">
|
||||
<input type="submit" value="{{ _("Reboot Raspberry Pi") }}">
|
||||
</form>
|
||||
</td>
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/pi/shutdown" method="post" onclick="if (confirm('Shut down the Raspberry Pi?')) shutdownNotify('Shutting down the Raspberry Pi...'); else event.preventDefault();">
|
||||
<input type="submit" value="Shut Down Raspberry Pi">
|
||||
<form action="/pi/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the Raspberry Pi?") }}')) shutdownNotify('{{ _("Shutting down the Raspberry Pi...") }}'); else event.preventDefault();">
|
||||
<input type="submit" value="{{ _("Shut Down Raspberry Pi") }}">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
1127
src/web/translations/sv/LC_MESSAGES/messages.po
Normal file
1127
src/web/translations/sv/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
157
src/web/web.py
157
src/web/web.py
@ -21,6 +21,7 @@ from flask import (
|
||||
session,
|
||||
abort,
|
||||
)
|
||||
from flask_babel import Babel, _
|
||||
|
||||
from file_cmds import (
|
||||
list_images,
|
||||
@ -81,6 +82,11 @@ from settings import (
|
||||
)
|
||||
|
||||
APP = Flask(__name__)
|
||||
BABEL = Babel(APP)
|
||||
|
||||
@BABEL.localeselector
|
||||
def get_locale():
|
||||
return request.accept_languages.best_match(["en", "de", "sv"])
|
||||
|
||||
@APP.route("/")
|
||||
def index():
|
||||
@ -88,7 +94,7 @@ def index():
|
||||
Sets up data structures for and renders the index page
|
||||
"""
|
||||
if not is_token_auth()["status"] and not APP.config["TOKEN"]:
|
||||
abort(403, "RaSCSI is password protected. Start the Web Interface with the --password parameter.")
|
||||
abort(403, _(u"RaSCSI is password protected. Start the Web Interface with the --password parameter."))
|
||||
|
||||
server_info = get_server_info()
|
||||
disk = disk_space()
|
||||
@ -182,7 +188,7 @@ def drive_list():
|
||||
return redirect(url_for("index"))
|
||||
conf = process["conf"]
|
||||
else:
|
||||
flash("Could not read drive properties from " + str(drive_properties), "error")
|
||||
flash(_("Could not read drive properties from %(properties_file)s", properties_file=drive_properties), "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
hd_conf = []
|
||||
@ -246,7 +252,7 @@ def login():
|
||||
if authenticate(str(username), str(password)):
|
||||
session["username"] = request.form["username"]
|
||||
return redirect(url_for("index"))
|
||||
flash(f"You must log in with credentials for a user in the '{AUTH_GROUP}' group!", "error")
|
||||
flash(_(u"You must log in with credentials for a user in the '%(group)s' group", group=AUTH_GROUP), "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@ -294,11 +300,12 @@ def drive_create():
|
||||
size = request.form.get("size")
|
||||
file_type = request.form.get("file_type")
|
||||
file_name = request.form.get("file_name")
|
||||
full_file_name = file_name + "." + file_type
|
||||
|
||||
# Creating the image file
|
||||
process = create_new_image(file_name, file_type, size)
|
||||
if process["status"]:
|
||||
flash(f"Created drive image file: {file_name}.{file_type}")
|
||||
flash(_(u"Image file created: %(file_name)s", file_name=full_file_name))
|
||||
else:
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
@ -392,6 +399,7 @@ def config_load():
|
||||
flash(process['msg'], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
# The only reason we would reach here would be a Web UI bug. Will not localize.
|
||||
flash("Got an unhandled request (needs to be either load or delete)", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -422,8 +430,7 @@ def show_logs():
|
||||
headers = {"content-type": "text/plain"}
|
||||
return process.stdout.decode("utf-8"), int(lines), headers
|
||||
|
||||
flash("Failed to get logs")
|
||||
flash(process.stdout.decode("utf-8"), "stdout")
|
||||
flash(_(u"An error occurred when fetching logs."))
|
||||
flash(process.stderr.decode("utf-8"), "stderr")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -438,10 +445,10 @@ def log_level():
|
||||
|
||||
process = set_log_level(level)
|
||||
if process["status"]:
|
||||
flash(f"Log level set to {level}")
|
||||
flash(_(u"Log level set to %(value)s", value=level))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to set log level to {level}!", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@ -456,22 +463,26 @@ def daynaport_attach():
|
||||
ip_addr = request.form.get("ip")
|
||||
mask = request.form.get("mask")
|
||||
|
||||
error_msg = ("Please follow the instructions at "
|
||||
"https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link")
|
||||
error_url = "https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link"
|
||||
error_msg = _(u"Please follow the instructions at %(url)s", url=error_url)
|
||||
|
||||
if interface.startswith("wlan"):
|
||||
if not introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"):
|
||||
flash("IPv4 forwarding is not enabled. " + error_msg, "error")
|
||||
flash(_(u"Configure IPv4 forwarding before using a wireless network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
if not Path("/etc/iptables/rules.v4").is_file():
|
||||
flash("NAT has not been configured. " + error_msg, "error")
|
||||
flash(_(u"Configure NAT before using a wireless network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
if not introspect_file("/etc/dhcpcd.conf", r"^denyinterfaces " + interface + r"$"):
|
||||
flash("The network bridge hasn't been configured. " + error_msg, "error")
|
||||
flash(_(u"Configure the network bridge before using a wired network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
if not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
|
||||
flash("The network bridge hasn't been configured. " + error_msg, "error")
|
||||
flash(_(u"Configure the network bridge before using a wired network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
kwargs = {"device_type": "SCDP"}
|
||||
@ -483,7 +494,7 @@ def daynaport_attach():
|
||||
|
||||
process = attach_image(scsi_id, **kwargs)
|
||||
if process["status"]:
|
||||
flash(f"Attached DaynaPORT to SCSI ID {scsi_id}!")
|
||||
flash(_(u"Attached DaynaPORT to SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(process["msg"], "error")
|
||||
@ -531,14 +542,17 @@ def attach():
|
||||
|
||||
process = attach_image(scsi_id, **kwargs)
|
||||
if process["status"]:
|
||||
flash(f"Attached {file_name} to SCSI ID {scsi_id} LUN {unit}")
|
||||
flash(_(u"Attached %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name, id_number=scsi_id, unit_number=unit))
|
||||
if int(file_size) % int(expected_block_size):
|
||||
flash(f"The image file size {file_size} bytes is not a multiple of "
|
||||
f"{expected_block_size} and RaSCSI will ignore the trailing data. "
|
||||
f"The image may be corrupted so proceed with caution.", "error")
|
||||
flash(_(u"The image file size %(file_size)s bytes is not a multiple of "
|
||||
u"%(block_size)s. RaSCSI will ignore the trailing data. "
|
||||
u"The image may be corrupted, so proceed with caution.",
|
||||
file_size=file_size, block_size=expected_block_size), "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to attach {file_name} to SCSI ID {scsi_id} LUN {unit}", "error")
|
||||
flash(_(u"Failed to attach %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name, id_number=scsi_id, unit_number=unit), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -551,7 +565,7 @@ def detach_all_devices():
|
||||
"""
|
||||
process = detach_all()
|
||||
if process["status"]:
|
||||
flash("Detached all SCSI devices")
|
||||
flash(_(u"Detached all SCSI devices"))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(process["msg"], "error")
|
||||
@ -568,10 +582,12 @@ def detach():
|
||||
unit = request.form.get("unit")
|
||||
process = detach_by_id(scsi_id, unit)
|
||||
if process["status"]:
|
||||
flash(f"Detached SCSI ID {scsi_id} LUN {unit}")
|
||||
flash(_(u"Detached SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to detach SCSI ID {scsi_id} LUN {unit}", "error")
|
||||
flash(_(u"Failed to detach %(file_name)s from SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name, id_number=scsi_id, unit_number=unit), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -587,10 +603,12 @@ def eject():
|
||||
|
||||
process = eject_by_id(scsi_id, unit)
|
||||
if process["status"]:
|
||||
flash(f"Ejected SCSI ID {scsi_id} LUN {unit}")
|
||||
flash(_(u"Ejected SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to eject SCSI ID {scsi_id} LUN {unit}", "error")
|
||||
flash(_(u"Failed to eject %(file_name)s from SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name, id_number=scsi_id, unit_number=unit), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -612,18 +630,19 @@ def device_info():
|
||||
# the one and only device that should have been returned
|
||||
device = devices["device_list"][0]
|
||||
if str(device["id"]) == scsi_id:
|
||||
flash("=== DEVICE INFO ===")
|
||||
flash(f"SCSI ID: {device['id']}")
|
||||
flash(f"LUN: {device['unit']}")
|
||||
flash(f"Type: {device['device_type']}")
|
||||
flash(f"Status: {device['status']}")
|
||||
flash(f"File: {device['image']}")
|
||||
flash(f"Parameters: {device['params']}")
|
||||
flash(f"Vendor: {device['vendor']}")
|
||||
flash(f"Product: {device['product']}")
|
||||
flash(f"Revision: {device['revision']}")
|
||||
flash(f"Block Size: {device['block_size']} bytes")
|
||||
flash(f"Image Size: {device['size']} bytes")
|
||||
flash(_(u"DEVICE INFO"))
|
||||
flash("===========")
|
||||
flash(_(u"SCSI ID: %(id_number)s", id_number=device["id"]))
|
||||
flash(_(u"LUN: %(unit_number)s", unit_number=device["unit"]))
|
||||
flash(_(u"Type: %(device_type)s", device_type=device["device_type"]))
|
||||
flash(_(u"Status: %(device_status)s", device_status=device["status"]))
|
||||
flash(_(u"File: %(image_file)s", image_file=device["image"]))
|
||||
flash(_(u"Parameters: %(value)s", value=device["params"]))
|
||||
flash(_(u"Vendor: %(value)s", value=device["vendor"]))
|
||||
flash(_(u"Product: %(value)s", value=device["product"]))
|
||||
flash(_(u"Revision: %(revision_number)s", revision_number=device["revision"]))
|
||||
flash(_(u"Block Size: %(value)s bytes", value=device["block_size"]))
|
||||
flash(_(u"Image Size: %(value)s bytes", value=device["size"]))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(devices["msg"], "error")
|
||||
@ -642,9 +661,10 @@ def reserve_id():
|
||||
process = reserve_scsi_ids(reserved_ids)
|
||||
if process["status"]:
|
||||
RESERVATIONS[int(scsi_id)] = memo
|
||||
flash(f"Reserved SCSI ID {scsi_id}")
|
||||
flash(_(u"Reserved SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(_(u"Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -660,9 +680,10 @@ def unreserve_id():
|
||||
process = reserve_scsi_ids(reserved_ids)
|
||||
if process["status"]:
|
||||
RESERVATIONS[int(scsi_id)] = ""
|
||||
flash(f"Released the reservation for SCSI ID {scsi_id}")
|
||||
flash(_(u"Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(_(u"Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -700,18 +721,19 @@ def download_to_iso():
|
||||
process = download_file_to_iso(url, *iso_args)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
flash(f"Saved image as: {process['file_name']}")
|
||||
flash(_(u"Saved image as: %(file_name)s", file_name=process['file_name']))
|
||||
else:
|
||||
flash(f"Failed to create CD-ROM image from {url}", "error")
|
||||
flash(_(u"Failed to create CD-ROM image from %(url)s", url), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
process_attach = attach_image(scsi_id, device_type="SCCD", image=process["file_name"])
|
||||
if process_attach["status"]:
|
||||
flash(f"Attached to SCSI ID {scsi_id}")
|
||||
flash(_(u"Attached to SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to attach image to SCSI ID {scsi_id}. Try attaching it manually.", "error")
|
||||
flash(_(u"Failed to attach image to SCSI ID %(id_number)s. Try attaching it manually.",
|
||||
id_number=scsi_id), "error")
|
||||
flash(process_attach["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -729,7 +751,7 @@ def download_img():
|
||||
flash(process["msg"])
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to download file {url}", "error")
|
||||
flash(_(u"Failed to download file from %(url)s", url), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -746,7 +768,7 @@ def download_afp():
|
||||
flash(process["msg"])
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(f"Failed to download file {url}", "error")
|
||||
flash(_(u"Failed to download file from %(url)s", url), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -766,26 +788,26 @@ def upload_file():
|
||||
from os import path
|
||||
|
||||
log = logging.getLogger("pydrop")
|
||||
file = request.files["file"]
|
||||
filename = secure_filename(file.filename)
|
||||
file_object = request.files["file"]
|
||||
file_name = secure_filename(file_object.filename)
|
||||
|
||||
server_info = get_server_info()
|
||||
|
||||
save_path = path.join(server_info["image_dir"], filename)
|
||||
save_path = path.join(server_info["image_dir"], file_name)
|
||||
current_chunk = int(request.form['dzchunkindex'])
|
||||
|
||||
# Makes sure not to overwrite an existing file,
|
||||
# but continues writing to a file transfer in progress
|
||||
if path.exists(save_path) and current_chunk == 0:
|
||||
return make_response((f"The file {file.filename} already exists!", 400))
|
||||
return make_response(_(u"The file already exists!"), 400)
|
||||
|
||||
try:
|
||||
with open(save_path, "ab") as save:
|
||||
save.seek(int(request.form["dzchunkbyteoffset"]))
|
||||
save.write(file.stream.read())
|
||||
save.write(file_object.stream.read())
|
||||
except OSError:
|
||||
log.exception("Could not write to file")
|
||||
return make_response(("Unable to write the file to disk!", 500))
|
||||
return make_response(_(u"Unable to write the file to disk!"), 500)
|
||||
|
||||
total_chunks = int(request.form["dztotalchunkcount"])
|
||||
|
||||
@ -795,14 +817,14 @@ def upload_file():
|
||||
log.error("Finished transferring %s, "
|
||||
"but it has a size mismatch with the original file."
|
||||
"Got %s but we expected %s.",
|
||||
file.filename, path.getsize(save_path), request.form['dztotalfilesize'])
|
||||
return make_response(("Transferred file corrupted!", 500))
|
||||
file_object.filename, path.getsize(save_path), request.form['dztotalfilesize'])
|
||||
return make_response(_(u"Transferred file corrupted!"), 500)
|
||||
|
||||
log.info("File %s has been uploaded successfully", file.filename)
|
||||
log.info("File %s has been uploaded successfully", file_object.filename)
|
||||
log.debug("Chunk %s of %s for file %s completed.",
|
||||
current_chunk + 1, total_chunks, file.filename)
|
||||
current_chunk + 1, total_chunks, file_object.filename)
|
||||
|
||||
return make_response(("File upload successful!", 200))
|
||||
return make_response(_(u"File upload successful!"), 200)
|
||||
|
||||
|
||||
@APP.route("/files/create", methods=["POST"])
|
||||
@ -814,10 +836,11 @@ def create_file():
|
||||
file_name = request.form.get("file_name")
|
||||
size = (int(request.form.get("size")) * 1024 * 1024)
|
||||
file_type = request.form.get("type")
|
||||
full_file_name = file_name + "." + file_type
|
||||
|
||||
process = create_new_image(file_name, file_type, size)
|
||||
if process["status"]:
|
||||
flash(f"Drive image created: {file_name}.{file_type}")
|
||||
flash(_(u"Image file created: %(file_name)s", file_name=full_file_name))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash(process["msg"], "error")
|
||||
@ -844,7 +867,7 @@ def delete():
|
||||
|
||||
process = delete_image(file_name)
|
||||
if process["status"]:
|
||||
flash(f"Image file deleted: {file_name}")
|
||||
flash(_(u"Image file deleted: %(file_name)s", file_name=file_name))
|
||||
else:
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
@ -874,7 +897,7 @@ def rename():
|
||||
|
||||
process = rename_image(file_name, new_file_name)
|
||||
if process["status"]:
|
||||
flash(f"Image file renamed to: {new_file_name}")
|
||||
flash(_(u"Image file renamed to: %(file_name)s", file_name=new_file_name))
|
||||
else:
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
@ -911,16 +934,16 @@ def unzip():
|
||||
process = unzip_file(zip_file, zip_member, zip_members)
|
||||
if process["status"]:
|
||||
if not process["msg"]:
|
||||
flash("Aborted unzip: File(s) with the same name already exists.", "error")
|
||||
flash(_(u"Aborted unzip: File(s) with the same name already exists."), "error")
|
||||
return redirect(url_for("index"))
|
||||
flash("Unzipped the following files:")
|
||||
flash(_(u"Unzipped the following files:"))
|
||||
for msg in process["msg"]:
|
||||
flash(msg)
|
||||
if process["prop_flag"]:
|
||||
flash(f"Properties file(s) have been moved to {CFG_DIR}")
|
||||
flash(_(u"Properties file(s) have been moved to %(directory)s", directory=CFG_DIR))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
flash("Failed to unzip " + zip_file, "error")
|
||||
flash(_(u"Failed to unzip %(zip_file)s", zip_file=zip_file), "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@ -928,8 +951,12 @@ def unzip():
|
||||
@APP.before_first_request
|
||||
def load_default_config():
|
||||
"""
|
||||
Load the default configuration file, if found
|
||||
Webapp initialization steps that require the Flask app to have started:
|
||||
- Get the detected locale to use for localizations
|
||||
- Load the default configuration file, if found
|
||||
"""
|
||||
APP.config["LOCALE"] = get_locale()
|
||||
logging.info("Detected locale: " + APP.config["LOCALE"])
|
||||
if Path(f"{CFG_DIR}/{DEFAULT_CONFIG}").is_file():
|
||||
read_config(DEFAULT_CONFIG)
|
||||
|
||||
@ -939,7 +966,7 @@ if __name__ == "__main__":
|
||||
APP.config["SESSION_TYPE"] = "filesystem"
|
||||
APP.config["MAX_CONTENT_LENGTH"] = int(MAX_FILE_SIZE)
|
||||
|
||||
parser = argparse.ArgumentParser(description="RaSCSI Web Interface arguments")
|
||||
parser = argparse.ArgumentParser(description="RaSCSI Web Interface command line arguments")
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
|
Loading…
Reference in New Issue
Block a user