Add Version and System Info tab in admin dropdown menu
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m55s

This commit is contained in:
2026-10-11 09:51:06 +02:00
parent 72d9646cc4
commit 6b281626da
2 changed files with 239 additions and 4 deletions
+199 -4
View File
@@ -8,6 +8,8 @@ import html
import json
import socket
import sqlite3
import sys
import platform
from datetime import datetime, timezone
from typing import Optional, Dict, Any, List
@@ -1106,6 +1108,7 @@ class WebUIRenderer:
</div>
<div class="user-dropdown" id="userDropdown">
<div class="user-dropdown-header">Logged in as {auth_user}</div>
<button class="user-dropdown-item" onclick="openVersionInfoModal(); hideUserDropdown();">ℹ️ Version &amp; System Info</button>
<button class="user-dropdown-item" onclick="openTwoFactorModal(); hideUserDropdown();">🛡️ Two-Factor Auth (2FA)</button>
<button class="user-dropdown-item" onclick="openPasswordModal(); hideUserDropdown();">🔑 Change Password</button>
<a href="/logout" class="user-dropdown-item danger">🚪 Logout</a>
@@ -1397,6 +1400,69 @@ class WebUIRenderer:
</div>
</div>
<!-- Version & System Info Modal -->
<div id="versionInfoModal" class="modal">
<div class="modal-content" style="max-width: 580px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 14px;">
<div class="modal-title" style="margin-bottom: 0; display: flex; align-items: center; gap: 8px;">
<span>ℹ️</span> LOGAR Version &amp; Telemetry Info
</div>
<div style="cursor: pointer; font-size: 22px; color: var(--text-muted); line-height: 1;" onclick="closeModal('versionInfoModal')">&times;</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;">
<div style="background: #0b0f19; padding: 14px; border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 700; margin-bottom: 4px;">Server Hub Release</div>
<div style="font-size: 22px; font-weight: 800; font-family: 'JetBrains Mono', monospace; color: #60a5fa;">v{LOGAR_VERSION}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 2px;">LOGAR Central Ingestion Engine</div>
</div>
<div style="background: #0b0f19; padding: 14px; border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 700; margin-bottom: 4px;">Hosted Client Binaries</div>
<div style="font-size: 22px; font-weight: 800; font-family: 'JetBrains Mono', monospace; color: #34d399;">v{hosted_client_ver}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 2px;">Edge forwarder distribution release</div>
</div>
</div>
<div style="background: #0b0f19; border: 1px solid var(--border); border-radius: 8px; padding: 14px; margin-bottom: 18px; font-size: 12px; line-height: 1.8;">
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Repository Update Mode:</span>
<strong style="color: #fff;">{update_mode.upper()}</strong>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Distribution Source:</span>
<span style="color: #38bdf8; font-family: 'JetBrains Mono', monospace; font-size: 11px; word-break: break-all;">{html.escape(str(upstream_url))}</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Python Environment:</span>
<span style="color: #fff; font-family: 'JetBrains Mono', monospace; font-size: 11px;">Python {sys.version.split()[0]} ({platform.architecture()[0]})</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Host Platform &amp; OS:</span>
<span style="color: #fff;">{html.escape(platform.platform())}</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Cryptographic Transport:</span>
<span style="color: #34d399; font-weight: 600;">TLS 1.3 mTLS Encrypted</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Storage Backend Engine:</span>
<span style="color: #fff;">{config.get('storage_engine', 'sqlite').upper()} ({config.get('db_path', 'logar_state.db')})</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: var(--text-muted);">License Status &amp; Seats:</span>
<span style="color: #c084fc; font-weight: 600;">{summary['status']} ({summary['active_seats']}/{summary['max_seats']} Seats)</span>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; gap: 10px;">
<button onclick="closeModal('versionInfoModal'); switchTab('updates');" class="btn btn-outline" style="font-size: 12px;">
📦 Updates &amp; Distribution Tab
</button>
<button onclick="closeModal('versionInfoModal')" class="btn btn-primary">Close</button>
</div>
</div>
</div>
<!-- Password Modal -->
<div id="passwordModal" class="modal">
<div class="modal-content" style="max-width: 440px;">
@@ -1546,6 +1612,7 @@ class WebUIRenderer:
function openExportProfileModal() {{ openModal('exportProfileModal'); }}
function openImportLicModal() {{ openModal('importLicModal'); }}
function openPasswordModal() {{ openModal('passwordModal'); }}
function openVersionInfoModal() {{ openModal('versionInfoModal'); }}
function downloadLicRequest() {{
window.location.href = '/api/license/export-lic';
@@ -2241,8 +2308,13 @@ class WebUIRenderer:
db_path = config.get("db_path", "logar_state.db")
auth_display = html.escape(str(auth_user or "admin"))
# Check Best Practice licensing feature
# Check Best Practice licensing feature & summary
is_bp_licensed = LicenseManager.is_feature_licensed("best_practices", db_path=db_path, server_state=state)
summary = LicenseManager.get_license_summary(state, db_path=db_path)
update_status = state.get("update_status", {})
update_mode = update_status.get("update_mode") or config.get("update_mode", "official")
upstream_url = update_status.get("upstream_url") or config.get("upstream_url", "https://gitea.eibl.tech/api/v1/repos/me0nline/LOGAR/releases/latest")
hosted_client_ver = state.get("hosted_client_version") or LOGAR_VERSION
# Query clients and host telemetry
conn = sqlite3.connect(db_path)
@@ -2661,6 +2733,34 @@ class WebUIRenderer:
.logo-title {{ font-size: 20px; font-weight: 800; letter-spacing: -0.5px; color: #fff; }}
.logo-sub {{ font-size: 12px; color: var(--text-muted); margin-top: 2px; }}
/* User Dropdown */
.user-menu-container {{ position: relative; display: inline-block; }}
.user-tag {{
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 14px; background: #0f172a;
border: 1px solid var(--border); border-radius: 8px;
font-size: 13px; font-weight: 600; cursor: pointer;
user-select: none;
}}
.user-tag:hover {{ border-color: var(--primary); background: #151c2e; }}
.user-dropdown {{
display: none; position: absolute; right: 0; top: calc(100% + 6px);
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
min-width: 220px; z-index: 200; overflow: hidden;
}}
.user-menu-container:hover .user-dropdown, .user-menu-container:focus-within .user-dropdown, .user-dropdown.show {{ display: block; }}
.user-dropdown-header {{ padding: 10px 14px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); background: #0f172a; border-bottom: 1px solid var(--border); }}
.user-dropdown-item {{
display: flex; align-items: center; gap: 8px; padding: 10px 14px;
font-size: 13px; color: var(--text); text-decoration: none; cursor: pointer;
border: none; width: 100%; background: transparent; text-align: left;
transition: background 0.2s;
}}
.user-dropdown-item:hover {{ background: rgba(59, 130, 246, 0.1); color: #fff; }}
.user-dropdown-item.danger {{ color: #f87171; }}
.user-dropdown-item.danger:hover {{ background: rgba(239, 68, 68, 0.15); }}
/* Buttons & Badges */
.btn {{
display: inline-flex; align-items: center; gap: 6px;
@@ -2777,9 +2877,17 @@ class WebUIRenderer:
<a href="/webui#pki" class="btn btn-outline" style="font-size: 13px;">
📜 Sub-CA &amp; Licensing
</a>
<span style="font-size: 12px; color: var(--text-muted); background: #0f172a; padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border);">
👤 {auth_display}
</span>
<div class="user-menu-container" tabindex="0">
<div class="user-tag" onclick="toggleUserDropdown(event)">
<span>👤 <strong>{auth_display}</strong></span>
<span style="font-size: 10px; color: var(--text-muted);">▼</span>
</div>
<div class="user-dropdown" id="userDropdown">
<div class="user-dropdown-header">Logged in as {auth_display}</div>
<button class="user-dropdown-item" onclick="openVersionInfoModal(); hideUserDropdown();">ℹ️ Version &amp; System Info</button>
<a href="/logout" class="user-dropdown-item danger">🚪 Logout</a>
</div>
</div>
</div>
</header>
@@ -2900,7 +3008,94 @@ class WebUIRenderer:
</div>
</div>
<!-- Version & System Telemetry Info Modal -->
<div id="versionInfoModal" class="modal-overlay" onclick="closeVersionInfoModal(event)">
<div class="modal" onclick="event.stopPropagation()" style="max-width: 560px;">
<div class="modal-header">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 18px;">ℹ️</span>
<h4 style="font-size: 15px; font-weight: 700; color: #fff;">LOGAR Version &amp; System Telemetry</h4>
</div>
<button onclick="closeVersionInfoModal()" class="btn btn-outline" style="padding: 4px 8px; font-size: 12px;">✕</button>
</div>
<div class="modal-body">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;">
<div style="background: #0b0f19; padding: 14px; border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 700; margin-bottom: 4px;">Server Hub Release</div>
<div style="font-size: 22px; font-weight: 800; font-family: 'JetBrains Mono', monospace; color: #60a5fa;">v{LOGAR_VERSION}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 2px;">LOGAR Central Ingestion Engine</div>
</div>
<div style="background: #0b0f19; padding: 14px; border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 700; margin-bottom: 4px;">Hosted Client Binaries</div>
<div style="font-size: 22px; font-weight: 800; font-family: 'JetBrains Mono', monospace; color: #34d399;">v{hosted_client_ver}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 2px;">Edge forwarder distribution release</div>
</div>
</div>
<div style="background: #0b0f19; border: 1px solid var(--border); border-radius: 8px; padding: 14px; font-size: 12px; line-height: 1.8;">
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Repository Update Mode:</span>
<strong style="color: #fff;">{update_mode.upper()}</strong>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Distribution Source:</span>
<span style="color: #38bdf8; font-family: 'JetBrains Mono', monospace; font-size: 11px; word-break: break-all;">{html.escape(str(upstream_url))}</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Python Environment:</span>
<span style="color: #fff; font-family: 'JetBrains Mono', monospace; font-size: 11px;">Python {sys.version.split()[0]} ({platform.architecture()[0]})</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Host Platform &amp; OS:</span>
<span style="color: #fff;">{html.escape(platform.platform())}</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Cryptographic Transport:</span>
<span style="color: #34d399; font-weight: 600;">TLS 1.3 mTLS Encrypted</span>
</div>
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding-bottom: 6px; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Storage Backend Engine:</span>
<span style="color: #fff;">{config.get('storage_engine', 'sqlite').upper()} ({config.get('db_path', 'logar_state.db')})</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: var(--text-muted);">License Status &amp; Seats:</span>
<span style="color: #c084fc; font-weight: 600;">{summary['status']} ({summary['active_seats']}/{summary['max_seats']} Seats)</span>
</div>
</div>
</div>
<div class="modal-footer" style="display: flex; justify-content: space-between; align-items: center;">
<a href="/webui#updates" class="btn btn-outline" style="font-size: 12px; text-decoration: none;">
📦 Updates &amp; Distribution Tab
</a>
<button onclick="closeVersionInfoModal()" class="btn btn-primary">Close</button>
</div>
</div>
</div>
<script>
function toggleUserDropdown(e) {{
if (e) e.stopPropagation();
const d = document.getElementById('userDropdown');
if (d) d.classList.toggle('show');
}}
function hideUserDropdown() {{
const d = document.getElementById('userDropdown');
if (d) d.classList.remove('show');
}}
window.addEventListener('click', () => hideUserDropdown());
function openVersionInfoModal() {{
const m = document.getElementById('versionInfoModal');
if (m) m.style.display = 'flex';
}}
function closeVersionInfoModal(e) {{
const m = document.getElementById('versionInfoModal');
if (m) m.style.display = 'none';
}}
let currentFilter = 'all';
function setFilter(category, btn) {{
@@ -196,6 +196,46 @@ class TestServerRolesAndBestPractices(unittest.TestCase):
self.assertIn("warn_count", eval_data)
self.assertIn("fail_count", eval_data)
def test_admin_version_info_modal(self):
"""Verifies admin dropdown menu contains Version & System Info option and renders telemetry modal in both /webui and /client-info."""
from src.version import LOGAR_VERSION
# Check /webui
res_webui = self.client.get("/webui", cookies={SessionManager.SESSION_COOKIE_NAME: self.session_token})
self.assertEqual(res_webui.status_code, 200)
self.assertIn("id=\"userDropdown\"", res_webui.text)
self.assertIn("Version &amp; System Info", res_webui.text)
self.assertIn("openVersionInfoModal()", res_webui.text)
self.assertIn("id=\"versionInfoModal\"", res_webui.text)
self.assertIn(f"v{LOGAR_VERSION}", res_webui.text)
self.assertIn("Server Hub Release", res_webui.text)
self.assertIn("Hosted Client Binaries", res_webui.text)
self.assertIn("Repository Update Mode:", res_webui.text)
self.assertIn("Distribution Source:", res_webui.text)
self.assertIn("Python Environment:", res_webui.text)
self.assertIn("Host Platform &amp; OS:", res_webui.text)
self.assertIn("TLS 1.3 mTLS Encrypted", res_webui.text)
self.assertIn("Storage Backend Engine:", res_webui.text)
self.assertIn("License Status &amp; Seats:", res_webui.text)
# Check /client-info
res_client_info = self.client.get("/client-info", cookies={SessionManager.SESSION_COOKIE_NAME: self.session_token})
self.assertEqual(res_client_info.status_code, 200)
self.assertIn("id=\"userDropdown\"", res_client_info.text)
self.assertIn("Version &amp; System Info", res_client_info.text)
self.assertIn("openVersionInfoModal()", res_client_info.text)
self.assertIn("id=\"versionInfoModal\"", res_client_info.text)
self.assertIn(f"v{LOGAR_VERSION}", res_client_info.text)
self.assertIn("Server Hub Release", res_client_info.text)
self.assertIn("Hosted Client Binaries", res_client_info.text)
self.assertIn("Repository Update Mode:", res_client_info.text)
self.assertIn("Distribution Source:", res_client_info.text)
self.assertIn("Python Environment:", res_client_info.text)
self.assertIn("Host Platform &amp; OS:", res_client_info.text)
self.assertIn("TLS 1.3 mTLS Encrypted", res_client_info.text)
self.assertIn("Storage Backend Engine:", res_client_info.text)
self.assertIn("License Status &amp; Seats:", res_client_info.text)
if __name__ == "__main__":
unittest.main()