diff --git a/frontend/static/js/new-main.js b/frontend/static/js/new-main.js index 7eb6508b..8655357b 100644 --- a/frontend/static/js/new-main.js +++ b/frontend/static/js/new-main.js @@ -3,6 +3,11 @@ * Main JavaScript file for handling UI interactions and API communication */ +/** + * Huntarr - New UI Implementation + * Main JavaScript file for handling UI interactions and API communication + */ + let huntarrUI = { // Current state eventSources: {}, @@ -2064,6 +2069,38 @@ let huntarrUI = { }); }, + // Format large numbers with appropriate suffixes (K, MIL, BIL, TRI) + formatLargeNumber: function(num) { + if (num < 1000) { + // 0-999: Display as is + return num.toString(); + } else if (num < 10000) { + // 1,000-9,999: Display with single decimal and K (e.g., 5.2K) + return (num / 1000).toFixed(1) + 'K'; + } else if (num < 100000) { + // 10,000-99,999: Display with single decimal and K (e.g., 75.4K) + return (num / 1000).toFixed(1) + 'K'; + } else if (num < 1000000) { + // 100,000-999,999: Display with K (no decimal) (e.g., 982K) + return Math.floor(num / 1000) + 'K'; + } else if (num < 10000000) { + // 1,000,000-9,999,999: Display with single decimal and MIL (e.g., 9.7 MIL) + return (num / 1000000).toFixed(1) + ' MIL'; + } else if (num < 100000000) { + // 10,000,000-99,999,999: Display with single decimal and MIL (e.g., 99.7 MIL) + return (num / 1000000).toFixed(1) + ' MIL'; + } else if (num < 1000000000) { + // 100,000,000-999,999,999: Display with MIL (no decimal) + return Math.floor(num / 1000000) + ' MIL'; + } else if (num < 1000000000000) { + // 1B - 999B: Display with single decimal and BIL + return (num / 1000000000).toFixed(1) + ' BIL'; + } else { + // 1T+: Display with TRI + return (num / 1000000000000).toFixed(1) + ' TRI'; + } + }, + animateNumber: function(element, start, end) { const duration = 1000; // Animation duration in milliseconds const startTime = performance.now(); @@ -2076,12 +2113,15 @@ let huntarrUI = { const easeOutQuad = progress * (2 - progress); const currentValue = Math.floor(start + (end - start) * easeOutQuad); - element.textContent = currentValue; + + // Format number for display + element.textContent = this.formatLargeNumber(currentValue); if (progress < 1) { requestAnimationFrame(updateNumber); } else { - element.textContent = end; // Ensure we end with the exact target number + // Ensure we end with the exact formatted target number + element.textContent = this.formatLargeNumber(end); } }; diff --git a/frontend/static/js/settings_forms.js b/frontend/static/js/settings_forms.js index 79bac952..7dde0a22 100644 --- a/frontend/static/js/settings_forms.js +++ b/frontend/static/js/settings_forms.js @@ -1321,6 +1321,11 @@ const SettingsForms = {

How often Huntarr refreshes logs from apps (seconds)

+
+ + +

Base URL path for reverse proxy (e.g., '/huntarr'). Leave empty for root path. Requires restart.

+
`; diff --git a/frontend/static/js/utils.js b/frontend/static/js/utils.js index 040cbab8..88618969 100644 --- a/frontend/static/js/utils.js +++ b/frontend/static/js/utils.js @@ -31,7 +31,21 @@ const HuntarrUtils = { signal: controller.signal }; - return fetch(url, fetchOptions) + // Process URL to handle base URL for reverse proxy subpaths + let processedUrl = url; + + // Only process internal API requests (not external URLs) + if (url && typeof url === 'string' && !url.startsWith('http') && !url.startsWith('//')) { + // Handle base URL from window.HUNTARR_BASE_URL if available + const baseUrl = window.HUNTARR_BASE_URL || ''; + if (baseUrl && !url.startsWith(baseUrl)) { + // Ensure path starts with a slash + const normalizedPath = url.startsWith('/') ? url : '/' + url; + processedUrl = baseUrl + normalizedPath; + } + } + + return fetch(processedUrl, fetchOptions) .then(response => { clearTimeout(timeoutId); return response; diff --git a/frontend/templates/components/head.html b/frontend/templates/components/head.html index 2dcd865b..bc906703 100644 --- a/frontend/templates/components/head.html +++ b/frontend/templates/components/head.html @@ -4,6 +4,11 @@ + + diff --git a/src/primary/default_configs/general.json b/src/primary/default_configs/general.json index 1d7d9298..6cd5021d 100644 --- a/src/primary/default_configs/general.json +++ b/src/primary/default_configs/general.json @@ -13,5 +13,6 @@ "command_wait_attempts": 600, "minimum_download_queue_size": -1, "api_timeout": 120, - "ssl_verify": true + "ssl_verify": true, + "base_url": "" } \ No newline at end of file diff --git a/src/primary/web_server.py b/src/primary/web_server.py index 2e46fab0..5793cad9 100644 --- a/src/primary/web_server.py +++ b/src/primary/web_server.py @@ -123,11 +123,60 @@ if os.path.exists(template_dir): print(f"Template dir contents: {os.listdir(template_dir)}") +# Get base_url from settings (used for reverse proxy subpath configurations) +def get_base_url(): + """ + Get the configured base URL from general settings. + This allows Huntarr to run under a subpath like /huntarr when behind a reverse proxy. + + Returns: + str: The configured base URL (e.g., '/huntarr') or empty string if not configured + """ + try: + base_url = settings_manager.get_setting('general', 'base_url', '') + # Ensure base_url always starts with a / if not empty + if base_url and not base_url.startswith('/'): + base_url = f'/{base_url}' + # Remove trailing slash if present + if base_url and base_url != '/' and base_url.endswith('/'): + base_url = base_url.rstrip('/') + return base_url + except Exception as e: + print(f"Error getting base_url from settings: {e}") + return '' + +# Define base_url at module level +base_url = '' + # Create Flask app with additional debug logging app = Flask(__name__, template_folder=template_dir, static_folder=static_dir) print(f"Flask app created with template_folder: {app.template_folder}") print(f"Flask app created with static_folder: {app.static_folder}") +# Get and apply the base URL setting after app is created +try: + base_url = get_base_url() + if base_url: + print(f"Configuring base URL: {base_url}") + app.config['APPLICATION_ROOT'] = base_url + # Flask 1.x compatibility - needed for proper URL generation + if not hasattr(app, 'wsgi_app') or not hasattr(app.wsgi_app, '__call__'): + print("Warning: Unable to configure WSGI middleware for base URL") + else: + # This ensures static files and other routes respect the base URL + from werkzeug.middleware.dispatcher import DispatcherMiddleware + from werkzeug.exceptions import NotFound + app.wsgi_app = DispatcherMiddleware( + NotFound(), # Default 404 app when accessed without base URL + {base_url: app.wsgi_app} # Main app mounted at base URL + ) + print(f"WSGI middleware configured for base URL: {base_url}") + else: + print("Running at root URL path (no base URL)") +except Exception as e: + print(f"Error applying base URL setting: {e}") + base_url = '' # Fallback to empty string on error + # Add debug logging for template rendering def debug_template_rendering(): """Additional logging for Flask template rendering""" @@ -198,6 +247,12 @@ def get_source_wrapper(environment, template): # Register the authentication check to run before requests app.before_request(authenticate_request) +# Add base_url to template context so it can be used in templates +@app.context_processor +def inject_base_url(): + """Add base_url to template context for use in templates""" + return {'base_url': base_url} + # Removed MAIN_PID and signal-related code # Lock for accessing the log files @@ -219,14 +274,19 @@ def get_source_wrapper(environment, template): ALL_APP_LOG_FILES = list(KNOWN_LOG_FILES.values()) # List of all individual log file paths +# Handle both root path and base URL root path @app.route('/') def home(): + """Render the main index page""" return render_template('index.html') @app.route('/user') def user(): - # User account screen + """Render the user account screen""" return render_template('user.html') + +# This section previously contained code for redirecting paths to include the base URL +# It has been removed as Flask's APPLICATION_ROOT setting provides this functionality # Removed /settings and /logs routes if handled by index.html and JS routing # Keep /logs if it's the actual SSE endpoint diff --git a/version.txt b/version.txt index 2f963cd6..5febf260 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.2 \ No newline at end of file +7.0.3 \ No newline at end of file