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
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m18s
This commit is contained in:
+81
-58
@@ -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)
|
config["session_secret"] = secrets.token_hex(32)
|
||||||
if "db_path" not in config:
|
if "db_path" not in config:
|
||||||
config["db_path"] = DEFAULT_DB_FILE
|
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
|
# Resolve relative db_path and cert_dir to writable paths
|
||||||
config["db_path"] = resolve_writable_path(config["db_path"], "LOGAR-Server", DEFAULT_DB_FILE)
|
config["db_path"] = resolve_writable_path(config["db_path"], "LOGAR-Server", DEFAULT_DB_FILE)
|
||||||
@@ -3388,25 +3408,77 @@ def serve_webui(request: Request = None):
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-title">Database Storage Engine Configuration</span>
|
<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>
|
||||||
|
|
||||||
<div style="margin-bottom: 20px;">
|
<!-- Telemetry Ingestion Cache Mode & Buffer Tuning (Top) -->
|
||||||
<label class="form-label" style="font-size: 14px; font-weight: 700; margin-bottom: 8px;">Select Storage Engine Backend:</label>
|
<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>
|
||||||
|
<button type="button" onclick="flushIngestBuffer()" class="btn btn-accent" style="padding: 4px 10px; font-size: 11px;">🌊 Flush Buffer Now</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 14px;">
|
||||||
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
|
<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;">
|
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 14px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
|
||||||
|
<input type="radio" name="cacheModeRadio" id="cacheModeDirect" value="direct" {'checked' if config.get('cache_mode', 'direct') == 'direct' else ''} onchange="toggleCacheModeParams()">
|
||||||
|
<span>⚡ <strong>Immediate Direct Write</strong> <span style="color: var(--text-muted); font-size: 12px;">(Low Latency / Synchronous Verification)</span></span>
|
||||||
|
</label>
|
||||||
|
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 14px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
|
||||||
|
<input type="radio" name="cacheModeRadio" id="cacheModeBuffered" value="buffered" {'checked' if config.get('cache_mode', 'direct') == 'buffered' else ''} onchange="toggleCacheModeParams()">
|
||||||
|
<span>🚀 <strong>In-Memory Queue Buffer</strong> <span style="color: var(--text-muted); font-size: 12px;">(High-Throughput Batch Flushing)</span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="bufferParamsSection" style="display: {'grid' if config.get('cache_mode', 'direct') == 'buffered' else 'none'}; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; padding-top: 10px; border-top: 1px solid var(--border);">
|
||||||
|
<div class="form-group" style="margin-bottom: 0;">
|
||||||
|
<label class="form-label">Flush Interval (seconds)</label>
|
||||||
|
<input type="number" id="bufferFlushInterval" step="0.5" class="form-control" value="{config.get('flush_interval', queue_stats.get('flush_interval_seconds', 2.0))}" placeholder="2.0">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom: 0;">
|
||||||
|
<label class="form-label">Batch Size Threshold</label>
|
||||||
|
<input type="number" id="bufferBatchSize" class="form-control" value="{config.get('batch_size', queue_stats.get('batch_size_threshold', 100))}" placeholder="100">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom: 0;">
|
||||||
|
<label class="form-label">Max Buffer Capacity</label>
|
||||||
|
<input type="number" id="bufferMaxQueue" class="form-control" value="{config.get('max_queue', 50000)}" placeholder="50000">
|
||||||
|
</div>
|
||||||
|
</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()">
|
<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>
|
<span>💾 <strong>Local SQLite 3 (WAL Mode)</strong> <span style="color: var(--text-muted); font-size: 12px;">(Embedded, Zero Configuration)</span></span>
|
||||||
</label>
|
</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;">
|
<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()">
|
<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>
|
<span>🐘 <strong>External PostgreSQL Database Server</strong> <span style="color: #34d399; font-size: 12px; font-weight: 600;">(Enterprise Clustering)</span></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- External PostgreSQL Parameters -->
|
<!-- 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 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;">
|
<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 style="display: flex; align-items: center; gap: 8px;">🐘 External PostgreSQL Connection Parameters</span>
|
||||||
<span class="badge badge-active">ENTERPRISE POOL</span>
|
<span class="badge badge-active">ENTERPRISE POOL</span>
|
||||||
@@ -3457,55 +3529,6 @@ def serve_webui(request: Request = None):
|
|||||||
<button type="button" onclick="migrateToPostgres()" class="btn btn-accent">🚀 Migrate SQLite Data to PostgreSQL</button>
|
<button type="button" onclick="migrateToPostgres()" class="btn btn-accent">🚀 Migrate SQLite Data to PostgreSQL</button>
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
|
||||||
<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>
|
|
||||||
<button type="button" onclick="flushIngestBuffer()" class="btn btn-accent" style="padding: 4px 10px; font-size: 11px;">🌊 Flush Buffer Now</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 14px;">
|
|
||||||
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
|
|
||||||
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 14px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
|
|
||||||
<input type="radio" name="cacheModeRadio" id="cacheModeDirect" value="direct" {'checked' if config.get('cache_mode', 'direct') == 'direct' else ''} onchange="toggleCacheModeParams()">
|
|
||||||
<span>⚡ <strong>Immediate Direct Write</strong> <span style="color: var(--text-muted); font-size: 12px;">(Low Latency / Synchronous Verification)</span></span>
|
|
||||||
</label>
|
|
||||||
<label style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 14px; background: #151c2e; border: 1px solid var(--border); border-radius: 8px;">
|
|
||||||
<input type="radio" name="cacheModeRadio" id="cacheModeBuffered" value="buffered" {'checked' if config.get('cache_mode', 'direct') == 'buffered' else ''} onchange="toggleCacheModeParams()">
|
|
||||||
<span>🚀 <strong>In-Memory Queue Buffer</strong> <span style="color: var(--text-muted); font-size: 12px;">(High-Throughput Batch Flushing)</span></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="bufferParamsSection" style="display: {'grid' if config.get('cache_mode', 'direct') == 'buffered' else 'none'}; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; padding-top: 10px; border-top: 1px solid var(--border);">
|
|
||||||
<div class="form-group" style="margin-bottom: 0;">
|
|
||||||
<label class="form-label">Flush Interval (seconds)</label>
|
|
||||||
<input type="number" id="bufferFlushInterval" step="0.5" class="form-control" value="{config.get('flush_interval', queue_stats.get('flush_interval_seconds', 2.0))}" placeholder="2.0">
|
|
||||||
</div>
|
|
||||||
<div class="form-group" style="margin-bottom: 0;">
|
|
||||||
<label class="form-label">Batch Size Threshold</label>
|
|
||||||
<input type="number" id="bufferBatchSize" class="form-control" value="{config.get('batch_size', queue_stats.get('batch_size_threshold', 100))}" placeholder="100">
|
|
||||||
</div>
|
|
||||||
<div class="form-group" style="margin-bottom: 0;">
|
|
||||||
<label class="form-label">Max Buffer Capacity</label>
|
|
||||||
<input type="number" id="bufferMaxQueue" class="form-control" value="{config.get('max_queue', 50000)}" placeholder="50000">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Global Save Action Bar -->
|
<!-- Global Save Action Bar -->
|
||||||
@@ -4855,11 +4878,11 @@ def main():
|
|||||||
display_host = "localhost" if hermes_host in ("0.0.0.0", "127.0.0.1") else hermes_host
|
display_host = "localhost" if hermes_host in ("0.0.0.0", "127.0.0.1") else hermes_host
|
||||||
|
|
||||||
print("=" * 64)
|
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" 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" 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" 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" WebUI & Fleet Dashboard: http://{display_host}:{hermes_port}/webui")
|
||||||
print(f" Hermes Reporting API: http://{display_host}:{hermes_port}/api/hermes/report")
|
print(f" Hermes Reporting API: http://{display_host}:{hermes_port}/api/hermes/report")
|
||||||
print("=" * 64)
|
print("=" * 64)
|
||||||
|
|||||||
Reference in New Issue
Block a user