diff --git a/src/Server.py b/src/Server.py index 4f0a2ba..2c87259 100644 --- a/src/Server.py +++ b/src/Server.py @@ -3073,7 +3073,9 @@ def api_db_flush_buffer(): @app.post("/api/db/test-connection", dependencies=[Depends(require_admin_auth)]) def api_db_test_connection(req: DbConnectionTestRequest): """Tests connection to external PostgreSQL database. Requires enterprise_db license feature.""" - if not is_feature_licensed("enterprise_db"): + config = SERVER_STATE.get("config", {}) + db_path = config.get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("enterprise_db", db_path): raise HTTPException( status_code=403, detail="Enterprise Database feature is locked. This capability requires a valid license with the 'enterprise_db' feature." @@ -3087,12 +3089,13 @@ def api_db_test_connection(req: DbConnectionTestRequest): @app.post("/api/db/switch-backend", dependencies=[Depends(require_admin_auth)]) def api_db_switch_backend(req: DbSwitchBackendRequest): """Switches active database backend between SQLite and PostgreSQL. Requires enterprise_db license feature.""" - if not is_feature_licensed("enterprise_db"): + config = SERVER_STATE.get("config", {}) + db_path = config.get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("enterprise_db", db_path): raise HTTPException( status_code=403, detail="Enterprise Database feature is locked. This capability requires a valid license with the 'enterprise_db' feature." ) - config = SERVER_STATE.get("config", {}) if req.db_type == "postgres": pg_cfg = { "host": req.host or "localhost", @@ -3130,13 +3133,30 @@ def api_db_switch_backend(req: DbSwitchBackendRequest): @app.post("/api/db/config", dependencies=[Depends(require_admin_auth)]) def api_db_update_config(req: DbFullConfigRequest): """Updates complete database storage engine, cache mode, PostgreSQL parameters, and encryption at rest.""" - if req.storage_engine == "postgres" and not is_feature_licensed("enterprise_db"): - raise HTTPException( - status_code=403, - detail="External PostgreSQL storage is locked. This capability requires the 'enterprise_db' license feature." - ) - config = SERVER_STATE.get("config", {}) + db_path = config.get("db_path", DEFAULT_DB_FILE) + is_unlocked = is_feature_licensed("enterprise_db", db_path) + + if not is_unlocked: + if req.storage_engine == "postgres": + raise HTTPException( + status_code=403, + detail="External PostgreSQL storage is locked. This capability requires the 'enterprise_db' license feature." + ) + if req.cache_mode == "buffered": + raise HTTPException( + status_code=403, + detail="In-memory ingestion buffering is locked. This capability requires the 'enterprise_db' license feature." + ) + if req.encrypt_at_rest: + raise HTTPException( + status_code=403, + detail="Data-at-rest OpenPGP encryption is locked. This capability requires the 'enterprise_db' license feature." + ) + req.storage_engine = "sqlite" + req.cache_mode = "direct" + req.encrypt_at_rest = False + config["storage_engine"] = req.storage_engine config["db_type"] = req.storage_engine config["cache_mode"] = req.cache_mode @@ -3149,7 +3169,7 @@ def api_db_update_config(req: DbFullConfigRequest): config["batch_size"] = INGEST_QUEUE.batch_size config["max_queue"] = INGEST_QUEUE.max_queue - if req.pg_config: + if req.pg_config and is_unlocked: pg_dict = req.pg_config.model_dump() config["postgres"] = pg_dict if req.storage_engine == "postgres": @@ -3175,12 +3195,13 @@ def api_db_update_config(req: DbFullConfigRequest): @app.post("/api/db/migrate-to-postgres", dependencies=[Depends(require_admin_auth)]) def api_db_migrate_to_postgres(req: DbConnectionTestRequest): """Migrates all telemetry, runs, clients, users, and license configs from SQLite to PostgreSQL.""" - if not is_feature_licensed("enterprise_db"): + config = SERVER_STATE.get("config", {}) + db_path = config.get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("enterprise_db", db_path): raise HTTPException( status_code=403, detail="Enterprise Database feature is locked. This capability requires a valid license with the 'enterprise_db' feature." ) - config = SERVER_STATE.get("config", {}) sqlite_path = config.get("db_path", DEFAULT_DB_FILE) INGEST_QUEUE.flush(db_path=sqlite_path) @@ -3217,8 +3238,6 @@ def serve_webui(request: Request = None): queue_stats = INGEST_QUEUE.get_stats() storage_info = get_storage_engine_info() pg_config = config.get("postgres", {}) - db_tab_btn_badge = '🟢 UNLOCKED' if is_db_unlocked else '🔒 ENTERPRISE' - # Fetch clients conn = sqlite3.connect(db_path) @@ -3363,205 +3382,203 @@ def serve_webui(request: Request = None): if detected_host in ("0.0.0.0", "localhost", ""): detected_host = socket.gethostname() - is_encrypt_at_rest = config.get("encrypt_at_rest", False) + is_encrypt_at_rest = config.get("encrypt_at_rest", False) and is_db_unlocked encrypt_status_label = "🔒 OpenPGP Encrypted" if is_encrypt_at_rest else "🔓 Plaintext Storage" encrypt_status_color = "#34d399" if is_encrypt_at_rest else "#94a3b8" - if is_db_unlocked: - db_tab_pane_html = f""" - -
enterprise_db license capability.
+ - When enabled, anomaly message payloads stored in the external database are encrypted with the server's OpenPGP public key before writing. Only this hub instance can decrypt records for authorized WebUI and Hermes AI usage, ensuring zero-knowledge privacy on remote database servers. -
+- Direct PostgreSQL storage backend clustering, automatic schema synchronization, and customizable high-throughput batch-flush controls are gated behind the enterprise_db capability. + +
+ When enabled, anomaly message payloads stored in the external database are encrypted with the server's OpenPGP public key before writing. Only this hub instance can decrypt records for authorized WebUI and Hermes AI usage, ensuring zero-knowledge privacy on remote database servers.
-