diff --git a/rootfs/standard/var/pynode/application_info.py b/rootfs/standard/var/pynode/application_info.py index 643f0fa98..2fb8cb756 100644 --- a/rootfs/standard/var/pynode/application_info.py +++ b/rootfs/standard/var/pynode/application_info.py @@ -6,6 +6,7 @@ from drive_info import * from systemctl_info import * from utilities import * +from enable_disable_functions import * import copy import json import time @@ -241,6 +242,7 @@ def initialize_application_defaults(app): if not "app_page_show_open_button" in app: app["app_page_show_open_button"] = True if not "app_page_additional_buttons" in app: app["app_page_additional_buttons"] = [] if not "app_page_content" in app: app["app_page_content"] = [] + if not "data_manageable" in app: app["data_manageable"] = False # Update fields that may use variables that need replacing, like {VERSION}, {SHORT_NAME}, etc... app["download_source_url"] = replace_app_info_variables(app, app["download_source_url"]) @@ -633,25 +635,39 @@ def create_application_user(app_data): if app_data["requires_docker_image_installation"]: add_user_to_group(username, "docker") -def create_application_folders(app_data): - log_message(" Running create_application_folders...") +def create_application_install_folder(app_data): + log_message(" Running create_application_install_folder...") app_folder = app_data["install_folder"] - data_folder = app_data["storage_folder"] # Clear old data (not storage) if os.path.isdir(app_folder): log_message(" App folder exists, deleting...") run_linux_cmd("rm -rf {}".format(app_folder)) - log_message(" Making application folders...") + log_message(" Making application install folder...") run_linux_cmd("mkdir {}".format(app_folder)) - run_linux_cmd("mkdir -p {}".format(data_folder)) # Set folder permissions (always set for now - could check to see if already proper user) - log_message(" Updating folder permissions...") + log_message(" Updating install folder permissions...") run_linux_cmd("chown -R {}:{} {}".format(app_data["linux_user"], app_data["linux_user"], app_folder)) + +def create_application_storage_folder(app_data): + log_message(" Running create_application_storage_folder...") + data_folder = app_data["storage_folder"] + + log_message(" Making application storage_folder...") + run_linux_cmd("mkdir -p {}".format(data_folder)) + + # Set folder permissions (always set for now - could check to see if already proper user) + log_message(" Updating storage folder permissions...") run_linux_cmd("chown -R {}:{} {}".format(app_data["linux_user"], app_data["linux_user"], data_folder)) +def create_application_folders(app_data): + log_message(" Running create_application_folders...") + + create_application_install_folder(app_data) + create_application_storage_folder(app_data) + def create_application_tor_service(app_data): has_ports = False run_linux_cmd("mkdir -p /etc/torrc.d") @@ -738,6 +754,39 @@ def restart_application(short_name): except Exception as e: return False +def backup_data_folder(app_data): + log_message(" Running backup_data_folder...") + +def restore_data_folder(app_data): + log_message(" Running restore_data_folder...") + +def reset_data_folder(short_name): + log_message(f" Running reset_data_folder for '{short_name}'...") + + app_data = get_application(short_name) + if not app_data: + log_message(f" ERROR: application '{short_name}' not found") + return False + data_folder = app_data["storage_folder"] + + # Stop the service before removing data_folder + log_message(f" Stopping '{short_name}'…") + stop_service(short_name) + + # Remove App data_folder + log_message(f" Removing storage folder '{data_folder}'…") + run_linux_cmd(f"rm -rf {data_folder}") + + # Re-create the storage folder + log_message(f" Creating storage folder '{data_folder}'…") + create_application_storage_folder(app_data) + + # Re-start the service + log_message(f" Starting '{short_name}'…") + start_service(short_name) + + return True + ###################################################################################### ## Bulk Application Actions ###################################################################################### diff --git a/rootfs/standard/var/www/mynode/api.py b/rootfs/standard/var/www/mynode/api.py index 8d950d884..d87848d6b 100644 --- a/rootfs/standard/var/www/mynode/api.py +++ b/rootfs/standard/var/www/mynode/api.py @@ -149,6 +149,45 @@ def api_restart_app(): return "OK" +@mynode_api.route("/api/backup_data_folder") +def api_backup_data_folder(): + check_logged_in() + + short_name = request.args.get("short_name") + if not short_name: + return "NO_APP_SPECIFIED" + if not is_application_valid(short_name): + return "INVALID_APP_NAME" + if not backup_data_folder(short_name): + return "ERROR" + return "OK" + +@mynode_api.route("/api/restore_data_folder") +def api_restore_data_folder(): + check_logged_in() + + short_name = request.args.get("short_name") + if not short_name: + return "NO_APP_SPECIFIED" + if not is_application_valid(short_name): + return "INVALID_APP_NAME" + if not restore_data_folder(short_name): + return "ERROR" + return "OK" + +@mynode_api.route("/api/reset_data_folder") +def api_reset_data_folder(): + check_logged_in() + + short_name = request.args.get("short_name") + if not short_name: + return "NO_APP_SPECIFIED" + if not is_application_valid(short_name): + return "INVALID_APP_NAME" + if not reset_data_folder(short_name): + return "ERROR" + return "OK" + @mynode_api.route("/api/get_device_info") def api_get_device_info(): check_logged_in() diff --git a/rootfs/standard/var/www/mynode/static/js/manage_apps.js b/rootfs/standard/var/www/mynode/static/js/manage_apps.js index f7a3b5ee2..4c33e7b17 100644 --- a/rootfs/standard/var/www/mynode/static/js/manage_apps.js +++ b/rootfs/standard/var/www/mynode/static/js/manage_apps.js @@ -127,4 +127,53 @@ function toggleEnabled(short_name, full_name, enable, return_page="") { window.location.href="/toggle-enabled?app="+short_name+r }); } +} + +// ========================================== +// Manage app storage (data_folder) +// ========================================== + +function backup_data_folder_via_api(name, short_name) { + if ( confirm("Are you sure you want to backup "+name+"? This will stop, backup data and start app.") ) { + $('#loading_spinner_message').html("Making backup..."); + $('#loading_spinner_overlay').fadeIn(); + $.get('/api/backup_data_folder?app='+short_name) + .done(function( data ) { + if (data != "OK") { + alert("Error backing up app data: "+data) + } + $('#loading_spinner_overlay').fadeOut(); + } + ); + } +} + +function restore_data_folder_via_api(name, short_name) { + if ( confirm("Are you sure you want to restore "+name+"? This will stop, DELETE DATA, restore backup and start app.") ) { + $('#loading_spinner_message').html("Restoring..."); + $('#loading_spinner_overlay').fadeIn(); + $.get('/api/restore_data_folder?app='+short_name) + .done(function( data ) { + if (data != "OK") { + alert("Error restoring app data: "+data) + } + $('#loading_spinner_overlay').fadeOut(); + } + ); + } +} + +function reset_data_folder_via_api(name, short_name) { + if ( confirm("Are you sure you want to reset "+name+"? This will stop app, RESET ALL THE APP DATA and start app.") ) { + $('#loading_spinner_message').html("Resetting app..."); + $('#loading_spinner_overlay').fadeIn(); + $.get('/api/reset_data_folder?app='+short_name) + .done(function( data ) { + if (data != "OK") { + alert("Error removing app data: "+data) + } + $('#loading_spinner_overlay').fadeOut(); + } + ); + } } \ No newline at end of file diff --git a/rootfs/standard/var/www/mynode/templates/app/generic_app.html b/rootfs/standard/var/www/mynode/templates/app/generic_app.html index f1aad2b6f..01a17ed61 100644 --- a/rootfs/standard/var/www/mynode/templates/app/generic_app.html +++ b/rootfs/standard/var/www/mynode/templates/app/generic_app.html @@ -82,6 +82,12 @@ {% if app.is_enabled %} + {% if app.is_enabled and app.data_manageable %} + + {% endif %} + {% for btn in app.app_page_additional_buttons %}