diff --git a/python/web/src/static/style.css b/python/web/src/static/style.css index 48fc91c6..202b56d2 100644 --- a/python/web/src/static/style.css +++ b/python/web/src/static/style.css @@ -31,18 +31,33 @@ table, tr, td { margin: none; } -.error { - color: white; - font-size:20px; - background-color:red; - white-space: pre-line; +div.flash { + margin-top: 5px; + margin-bottom: 5px; } -.message { +div.flash div { color: white; - font-size:20px; - background-color:green; + font-size: 18px; white-space: pre-line; + padding: 2px 5px; +} + +div.flash div.success { + background-color: green; +} + +div.flash div.warning { + background-color: orange; + color: black; +} + +div.flash div.error { + background-color: red; +} + +div.flash div.info { + background-color: #0d6efd; } td.inactive { diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index ce8553e1..2eb304de 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -1,7 +1,7 @@ - {{ _("RaSCSI Reloaded Control Page") }} [{{ host }}] + {{ _("RaSCSI Reloaded Control Page") }} [{{ env["host"] }}] @@ -26,12 +26,12 @@ @@ -43,9 +43,9 @@
- {% if auth_active %} - {% if username %} - {{ _("Logged in as %(username)s", username=username) }} – {{ _("Log Out") }} + {% if env["auth_active"] %} + {% if env["username"] %} + {{ _("Logged in as %(username)s", username=env["username"]) }} – {{ _("Log Out") }} {% else %}
@@ -70,7 +70,7 @@ - hostname: {{ host }} ip: {{ ip_addr }} + hostname: {{ env["host"] }} ip: {{ env["ip_addr"] }} @@ -89,8 +89,8 @@ {% block content %}{% endblock content %}
diff --git a/python/web/src/templates/drives.html b/python/web/src/templates/drives.html index 76393faf..a9cdad95 100644 --- a/python/web/src/templates/drives.html +++ b/python/web/src/templates/drives.html @@ -135,7 +135,7 @@ {% endfor %} -

{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=free_disk) }}

+

{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}

{{ _("Cancel") }}

{% endblock content %} diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index 580d46d5..d0ed8ef7 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -309,7 +309,7 @@ {% endfor %} -

{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=free_disk) }}

+

{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}


