WebUI-Kopfleiste: Aufklappmenues statt elf Tabs
Umsetzung von #541, folgt auf #532. Elf Tabs nebeneinander waren nicht mehr zu ueberblicken. Die Leiste hat jetzt vier Eintraege, gruppiert nach dem, was man dort tut: - Dashboard - Analytics: Analytics, Accounting, Supervisor - Module: Moduleinstellungen (die allgemeine Modulansicht), danach alle Module - Einstellungen: Einstellungen, Jobs, Konten, Terminal / Logs Supervisor und Accounting stehen unter Analytics und zusaetzlich in der Modulliste, weil dort alle Module stehen sollen. Hervorgehoben wird das Oberelement nur an ihrem Hauptplatz (Analytics); der zweite Zugang traegt data-secondary. Sonst leuchteten bei geoeffnetem Supervisor zwei Menues. switchView() findet den aktiven Eintrag jetzt ueber data-view statt ueber eine Teilzeichenkette im onclick-Attribut. Das alte Verfahren hing am Zufall der Namen: Eine Ansicht, deren Name in einer anderen enthalten ist, haette beide markiert. Die Modulliste zeigt den Zustand aus /api/modules: Ein deaktiviertes Modul erscheint ausgegraut mit "deaktiviert" und ist nicht anklickbar, statt in eine leere Ansicht zu fuehren. Ein Modul, das der Server fuehrt, fuer das es im WebUI aber keine Ansicht gibt, wird mit "keine Ansicht" gelistet. Bedienbar mit Maus und Tastatur: Klick ausserhalb und Esc schliessen (Esc gibt den Fokus ans Oberelement zurueck), Pfeil nach unten oeffnet und springt in das Menue, Pfeiltasten laufen um und ueberspringen deaktivierte Eintraege, Tab verlaesst das Menue. aria-haspopup, aria-expanded und aria-current sind gesetzt. Unter 760 px haengen die Menues an der ganzen Leiste statt am Eintrag. Bei 400 px ragte "Module" sonst 35 px ueber den rechten Rand. Ein Nachmessen per Skript taugt dagegen nicht: In der Mobilansicht waechst der Layout-Viewport mit dem Ueberstand mit, und die Messung meldet genau dann "passt". Die Modulansicht heisst jetzt wie ihr Menueeintrag "Moduleinstellungen". Geprueft auf der Linux-Test-VM im Browser: Alle 12 Eintraege schalten die richtige Ansicht und heben das richtige Oberelement hervor, das Menue schliesst danach; Tastaturbedienung, deaktiviertes und unbekanntes Modul (simuliert) verhalten sich wie beschrieben. Vermessen bei 400, 700, 768 und 1024 px: kein Menue ragt aus dem Fenster, die Seite scrollt nie waagerecht. 556 von 556 Tests bestanden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,147 @@ body {
|
|||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Aufklappmenues der Kopfleiste (#541) */
|
||||||
|
.nav-dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Winkel statt Zeichen: "▾" sitzt je nach Schrift unterschiedlich hoch. */
|
||||||
|
.nav-dropdown-toggle::after {
|
||||||
|
content: "";
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor;
|
||||||
|
transform: translateY(-2px) rotate(45deg);
|
||||||
|
transition: transform 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-toggle[aria-expanded="true"]::after {
|
||||||
|
transform: translateY(1px) rotate(225deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-toggle[aria-expanded="true"]:not(.active) {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--surface-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: 0;
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: calc(100vw - 16px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
padding: 6px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ohne diese Regel gewinnt display:flex gegen das hidden-Attribut. */
|
||||||
|
.nav-menu[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item:hover:not(:disabled) {
|
||||||
|
background: var(--surface-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item.active {
|
||||||
|
color: var(--accent);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item:disabled {
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-sep {
|
||||||
|
height: 1px;
|
||||||
|
margin: 5px 4px;
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-label {
|
||||||
|
padding: 4px 10px 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schmale Fenster: Ein Menue, das am Oberelement klebt, ragt dort ueber den rechten Rand -
|
||||||
|
bei 400 px stand "Module" 35 px heraus. Nachmessen per Skript taugt als Abhilfe nicht:
|
||||||
|
Auf Mobilgeraeten waechst der Layout-Viewport mit dem Ueberstand mit, und die Messung meldet
|
||||||
|
genau dann "passt". Deshalb haengen die Menues hier an der ganzen Leiste statt am einzelnen
|
||||||
|
Eintrag. Ab 760 px passt die angeheftete Form: gemessen bei 768 px endet das aeusserste
|
||||||
|
Menue bei 701 px. */
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.header-nav {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% + 2px);
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -14,18 +14,53 @@
|
|||||||
<span style="font-size: 11px; padding: 2px 6px; background: var(--accent-soft); color: var(--accent); border-radius: 4px; font-weight: normal;">WebGUI</span>
|
<span style="font-size: 11px; padding: 2px 6px; background: var(--accent-soft); color: var(--accent); border-radius: 4px; font-weight: normal;">WebGUI</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="header-nav">
|
<!-- Kopfleiste (#541): Elf Tabs nebeneinander waren nicht mehr zu ueberblicken. Gruppiert
|
||||||
<button class="nav-link active" onclick="switchView('dashboard')">Dashboard</button>
|
wird nach dem, was man dort tut - auswerten, Module bedienen, den Betrieb einrichten.
|
||||||
<button class="nav-link" onclick="switchView('analytics')">Analytics</button>
|
|
||||||
<button class="nav-link" onclick="switchView('copytrading')">CopyTrading</button>
|
data-view verbindet einen Eintrag mit seiner Ansicht; switchView() hebt darueber den
|
||||||
<button class="nav-link" onclick="switchView('resolutionfarming')">Resolution Farming</button>
|
aktiven Eintrag und das Oberelement seines Menues hervor. data-module verbindet ihn mit
|
||||||
<button class="nav-link" onclick="switchView('supervisor')">Supervisor</button>
|
einem Modul, damit ein deaktiviertes Modul auch im Menue als deaktiviert erscheint.
|
||||||
<button class="nav-link" onclick="switchView('accounting')">Accounting</button>
|
data-secondary markiert einen zweiten Zugang: Supervisor und Accounting stehen unter
|
||||||
<button class="nav-link" onclick="switchView('accounts')">Konten</button>
|
Analytics und zusaetzlich in der Modulliste, hervorgehoben wird nur ihr Hauptplatz. -->
|
||||||
<button class="nav-link" onclick="switchView('jobs')">Jobs</button>
|
<nav class="header-nav" aria-label="Hauptnavigation">
|
||||||
<button class="nav-link" onclick="switchView('logs')">Terminal / Logs</button>
|
<button class="nav-link active" data-view="dashboard" aria-current="page" onclick="switchView('dashboard')">Dashboard</button>
|
||||||
<button class="nav-link" onclick="switchView('modules')">Module</button>
|
|
||||||
<button class="nav-link" onclick="switchView('settings')">Einstellungen</button>
|
<div class="nav-dropdown">
|
||||||
|
<button class="nav-link nav-dropdown-toggle" aria-haspopup="menu" aria-expanded="false" onclick="toggleNavDropdown(this)">Analytics</button>
|
||||||
|
<div class="nav-menu" role="menu" hidden>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="analytics" onclick="switchView('analytics')"><span>Analytics</span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="accounting" data-module="Accounting" onclick="switchView('accounting')"><span>Accounting</span><span class="nav-menu-hint"></span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="supervisor" data-module="Supervisor" onclick="switchView('supervisor')"><span>Supervisor</span><span class="nav-menu-hint"></span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-dropdown">
|
||||||
|
<button class="nav-link nav-dropdown-toggle" aria-haspopup="menu" aria-expanded="false" onclick="toggleNavDropdown(this)">Module</button>
|
||||||
|
<div class="nav-menu" role="menu" hidden>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="modules" onclick="switchView('modules')"><span>Moduleinstellungen</span></button>
|
||||||
|
<div class="nav-menu-sep" role="separator"></div>
|
||||||
|
<div class="nav-menu-label" aria-hidden="true">Module</div>
|
||||||
|
<!-- Die Liste ergaenzt refreshNavModules() um Module, die der Server kennt,
|
||||||
|
fuer die es im WebUI aber noch keine Ansicht gibt. -->
|
||||||
|
<div class="nav-menu-group" id="navModuleList" role="none">
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="copytrading" data-module="CopyTrading" onclick="switchView('copytrading')"><span>CopyTrading</span><span class="nav-menu-hint"></span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="resolutionfarming" data-module="ResolutionFarming" onclick="switchView('resolutionfarming')"><span>Resolution Farming</span><span class="nav-menu-hint"></span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="supervisor" data-module="Supervisor" data-secondary onclick="switchView('supervisor')"><span>Supervisor</span><span class="nav-menu-hint"></span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="accounting" data-module="Accounting" data-secondary onclick="switchView('accounting')"><span>Accounting</span><span class="nav-menu-hint"></span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-dropdown">
|
||||||
|
<button class="nav-link nav-dropdown-toggle" aria-haspopup="menu" aria-expanded="false" onclick="toggleNavDropdown(this)">Einstellungen</button>
|
||||||
|
<div class="nav-menu" role="menu" hidden>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="settings" onclick="switchView('settings')"><span>Einstellungen</span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="jobs" onclick="switchView('jobs')"><span>Jobs</span></button>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="accounts" onclick="switchView('accounts')"><span>Konten</span></button>
|
||||||
|
<div class="nav-menu-sep" role="separator"></div>
|
||||||
|
<button class="nav-menu-item" role="menuitem" data-view="logs" onclick="switchView('logs')"><span>Terminal / Logs</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@@ -1108,7 +1143,7 @@ Wähle ein Profil und stelle mir eine Frage zu deinen Positionen, Risiken oder d
|
|||||||
|
|
||||||
<!-- VIEW: MODULES -->
|
<!-- VIEW: MODULES -->
|
||||||
<section id="view-modules" class="view-panel">
|
<section id="view-modules" class="view-panel">
|
||||||
<h2 style="margin-bottom: 16px; font-size: 18px;">Strategie- & Analyse-Module</h2>
|
<h2 style="margin-bottom: 16px; font-size: 18px;">Moduleinstellungen</h2>
|
||||||
<div class="grid-2" id="modulesContainer">
|
<div class="grid-2" id="modulesContainer">
|
||||||
<div class="card"><div class="card-title">Lade Module …</div></div>
|
<div class="card"><div class="card-title">Lade Module …</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ let allLogs = [];
|
|||||||
|
|
||||||
// Init on DOM ready
|
// Init on DOM ready
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initNavMenus();
|
||||||
|
refreshNavModules();
|
||||||
initSseLogs();
|
initSseLogs();
|
||||||
refreshDashboard();
|
refreshDashboard();
|
||||||
loadDashboardAccounts();
|
loadDashboardAccounts();
|
||||||
@@ -18,12 +20,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Navigation
|
// Navigation
|
||||||
function switchView(viewName) {
|
function switchView(viewName) {
|
||||||
currentView = viewName;
|
currentView = viewName;
|
||||||
document.querySelectorAll('.nav-link').forEach(btn => btn.classList.remove('active'));
|
markActiveNav(viewName);
|
||||||
|
closeNavDropdowns();
|
||||||
document.querySelectorAll('.view-panel').forEach(p => p.classList.remove('active'));
|
document.querySelectorAll('.view-panel').forEach(p => p.classList.remove('active'));
|
||||||
|
|
||||||
const activeBtn = Array.from(document.querySelectorAll('.nav-link')).find(b => b.getAttribute('onclick')?.includes(viewName));
|
|
||||||
if (activeBtn) activeBtn.classList.add('active');
|
|
||||||
|
|
||||||
const panel = document.getElementById(`view-${viewName}`);
|
const panel = document.getElementById(`view-${viewName}`);
|
||||||
if (panel) panel.classList.add('active');
|
if (panel) panel.classList.add('active');
|
||||||
|
|
||||||
@@ -49,6 +49,143 @@ function switchView(viewName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Kopfleiste: Aufklappmenues (#541) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hebt den Eintrag der aktiven Ansicht hervor und dazu das Oberelement seines Menues -
|
||||||
|
* sonst sieht man bei geschlossenem Menue nicht, wo man ist. Supervisor und Accounting
|
||||||
|
* stehen zweimal im Menue; das Oberelement leuchtet nur an ihrem Hauptplatz (Analytics),
|
||||||
|
* der zweite Zugang in der Modulliste traegt data-secondary.
|
||||||
|
*/
|
||||||
|
function markActiveNav(viewName) {
|
||||||
|
document.querySelectorAll('.header-nav [data-view]').forEach(el => {
|
||||||
|
const on = el.dataset.view === viewName;
|
||||||
|
el.classList.toggle('active', on);
|
||||||
|
if (on) el.setAttribute('aria-current', 'page');
|
||||||
|
else el.removeAttribute('aria-current');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.nav-dropdown').forEach(dd => {
|
||||||
|
const home = dd.querySelector(`[data-view="${viewName}"]:not([data-secondary])`);
|
||||||
|
dd.querySelector('.nav-dropdown-toggle').classList.toggle('active', !!home);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNavDropdown(toggle) {
|
||||||
|
const menu = toggle.closest('.nav-dropdown').querySelector('.nav-menu');
|
||||||
|
const willOpen = menu.hidden;
|
||||||
|
closeNavDropdowns();
|
||||||
|
if (willOpen) {
|
||||||
|
menu.hidden = false;
|
||||||
|
toggle.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNavDropdowns() {
|
||||||
|
document.querySelectorAll('.nav-dropdown').forEach(dd => {
|
||||||
|
dd.querySelector('.nav-menu').hidden = true;
|
||||||
|
dd.querySelector('.nav-dropdown-toggle').setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function navMenuItems(menu) {
|
||||||
|
return Array.from(menu.querySelectorAll('.nav-menu-item')).filter(b => !b.disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initNavMenus() {
|
||||||
|
// Klick ausserhalb schliesst. Der Klick auf ein Oberelement selbst laeuft ueber
|
||||||
|
// toggleNavDropdown und kommt hier mit closest('.nav-dropdown') an - bleibt also offen.
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (!e.target.closest('.nav-dropdown')) closeNavDropdowns();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.header-nav')?.addEventListener('keydown', e => {
|
||||||
|
const dd = e.target.closest('.nav-dropdown');
|
||||||
|
if (!dd) return;
|
||||||
|
const toggle = dd.querySelector('.nav-dropdown-toggle');
|
||||||
|
const menu = dd.querySelector('.nav-menu');
|
||||||
|
|
||||||
|
if (e.key === 'Escape' && !menu.hidden) {
|
||||||
|
e.preventDefault();
|
||||||
|
closeNavDropdowns();
|
||||||
|
toggle.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfeil nach unten auf dem Oberelement oeffnet und springt in das Menue.
|
||||||
|
if (e.target === toggle && e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (menu.hidden) toggleNavDropdown(toggle);
|
||||||
|
navMenuItems(menu)[0]?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.contains(e.target) && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
const items = navMenuItems(menu);
|
||||||
|
const i = items.indexOf(e.target);
|
||||||
|
const next = e.key === 'Home' ? 0
|
||||||
|
: e.key === 'End' ? items.length - 1
|
||||||
|
: e.key === 'ArrowDown' ? (i + 1) % items.length
|
||||||
|
: (i - 1 + items.length) % items.length;
|
||||||
|
items[next]?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab verlaesst das Menue, statt durch alle Eintraege zu wandern.
|
||||||
|
if (e.key === 'Tab' && !menu.hidden) closeNavDropdowns();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt deaktivierte Module im Menue als deaktiviert und ergaenzt die Modulliste um
|
||||||
|
* Module, die der Server fuehrt, fuer die es im WebUI aber noch keine Ansicht gibt.
|
||||||
|
* Ohne Uebergabe wird die Liste selbst geholt; loadModules() reicht ihre mit durch.
|
||||||
|
*/
|
||||||
|
async function refreshNavModules(modules) {
|
||||||
|
if (!modules) {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/modules');
|
||||||
|
if (!res.ok) return;
|
||||||
|
modules = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActive = new Map(modules.map(m => [m.name.toLowerCase(), m.isActive]));
|
||||||
|
|
||||||
|
document.querySelectorAll('.header-nav [data-module]').forEach(item => {
|
||||||
|
const off = isActive.get(item.dataset.module.toLowerCase()) === false;
|
||||||
|
item.disabled = off;
|
||||||
|
item.title = off ? 'Modul ist deaktiviert – siehe Moduleinstellungen' : '';
|
||||||
|
const hint = item.querySelector('.nav-menu-hint');
|
||||||
|
if (hint) hint.textContent = off ? 'deaktiviert' : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = document.getElementById('navModuleList');
|
||||||
|
if (!list) return;
|
||||||
|
list.querySelectorAll('[data-no-view]').forEach(e => e.remove());
|
||||||
|
|
||||||
|
const shown = new Set(Array.from(list.querySelectorAll('[data-module]')).map(e => e.dataset.module.toLowerCase()));
|
||||||
|
modules.filter(m => !shown.has(m.name.toLowerCase())).forEach(m => {
|
||||||
|
const item = document.createElement('button');
|
||||||
|
item.className = 'nav-menu-item';
|
||||||
|
item.setAttribute('role', 'menuitem');
|
||||||
|
item.disabled = true;
|
||||||
|
item.dataset.noView = '1';
|
||||||
|
item.title = 'Für dieses Modul gibt es im WebUI noch keine eigene Ansicht.';
|
||||||
|
|
||||||
|
const label = document.createElement('span');
|
||||||
|
label.textContent = m.name;
|
||||||
|
const hint = document.createElement('span');
|
||||||
|
hint.className = 'nav-menu-hint';
|
||||||
|
hint.textContent = m.isActive ? 'keine Ansicht' : 'deaktiviert';
|
||||||
|
item.append(label, hint);
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard Refresh
|
// Dashboard Refresh
|
||||||
async function refreshDashboard() {
|
async function refreshDashboard() {
|
||||||
try {
|
try {
|
||||||
@@ -337,6 +474,7 @@ async function loadModules() {
|
|||||||
const res = await fetch('/api/modules');
|
const res = await fetch('/api/modules');
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const modules = await res.json();
|
const modules = await res.json();
|
||||||
|
refreshNavModules(modules);
|
||||||
|
|
||||||
const container = document.getElementById('modulesContainer');
|
const container = document.getElementById('modulesContainer');
|
||||||
container.innerHTML = modules.map(m => {
|
container.innerHTML = modules.map(m => {
|
||||||
|
|||||||
Reference in New Issue
Block a user