Add Version and System Info tab in admin dropdown menu
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m55s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m55s
This commit is contained in:
+199
-4
@@ -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 & 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 & Telemetry Info
|
||||
</div>
|
||||
<div style="cursor: pointer; font-size: 22px; color: var(--text-muted); line-height: 1;" onclick="closeModal('versionInfoModal')">×</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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & Seats:", res_client_info.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user