diff --git a/python/web/src/web.py b/python/web/src/web.py index 41db5e2a..918d7310 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -27,6 +27,7 @@ from flask import ( make_response, session, abort, + jsonify, ) from rascsi.ractl_cmds import RaCtlCmds @@ -67,6 +68,66 @@ from settings import ( APP = Flask(__name__) BABEL = Babel(APP) +def get_env_info(): + """ + Get information about the app/host environment + """ + ip_addr, host = sys_cmd.get_ip_and_host() + + if "username" in session: + username = session["username"] + else: + username = None + + return { + "running_env": sys_cmd.running_env(), + "username": username, + "auth_active": auth_active(AUTH_GROUP)["status"], + "ip_addr": ip_addr, + "host": host, + "free_disk_space": int(sys_cmd.disk_space()["free"] / 1024 / 1024), + } + + +def response( + template=None, + message=None, + redirect_url=None, + error=False, + status_code=200, + **kwargs +): + """ + Generates a HTML or JSON HTTP response + """ + status = "error" if error else "success" + + if isinstance(message, list): + messages = message + elif message is None: + messages = [] + else: + messages = [(str(message), status)] + + if request.headers.get("accept") == "application/json": + return jsonify({ + "status": status, + "messages": [{"message": m, "category": c} for m, c in messages], + "data": kwargs + }), status_code + + if messages: + for message, category in messages: + flash(message, category) + + if template: + kwargs["env"] = get_env_info() + return render_template(template, **kwargs) + + if redirect_url: + return redirect(url_for(redirect_url)) + return redirect(url_for("index")) + @BABEL.localeselector def get_locale(): @@ -161,14 +222,12 @@ def index(): else: username = None - return render_template( - "index.html", + return response( + template="index.html", locales=get_supported_locales(), bridge_configured=sys_cmd.is_bridge_setup(), netatalk_configured=sys_cmd.running_proc("afpd"), macproxy_configured=sys_cmd.running_proc("macproxy"), - ip_addr=ip_addr, - host=host, devices=formatted_devices, files=extended_image_files, config_files=config_files, @@ -183,20 +242,16 @@ def index(): reserved_scsi_ids=reserved_scsi_ids, RESERVATIONS=RESERVATIONS, max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024), - running_env=sys_cmd.running_env(), version=server_info["version"], log_levels=server_info["log_levels"], current_log_level=server_info["current_log_level"], netinfo=ractl_cmd.get_network_info(), device_types=device_types, - free_disk=int(sys_cmd.disk_space()["free"] / 1024 / 1024), image_suffixes_to_create=image_suffixes_to_create, valid_image_suffixes=valid_image_suffixes, cdrom_file_suffix=tuple(server_info["sccd"]), removable_file_suffix=tuple(server_info["scrm"]), mo_file_suffix=tuple(server_info["scmo"]), - username=username, - auth_active=auth_active(AUTH_GROUP)["status"], PROPERTIES_SUFFIX=PROPERTIES_SUFFIX, ARCHIVE_FILE_SUFFIXES=ARCHIVE_FILE_SUFFIXES, REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(), @@ -213,23 +268,20 @@ def drive_list(): # Reads the canonical drive properties into a dict # The file resides in the current dir of the web ui process drive_properties = Path(DRIVE_PROPERTIES_FILE) - if drive_properties.is_file(): - process = file_cmd.read_drive_properties(str(drive_properties)) - process = ReturnCodeMapper.add_msg(process) - if not process["status"]: - flash(process["msg"], "error") - return redirect(url_for("index")) - conf = process["conf"] - else: - flash( - _( - "Could not read drive properties from %(properties_file)s", - properties_file=drive_properties, - ), - "error", - ) - return redirect(url_for("index")) + if not drive_properties.is_file(): + return response( + error=True, + message=_("Could not read drive properties from %(properties_file)s", + properties_file=drive_properties), + ) + process = file_cmd.read_drive_properties(str(drive_properties)) + process = ReturnCodeMapper.add_msg(process) + + if not process["status"]: + return response(error=True, message=process["msg"]) + + conf = process["conf"] hd_conf = [] cd_conf = [] rm_conf = [] @@ -247,30 +299,18 @@ def drive_list(): device["size_mb"] = "{:,.2f}".format(device["size"] / 1024 / 1024) rm_conf.append(device) - if "username" in session: - username = session["username"] - else: - username = None - server_info = ractl_cmd.get_server_info() - ip_addr, host = sys_cmd.get_ip_and_host() - return render_template( + return response( "drives.html", files=file_cmd.list_images()["files"], base_dir=server_info["image_dir"], hd_conf=hd_conf, cd_conf=cd_conf, rm_conf=rm_conf, - running_env=sys_cmd.running_env(), version=server_info["version"], - free_disk=int(sys_cmd.disk_space()["free"] / 1024 / 1024), cdrom_file_suffix=tuple(server_info["sccd"]), - username=username, - auth_active=auth_active(AUTH_GROUP)["status"], - ip_addr=ip_addr, - host=host, - ) + ) @APP.route("/login", methods=["POST"]) @@ -280,20 +320,17 @@ def login(): """ username = request.form["username"] password = request.form["password"] - groups = [g.gr_name for g in getgrall() if username in g.gr_mem] + if AUTH_GROUP in groups: if authenticate(str(username), str(password)): session["username"] = request.form["username"] - return redirect(url_for("index")) - flash( - _( - "You must log in with credentials for a user in the '%(group)s' group", - group=AUTH_GROUP, - ), - "error", - ) - return redirect(url_for("index")) + return response() + + return response(error=True, status_code=401, message=_( + "You must log in with valid credentials for a user in the '%(group)s' group", + group=AUTH_GROUP, + )) @APP.route("/logout") @@ -302,7 +339,7 @@ def logout(): Removes the logged in user from the session """ session.pop("username", None) - return redirect(url_for("index")) + return response() @APP.route("/pwa/") @@ -321,8 +358,7 @@ def login_required(func): def decorated_function(*args, **kwargs): auth = auth_active(AUTH_GROUP) if auth["status"] and "username" not in session: - flash(auth["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=auth["msg"]) return func(*args, **kwargs) return decorated_function @@ -344,11 +380,8 @@ def drive_create(): # Creating the image file process = file_cmd.create_new_image(file_name, file_type, size) - if process["status"]: - flash(_("Image file created: %(file_name)s", file_name=full_file_name)) - else: - flash(process["msg"], "error") - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=process["msg"]) # Creating the drive properties file prop_file_name = f"{file_name}.{file_type}.{PROPERTIES_SUFFIX}" @@ -360,12 +393,10 @@ def drive_create(): } process = file_cmd.write_drive_properties(prop_file_name, properties) process = ReturnCodeMapper.add_msg(process) - if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=process["msg"]) - flash(process['msg'], "error") - return redirect(url_for("index")) + return response(message=_("Image file created: %(file_name)s", file_name=full_file_name)) @APP.route("/drive/cdrom", methods=["POST"]) @@ -391,11 +422,9 @@ def drive_cdrom(): process = file_cmd.write_drive_properties(file_name, properties) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) - flash(process['msg'], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/config/save", methods=["POST"]) @@ -410,11 +439,9 @@ def config_save(): process = file_cmd.write_config(file_name) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) - flash(process['msg'], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/config/load", methods=["POST"]) @@ -429,24 +456,19 @@ def config_load(): process = file_cmd.read_config(file_name) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) + + return response(error=True, message=process["msg"]) - flash(process['msg'], "error") - return redirect(url_for("index")) if "delete" in request.form: process = file_cmd.delete_file(f"{CFG_DIR}/{file_name}") process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) - flash(process['msg'], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) - # 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")) + return response(error=True, message="Action field (load, delete) missing") @APP.route("/logs/show", methods=["POST"]) @@ -457,14 +479,16 @@ def show_logs(): lines = request.form.get("lines") scope = request.form.get("scope") + # TODO: Render logs in template returncode, logs = sys_cmd.get_logs(lines, scope) if returncode == 0: headers = {"content-type": "text/plain"} - return logs, int(lines), headers + return logs, headers - flash(_("An error occurred when fetching logs.")) - flash(logs, "stderr") - return redirect(url_for("index")) + return response(error=True, message=[ + (_("An error occurred when fetching logs."), "error"), + (logs, "stderr"), + ]) @APP.route("/logs/level", methods=["POST"]) @@ -477,11 +501,9 @@ def log_level(): process = ractl_cmd.set_log_level(level) if process["status"]: - flash(_("Log level set to %(value)s", value=level)) - return redirect(url_for("index")) + return response(message=_("Log level set to %(value)s", value=level)) - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/scsi/attach_device", methods=["POST"]) @@ -507,11 +529,14 @@ def attach_device(): error_msg = _("Please follow the instructions at %(url)s", url=error_url) if "interface" in params.keys(): + # TODO: Can the behaviour of this function be made more intuitive? bridge_status = is_bridge_configured(params["interface"]) + # Error condition is truthy if bridge_status: - flash(bridge_status, "error") - flash(error_msg, "error") - return redirect(url_for("index")) + return response(error=True, message=[ + (bridge_status, "error"), + (error_msg, "error") + ]) kwargs = { "unit": int(unit), @@ -521,16 +546,14 @@ def attach_device(): process = ractl_cmd.attach_device(scsi_id, **kwargs) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(_( + return response(message=_( "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s", device_type=get_device_name(device_type), id_number=scsi_id, unit_number=unit, )) - return redirect(url_for("index")) - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/scsi/attach", methods=["POST"]) @@ -559,8 +582,7 @@ def attach_image(): process = file_cmd.read_drive_properties(drive_properties) process = ReturnCodeMapper.add_msg(process) if not process["status"]: - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) conf = process["conf"] kwargs["vendor"] = conf["vendor"] kwargs["product"] = conf["product"] @@ -571,28 +593,31 @@ def attach_image(): process = ractl_cmd.attach_device(scsi_id, **kwargs) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(_( + response_messages = [(_( "Attached %(file_name)s as %(device_type)s to " "SCSI ID %(id_number)s LUN %(unit_number)s", file_name=file_name, device_type=get_device_name(device_type), id_number=scsi_id, unit_number=unit, - )) + ), "success")] + if int(file_size) % int(expected_block_size): - flash(_( + response_messages.append((_( "The image file size %(file_size)s bytes is not a multiple of " "%(block_size)s. RaSCSI will ignore the trailing data. " "The image may be corrupted, so proceed with caution.", file_size=file_size, block_size=expected_block_size, - ), "error") - return redirect(url_for("index")) + ), "warning")) - flash(_("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")) + return response(message=response_messages) + + return response(error=True, message=[ + (_("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"), + (process["msg"], "error"), + ]) @APP.route("/scsi/detach_all", methods=["POST"]) @@ -603,11 +628,9 @@ def detach_all_devices(): """ process = ractl_cmd.detach_all() if process["status"]: - flash(_("Detached all SCSI devices")) - return redirect(url_for("index")) + return response(message=_("Detached all SCSI devices")) - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/scsi/detach", methods=["POST"]) @@ -620,14 +643,14 @@ def detach(): unit = request.form.get("unit") process = ractl_cmd.detach_by_id(scsi_id, unit) if process["status"]: - flash(_("Detached SCSI ID %(id_number)s LUN %(unit_number)s", - id_number=scsi_id, unit_number=unit)) - return redirect(url_for("index")) + return response(message=_("Detached SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit)) - flash(_("Failed to detach SCSI ID %(id_number)s LUN %(unit_number)s", - id_number=scsi_id, unit_number=unit), "error") - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=[ + (_("Failed to detach SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit), "error"), + (process["msg"], "error"), + ]) @APP.route("/scsi/eject", methods=["POST"]) @@ -641,14 +664,15 @@ def eject(): process = ractl_cmd.eject_by_id(scsi_id, unit) if process["status"]: - flash(_("Ejected SCSI ID %(id_number)s LUN %(unit_number)s", - id_number=scsi_id, unit_number=unit)) - return redirect(url_for("index")) + return response(message=_("Ejected SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit)) + + return response(error=True, message=[ + (_("Failed to eject SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit), "error"), + (process["msg"], "error"), + ]) - flash(_("Failed to eject SCSI ID %(id_number)s LUN %(unit_number)s", - id_number=scsi_id, unit_number=unit), "error") - flash(process["msg"], "error") - return redirect(url_for("index")) @APP.route("/scsi/info", methods=["POST"]) def device_info(): @@ -662,29 +686,47 @@ def device_info(): # First check if any device at all was returned if not devices["status"]: - flash(devices["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=devices["msg"]) # Looking at the first dict in list to get # the one and only device that should have been returned device = devices["device_list"][0] if str(device["id"]) == scsi_id: - flash(_("DEVICE INFO")) - flash("===========") - flash(_("SCSI ID: %(id_number)s", id_number=device["id"])) - flash(_("LUN: %(unit_number)s", unit_number=device["unit"])) - flash(_("Type: %(device_type)s", device_type=device["device_type"])) - flash(_("Status: %(device_status)s", device_status=device["status"])) - flash(_("File: %(image_file)s", image_file=device["image"])) - flash(_("Parameters: %(value)s", value=device["params"])) - flash(_("Vendor: %(value)s", value=device["vendor"])) - flash(_("Product: %(value)s", value=device["product"])) - flash(_("Revision: %(revision_number)s", revision_number=device["revision"])) - flash(_("Block Size: %(value)s bytes", value=device["block_size"])) - flash(_("Image Size: %(value)s bytes", value=device["size"])) - return redirect(url_for("index")) + message = "\n".join([ + _("DEVICE INFO"), + "===========", + _("SCSI ID: %(id_number)s", id_number=device["id"]), + _("LUN: %(unit_number)s", unit_number=device["unit"]), + _("Type: %(device_type)s", device_type=device["device_type"]), + _("Status: %(device_status)s", device_status=device["status"]), + _("File: %(image_file)s", image_file=device["image"]), + _("Parameters: %(value)s", value=device["params"]), + _("Vendor: %(value)s", value=device["vendor"]), + _("Product: %(value)s", value=device["product"]), + _("Revision: %(revision_number)s", revision_number=device["revision"]), + _("Block Size: %(value)s bytes", value=device["block_size"]), + _("Image Size: %(value)s bytes", value=device["size"]), + ]) + + # Don't send redundant "info" message with the JSON response + if request.headers.get("accept") == "application/json": + return response(device_info={ + "scsi_id": device["id"], + "lun": device["unit"], + "device_type": device["device_type"], + "status": device["status"], + "file": device["image"], + "parameters": device["params"], + "vendor": device["vendor"], + "product": device["product"], + "revision": device["revision"], + "block_size": device["block_size"], + "size": device["size"], + }) + + return response(message=[(message, "info")]) + + return response(error=True, message=devices["msg"]) - flash(devices["msg"], "error") - return redirect(url_for("index")) @APP.route("/scsi/reserve", methods=["POST"]) @login_required @@ -699,12 +741,13 @@ def reserve_id(): process = ractl_cmd.reserve_scsi_ids(reserved_ids) if process["status"]: RESERVATIONS[int(scsi_id)] = memo - flash(_("Reserved SCSI ID %(id_number)s", id_number=scsi_id)) - return redirect(url_for("index")) + return response(message=_("Reserved SCSI ID %(id_number)s", id_number=scsi_id)) + + return response(error=True, message=[ + (_("Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id), "error"), + (process["msg"], "error"), + ]) - flash(_("Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id)) - flash(process["msg"], "error") - return redirect(url_for("index")) @APP.route("/scsi/release", methods=["POST"]) @login_required @@ -718,12 +761,12 @@ def release_id(): process = ractl_cmd.reserve_scsi_ids(reserved_ids) if process["status"]: RESERVATIONS[int(scsi_id)] = "" - flash(_("Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id)) - return redirect(url_for("index")) + return response(message=_("Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id)) - flash(_("Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id)) - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=[ + (_("Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id), "error"), + (process["msg"], "error"), + ]) @APP.route("/pi/reboot", methods=["POST"]) @@ -733,7 +776,7 @@ def restart(): Restarts the Pi """ ractl_cmd.shutdown_pi("reboot") - return redirect(url_for("index")) + return response() @APP.route("/pi/shutdown", methods=["POST"]) @@ -743,7 +786,7 @@ def shutdown(): Shuts down the Pi """ ractl_cmd.shutdown_pi("system") - return redirect(url_for("index")) + return response() @APP.route("/files/download_to_iso", methods=["POST"]) @@ -755,16 +798,18 @@ def download_to_iso(): scsi_id = request.form.get("scsi_id") url = request.form.get("url") iso_args = request.form.get("type").split() + response_messages = [] process = file_cmd.download_file_to_iso(url, *iso_args) process = ReturnCodeMapper.add_msg(process) - if process["status"]: - flash(process["msg"]) - flash(_("Saved image as: %(file_name)s", file_name=process['file_name'])) - else: - flash(_("Failed to create CD-ROM image from %(url)s", url=url), "error") - flash(process["msg"], "error") - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=[ + (_("Failed to create CD-ROM image from %(url)s", url=url), "error"), + (process["msg"], "error"), + ]) + + response_messages.append((process["msg"], "success")) + response_messages.append((_("Saved image as: %(file_name)s", file_name=process['file_name']), "success")) process_attach = ractl_cmd.attach_device( scsi_id, @@ -773,13 +818,13 @@ def download_to_iso(): ) process_attach = ReturnCodeMapper.add_msg(process_attach) if process_attach["status"]: - flash(_("Attached to SCSI ID %(id_number)s", id_number=scsi_id)) - return redirect(url_for("index")) + response_messages.append((_("Attached to SCSI ID %(id_number)s", id_number=scsi_id), "success")) + return response(message=response_messages) - flash(_("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")) + return response(error=True, message=[ + (_("Failed to attach image to SCSI ID %(id_number)s. Try attaching it manually.", id_number=scsi_id), "error"), + (process_attach["msg"], "error"), + ]) @APP.route("/files/download_to_images", methods=["POST"]) @@ -793,12 +838,12 @@ def download_img(): process = file_cmd.download_to_dir(url, server_info["image_dir"], Path(url).name) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) - flash(_("Failed to download file from %(url)s", url=url), "error") - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=[ + (_("Failed to download file from %(url)s", url=url), "error"), + (process["msg"], "error"), + ]) @APP.route("/files/download_to_afp", methods=["POST"]) @@ -812,12 +857,12 @@ def download_afp(): process = file_cmd.download_to_dir(url, AFP_DIR, file_name) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + return response(message=process["msg"]) - flash(_("Failed to download file from %(url)s", url=url), "error") - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=[ + (_("Failed to download file from %(url)s", url=url), "error"), + (process["msg"], "error"), + ]) @APP.route("/files/upload", methods=["POST"]) @@ -848,11 +893,13 @@ def create_file(): process = file_cmd.create_new_image(file_name, file_type, size) if process["status"]: - flash(_("Image file created: %(file_name)s", file_name=full_file_name)) - return redirect(url_for("index")) + return response( + status_code=201, + message=_("Image file created: %(file_name)s", file_name=full_file_name), + image=full_file_name, + ) - flash(process["msg"], "error") - return redirect(url_for("index")) + return response(error=True, message=process["msg"]) @APP.route("/files/download", methods=["POST"]) @@ -874,26 +921,23 @@ def delete(): file_name = request.form.get("file_name") process = file_cmd.delete_image(file_name) - if process["status"]: - flash(_("Image file deleted: %(file_name)s", file_name=file_name)) - else: - flash(process["msg"], "error") - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=process["msg"]) + + response_messages = [ + (_("Image file deleted: %(file_name)s", file_name=file_name), "success")] # Delete the drive properties file, if it exists prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}" if Path(prop_file_path).is_file(): process = file_cmd.delete_file(prop_file_path) process = ReturnCodeMapper.add_msg(process) - if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + response_messages.append((process["msg"], "success")) + else: + response_messages.append((process["msg"], "error")) - flash(process["msg"], "error") - return redirect(url_for("index")) - - return redirect(url_for("index")) + return response(message=response_messages) @APP.route("/files/rename", methods=["POST"]) @@ -906,11 +950,11 @@ def rename(): new_file_name = request.form.get("new_file_name") process = file_cmd.rename_image(file_name, new_file_name) - if process["status"]: - flash(_("Image file renamed to: %(file_name)s", file_name=new_file_name)) - else: - flash(process["msg"], "error") - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=process["msg"]) + + response_messages = [ + (_("Image file renamed to: %(file_name)s", file_name=new_file_name), "success")] # Rename the drive properties file, if it exists prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}" @@ -919,13 +963,11 @@ def rename(): process = file_cmd.rename_file(prop_file_path, new_prop_file_path) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + response_messages.append((process["msg"], "success")) + else: + response_messages.append((process["msg"], "error")) - flash(process["msg"], "error") - return redirect(url_for("index")) - - return redirect(url_for("index")) + return response(message=response_messages) @APP.route("/files/copy", methods=["POST"]) @@ -938,11 +980,11 @@ def copy(): new_file_name = request.form.get("copy_file_name") process = file_cmd.copy_image(file_name, new_file_name) - if process["status"]: - flash(_("Copy of image file saved as: %(file_name)s", file_name=new_file_name)) - else: - flash(process["msg"], "error") - return redirect(url_for("index")) + if not process["status"]: + return response(error=True, message=process["msg"]) + + response_messages = [ + (_("Copy of image file saved as: %(file_name)s", file_name=new_file_name), "success")] # Create a copy of the drive properties file, if it exists prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}" @@ -951,13 +993,11 @@ def copy(): process = file_cmd.copy_file(prop_file_path, new_prop_file_path) process = ReturnCodeMapper.add_msg(process) if process["status"]: - flash(process["msg"]) - return redirect(url_for("index")) + response_messages.append((process["msg"], "success")) + else: + response_messages.append((process["msg"], "error")) - flash(process["msg"], "error") - return redirect(url_for("index")) - - return redirect(url_for("index")) + return response(message=response_messages) @APP.route("/files/extract_image", methods=["POST"]) @@ -976,23 +1016,23 @@ def extract_image(): ) if extract_result["return_code"] == ReturnCodes.EXTRACTIMAGE_SUCCESS: - flash(ReturnCodeMapper.add_msg(extract_result).get("msg")) + response_messages = [(ReturnCodeMapper.add_msg(extract_result).get("msg"), "success")] for properties_file in extract_result["properties_files_moved"]: if properties_file["status"]: - flash(_("Properties file %(file)s moved to %(directory)s", + response_messages.append((_("Properties file %(file)s moved to %(directory)s", file=properties_file['name'], directory=CFG_DIR - )) + ), "success")) else: - flash(_("Failed to move properties file %(file)s to %(directory)s", + response_messages.append((_("Failed to move properties file %(file)s to %(directory)s", file=properties_file['name'], directory=CFG_DIR - ), "error") - else: - flash(ReturnCodeMapper.add_msg(extract_result).get("msg"), "error") + ), "error")) - return redirect(url_for("index")) + return response(message=response_messages) + + return response(error=True, message=ReturnCodeMapper.add_msg(extract_result).get("msg")) @APP.route("/language", methods=["POST"]) @@ -1008,8 +1048,7 @@ def change_language(): language = Locale.parse(locale) language_name = language.get_language_name(locale) - flash(_("Changed Web Interface language to %(locale)s", locale=language_name)) - return redirect(url_for("index")) + return response(message=_("Changed Web Interface language to %(locale)s", locale=language_name)) @APP.before_first_request