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