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 %}