feat(ui): reorder DB Engine tab (cache mode on top, encryption in middle, DB engine switcher below)
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m18s

This commit is contained in:
2026-09-30 17:28:53 +02:00
parent fa662da476
commit b7c27ee3a1
+108 -85
View File
@@ -808,6 +808,26 @@ def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
config["session_secret"] = secrets.token_hex(32)
if "db_path" not in config:
config["db_path"] = DEFAULT_DB_FILE
if "server_name" not in config:
config["server_name"] = "LOGAR Enterprise Hub"
if "tcp_host" not in config:
config["tcp_host"] = "0.0.0.0"
if "tcp_port" not in config:
config["tcp_port"] = 9443
if "hermes_host" not in config:
config["hermes_host"] = "0.0.0.0"
if "hermes_port" not in config:
config["hermes_port"] = 8443
if "evaluation_window_hours" not in config:
config["evaluation_window_hours"] = 12
if "min_persistence_runs" not in config:
config["min_persistence_runs"] = 2
if "storage_engine" not in config:
config["storage_engine"] = config.get("db_type", "sqlite")
if "cache_mode" not in config:
config["cache_mode"] = "direct"
if "encrypt_at_rest" not in config:
config["encrypt_at_rest"] = False
# Resolve relative db_path and cert_dir to writable paths
config["db_path"] = resolve_writable_path(config["db_path"], "LOGAR-Server", DEFAULT_DB_FILE)
@@ -3388,91 +3408,10 @@ def serve_webui(request: Request = None):
<div class="card">
<div class="card-header">
<span class="card-title">Database Storage Engine Configuration</span>
<span class="badge badge-os">STORAGE SELECTION</span>
<span class="badge badge-os">CONFIG & TUNING</span>
</div>
<div style="margin-bottom: 20px;">
<label class="form-label" style="font-size: 14px; font-weight: 700; margin-bottom: 8px;">Select Storage Engine Backend:</label>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 12px 18px; background: #0f172a; border: 1px solid var(--border); border-radius: 8px;">
<input type="radio" name="dbEngineRadio" id="engineRadioSqlite" value="sqlite" {'checked' if storage_info['engine'] == 'sqlite' else ''} onchange="toggleStorageEngineForm()">
<span>💾 <strong>Local SQLite 3 (WAL Mode)</strong> <span style="color: var(--text-muted); font-size: 12px;">(Embedded, Zero Configuration)</span></span>
</label>
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 12px 18px; background: #0f172a; border: 1px solid var(--border); border-radius: 8px;">
<input type="radio" name="dbEngineRadio" id="engineRadioPostgres" value="postgres" {'checked' if storage_info['engine'] == 'postgres' else ''} onchange="toggleStorageEngineForm()">
<span>🐘 <strong>External PostgreSQL Database Server</strong> <span style="color: #34d399; font-size: 12px; font-weight: 600;">(Enterprise Clustering)</span></span>
</label>
</div>
</div>
<!-- External PostgreSQL Parameters -->
<div id="pgConfigSection" style="background: #0f172a; padding: 20px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 20px; display: {'block' if storage_info['engine'] == 'postgres' else 'none'};">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between;">
<span style="display: flex; align-items: center; gap: 8px;">🐘 External PostgreSQL Connection Parameters</span>
<span class="badge badge-active">ENTERPRISE POOL</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 16px;">
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Host / IP Address</label>
<input type="text" id="pgHost" class="form-control" value="{pg_config.get('host', 'localhost')}" placeholder="localhost">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Port</label>
<input type="number" id="pgPort" class="form-control" value="{pg_config.get('port', 5432)}" placeholder="5432">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Database Name</label>
<input type="text" id="pgDatabase" class="form-control" value="{pg_config.get('database', 'logar')}" placeholder="logar">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Username</label>
<input type="text" id="pgUser" class="form-control" value="{pg_config.get('user', 'postgres')}" placeholder="postgres">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Password</label>
<input type="password" id="pgPassword" class="form-control" value="{pg_config.get('password', '')}" placeholder="••••••••">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">SSL Mode</label>
<select id="pgSslMode" class="form-control">
<option value="prefer" {'selected' if pg_config.get('sslmode') == 'prefer' else ''}>prefer</option>
<option value="require" {'selected' if pg_config.get('sslmode') == 'require' else ''}>require</option>
<option value="verify-ca" {'selected' if pg_config.get('sslmode') == 'verify-ca' else ''}>verify-ca</option>
<option value="verify-full" {'selected' if pg_config.get('sslmode') == 'verify-full' else ''}>verify-full</option>
<option value="disable" {'selected' if pg_config.get('sslmode') == 'disable' else ''}>disable</option>
<option value="allow" {'selected' if pg_config.get('sslmode') == 'allow' else ''}>allow</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Connect Timeout (sec)</label>
<input type="number" id="pgConnectTimeout" class="form-control" value="{pg_config.get('connect_timeout', 5)}" placeholder="5">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Connection Pool Size</label>
<input type="number" id="pgPoolSize" class="form-control" value="{pg_config.get('pool_size', 10)}" placeholder="10">
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" onclick="testPgConnection()" class="btn btn-secondary">🔍 Test Connection</button>
<button type="button" onclick="migrateToPostgres()" class="btn btn-accent">🚀 Migrate SQLite Data to PostgreSQL</button>
</div>
</div>
<!-- Zero-Knowledge OpenPGP Data Encryption at Rest -->
<div style="background: #0f172a; padding: 20px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 20px;">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 8px;">
<span>🔐</span> Zero-Knowledge Data Encryption at Rest
</div>
<label style="display: inline-flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
<input type="checkbox" id="encryptAtRestCheck" {'checked' if config.get('encrypt_at_rest', False) else ''} style="width: 18px; height: 18px; accent-color: var(--accent);">
<span style="font-weight: 600; font-size: 14px;">Encrypt Sensitive Telemetry & Log Messages with Server OpenPGP Key</span>
</label>
<p style="font-size: 12px; color: var(--text-muted); line-height: 1.5; margin-left: 28px;">
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.
</p>
</div>
<!-- Telemetry Ingestion Cache Mode & Buffer Tuning -->
<!-- Telemetry Ingestion Cache Mode & Buffer Tuning (Top) -->
<div style="background: #0f172a; padding: 20px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 20px;">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between;">
<span style="display: flex; align-items: center; gap: 8px;">⚡ Ingestion Cache & Buffer Mode</span>
@@ -3508,6 +3447,90 @@ def serve_webui(request: Request = None):
</div>
</div>
<!-- Zero-Knowledge OpenPGP Data Encryption at Rest (Middle) -->
<div style="background: #0f172a; padding: 20px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 20px;">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 8px;">
<span>🔐</span> Zero-Knowledge Data Encryption at Rest
</div>
<label style="display: inline-flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
<input type="checkbox" id="encryptAtRestCheck" {'checked' if config.get('encrypt_at_rest', False) else ''} style="width: 18px; height: 18px; accent-color: var(--accent);">
<span style="font-weight: 600; font-size: 14px;">Encrypt Sensitive Telemetry & Log Messages with Server OpenPGP Key</span>
</label>
<p style="font-size: 12px; color: var(--text-muted); line-height: 1.5; margin-left: 28px;">
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.
</p>
</div>
<!-- Select Storage Engine Backend (Bottom) -->
<div style="background: #0f172a; padding: 20px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 20px;">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
<span>🗄️</span> Select Storage Engine Backend
</div>
<div style="display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px;">
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 12px 18px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
<input type="radio" name="dbEngineRadio" id="engineRadioSqlite" value="sqlite" {'checked' if storage_info['engine'] == 'sqlite' else ''} onchange="toggleStorageEngineForm()">
<span>💾 <strong>Local SQLite 3 (WAL Mode)</strong> <span style="color: var(--text-muted); font-size: 12px;">(Embedded, Zero Configuration)</span></span>
</label>
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 12px 18px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
<input type="radio" name="dbEngineRadio" id="engineRadioPostgres" value="postgres" {'checked' if storage_info['engine'] == 'postgres' else ''} onchange="toggleStorageEngineForm()">
<span>🐘 <strong>External PostgreSQL Database Server</strong> <span style="color: #34d399; font-size: 12px; font-weight: 600;">(Enterprise Clustering)</span></span>
</label>
</div>
<!-- External PostgreSQL Parameters -->
<div id="pgConfigSection" style="background: #151c2e; padding: 20px; border-radius: 10px; border: 1px solid var(--border); display: {'block' if storage_info['engine'] == 'postgres' else 'none'};">
<div style="font-size: 14px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between;">
<span style="display: flex; align-items: center; gap: 8px;">🐘 External PostgreSQL Connection Parameters</span>
<span class="badge badge-active">ENTERPRISE POOL</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 16px;">
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Host / IP Address</label>
<input type="text" id="pgHost" class="form-control" value="{pg_config.get('host', 'localhost')}" placeholder="localhost">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Port</label>
<input type="number" id="pgPort" class="form-control" value="{pg_config.get('port', 5432)}" placeholder="5432">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Database Name</label>
<input type="text" id="pgDatabase" class="form-control" value="{pg_config.get('database', 'logar')}" placeholder="logar">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Username</label>
<input type="text" id="pgUser" class="form-control" value="{pg_config.get('user', 'postgres')}" placeholder="postgres">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Password</label>
<input type="password" id="pgPassword" class="form-control" value="{pg_config.get('password', '')}" placeholder="••••••••">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">SSL Mode</label>
<select id="pgSslMode" class="form-control">
<option value="prefer" {'selected' if pg_config.get('sslmode') == 'prefer' else ''}>prefer</option>
<option value="require" {'selected' if pg_config.get('sslmode') == 'require' else ''}>require</option>
<option value="verify-ca" {'selected' if pg_config.get('sslmode') == 'verify-ca' else ''}>verify-ca</option>
<option value="verify-full" {'selected' if pg_config.get('sslmode') == 'verify-full' else ''}>verify-full</option>
<option value="disable" {'selected' if pg_config.get('sslmode') == 'disable' else ''}>disable</option>
<option value="allow" {'selected' if pg_config.get('sslmode') == 'allow' else ''}>allow</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Connect Timeout (sec)</label>
<input type="number" id="pgConnectTimeout" class="form-control" value="{pg_config.get('connect_timeout', 5)}" placeholder="5">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">Connection Pool Size</label>
<input type="number" id="pgPoolSize" class="form-control" value="{pg_config.get('pool_size', 10)}" placeholder="10">
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" onclick="testPgConnection()" class="btn btn-secondary">🔍 Test Connection</button>
<button type="button" onclick="migrateToPostgres()" class="btn btn-accent">🚀 Migrate SQLite Data to PostgreSQL</button>
</div>
</div>
</div>
<!-- Global Save Action Bar -->
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;">
<button type="button" onclick="saveDbConfig()" class="btn btn-primary" style="padding: 10px 24px; font-size: 14px;">
@@ -4855,11 +4878,11 @@ def main():
display_host = "localhost" if hermes_host in ("0.0.0.0", "127.0.0.1") else hermes_host
print("=" * 64)
print(f" LOGAR Server Hub: {config['server_name']}")
print(f" LOGAR Server Hub: {config.get('server_name', 'LOGAR Enterprise Hub')}")
print(f" Transport Security: {'mTLS (TLS 1.3)' if ssl_ctx else 'Plain TCP'}")
print(f" License Quota: {config.get('max_seats', 10)} Active Seats ({ca_info.get('license_id') or 'Local Sub-CA'})")
print(f" Upstream Authority: {config.get('license_server_url', 'https://logar.eibl.tech')}")
print(f" Server Encryption Fingerprint: {config['server_fingerprint']}")
print(f" Server Encryption Fingerprint: {config.get('server_fingerprint', 'N/A')}")
print(f" WebUI & Fleet Dashboard: http://{display_host}:{hermes_port}/webui")
print(f" Hermes Reporting API: http://{display_host}:{hermes_port}/api/hermes/report")
print("=" * 64)