diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f9658..f96d966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to iNTERCEPT will be documented in this file. +## [2.26.5] - 2026-03-14 + +### Fixed +- **Database errors crash entire UI** — `get_setting()` now catches `sqlite3.OperationalError` and returns the default value instead of propagating the exception. Previously, if the database was inaccessible (e.g. root-owned `instance/` directory from running with `sudo`), the `inject_offline_settings` context processor would crash every page render with a 500 Internal Server Error. (#190) + +--- + ## [2.26.4] - 2026-03-14 ### Fixed diff --git a/config.py b/config.py index 7d1cf6d..5c8fd5e 100644 --- a/config.py +++ b/config.py @@ -7,10 +7,17 @@ import os import sys # Application version -VERSION = "2.26.4" +VERSION = "2.26.5" # Changelog - latest release notes (shown on welcome screen) CHANGELOG = [ + { + "version": "2.26.5", + "date": "March 2026", + "highlights": [ + "Fix database errors crashing the entire UI — pages now degrade gracefully", + ] + }, { "version": "2.26.4", "date": "March 2026", diff --git a/pyproject.toml b/pyproject.toml index 60029ad..77b7e86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "intercept" -version = "2.26.4" +version = "2.26.5" description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth" readme = "README.md" requires-python = ">=3.9" diff --git a/utils/database.py b/utils/database.py index ae31a46..00d1ac3 100644 --- a/utils/database.py +++ b/utils/database.py @@ -661,32 +661,36 @@ def get_setting(key: str, default: Any = None) -> Any: Returns: Setting value (auto-converted from JSON for complex types) """ - with get_db() as conn: - cursor = conn.execute( - 'SELECT value, value_type FROM settings WHERE key = ?', - (key,) - ) - row = cursor.fetchone() + try: + with get_db() as conn: + cursor = conn.execute( + 'SELECT value, value_type FROM settings WHERE key = ?', + (key,) + ) + row = cursor.fetchone() - if row is None: - return default - - value, value_type = row['value'], row['value_type'] - - # Convert based on type - if value_type == 'json': - try: - return json.loads(value) - except json.JSONDecodeError: + if row is None: return default - elif value_type == 'int': - return int(value) - elif value_type == 'float': - return float(value) - elif value_type == 'bool': - return value.lower() in ('true', '1', 'yes') - else: - return value + + value, value_type = row['value'], row['value_type'] + + # Convert based on type + if value_type == 'json': + try: + return json.loads(value) + except json.JSONDecodeError: + return default + elif value_type == 'int': + return int(value) + elif value_type == 'float': + return float(value) + elif value_type == 'bool': + return value.lower() in ('true', '1', 'yes') + else: + return value + except sqlite3.OperationalError: + logger.warning("Database unavailable reading setting '%s', using default", key) + return default def set_setting(key: str, value: Any) -> None: