layout
This commit is contained in:
@@ -1,30 +1,4 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
<script src="<?= e(app()->assets()->versioned('/assets/js/app.js')) ?>" defer></script>
|
||||||
<footer class="site-footer">
|
|
||||||
<div class="footer-left">© <?= date('Y') ?> Nexus</div>
|
|
||||||
<div class="footer-right">Security first · Internal only</div>
|
|
||||||
</footer>
|
|
||||||
<?php asset_scripts('footer'); ?>
|
|
||||||
<?php if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL): ?>
|
|
||||||
<div class="debug-modal" id="debug-modal" aria-hidden="true">
|
|
||||||
<div class="debug-modal__backdrop" data-debug-close></div>
|
|
||||||
<div class="debug-modal__panel card">
|
|
||||||
<div class="debug-modal__header">
|
|
||||||
<strong>Debug Logs</strong>
|
|
||||||
<button class="debug-modal__close" data-debug-close type="button">✕</button>
|
|
||||||
</div>
|
|
||||||
<div class="debug-modal__body">
|
|
||||||
<div class="debug-modal__list">
|
|
||||||
<div class="muted">Logs</div>
|
|
||||||
<ul id="debug-log-list"></ul>
|
|
||||||
</div>
|
|
||||||
<div class="debug-modal__content">
|
|
||||||
<div class="muted">Inhalt</div>
|
|
||||||
<pre id="debug-log-content"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,134 +1,23 @@
|
|||||||
<?php
|
|
||||||
/** @var \App\App $app */
|
|
||||||
$app = app();
|
|
||||||
$app->assets()->addStyle('/assets/css/app.css', 'early');
|
|
||||||
$app->assets()->addScript('/assets/js/app.js', 'footer', true);
|
|
||||||
$theme = user_theme();
|
|
||||||
$currentModule = current_module_name();
|
|
||||||
$path = $app->request()->path();
|
|
||||||
$moduleMenu = [];
|
|
||||||
$moduleSidebar = [];
|
|
||||||
if ($currentModule) {
|
|
||||||
$module = modules()->get($currentModule);
|
|
||||||
$moduleMenu = $module['menu'] ?? [];
|
|
||||||
$moduleSidebar = $module['sidebar'] ?? [];
|
|
||||||
}
|
|
||||||
$sidebarEnabled = !empty($moduleSidebar['enabled']);
|
|
||||||
$sidebarCollapsible = !empty($moduleSidebar['collapsible']);
|
|
||||||
$sidebarDefault = ($moduleSidebar['default'] ?? 'collapsed') === 'open' ? 'open' : 'collapsed';
|
|
||||||
$sidebarItems = $moduleSidebar['items'] ?? [];
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="de" data-theme="<?= e($theme) ?>" data-accent="logo">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><?= htmlspecialchars(t('common.title'), ENT_QUOTES) ?></title>
|
<title>Nexus</title>
|
||||||
<?php asset_styles(); ?>
|
<script>
|
||||||
<?php asset_scripts('header'); ?>
|
(() => {
|
||||||
|
try {
|
||||||
|
const theme = localStorage.getItem('nexus.theme') || 'day';
|
||||||
|
const accent = localStorage.getItem('nexus.accent') || 'logo';
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
document.documentElement.dataset.accent = accent;
|
||||||
|
} catch (error) {
|
||||||
|
document.documentElement.dataset.theme = 'day';
|
||||||
|
document.documentElement.dataset.accent = 'logo';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="<?= e(app()->assets()->versioned('/assets/css/app.css')) ?>">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="bg-orb orb-a"></div>
|
<main class="main-shell">
|
||||||
<div class="bg-orb orb-b"></div>
|
|
||||||
|
|
||||||
<header class="site-header card">
|
|
||||||
<div class="logo-wrap">
|
|
||||||
<img src="/assets/images/logo.png" alt="Nexus Logo" class="site-logo">
|
|
||||||
</div>
|
|
||||||
<nav class="header-nav">
|
|
||||||
<a class="nav-link" href="/">Dashboard</a>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="nav-link dropdown-toggle" type="button">Module ▾</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<?php foreach (app()->auth()->filterModules(modules()->all()) as $m): ?>
|
|
||||||
<?php if (!empty($m['enabled'])): ?>
|
|
||||||
<a class="dropdown-item" href="/module/<?= e($m['name']) ?>"><?= e($m['title']) ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="/modules">Module Übersicht</a>
|
|
||||||
<a class="dropdown-item" href="/modules/install">Modul installieren/aktivieren</a>
|
|
||||||
<a class="dropdown-item" href="/settings">Settings</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown">
|
|
||||||
<?php if (auth_enabled() && auth_user()): ?>
|
|
||||||
<button class="avatar-btn" type="button">
|
|
||||||
<span class="avatar"><?= e(auth_initials()) ?></span>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<div class="dropdown-header"><?= e(auth_display_name()) ?></div>
|
|
||||||
<a class="dropdown-item" href="/settings">User-Settings</a>
|
|
||||||
<a class="dropdown-item" href="/auth/logout">Logout</a>
|
|
||||||
</div>
|
|
||||||
<?php elseif (auth_enabled()): ?>
|
|
||||||
<a class="nav-link" href="/auth/login">Login</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($moduleMenu): ?>
|
|
||||||
<div class="module-subnav card">
|
|
||||||
<?php foreach ($moduleMenu as $entry): ?>
|
|
||||||
<?php
|
|
||||||
$href = (string)($entry['href'] ?? '');
|
|
||||||
$label = (string)($entry['label'] ?? 'Link');
|
|
||||||
$children = is_array($entry['children'] ?? null) ? $entry['children'] : [];
|
|
||||||
$isSetup = $href !== '' && str_starts_with($href, '/modules/setup/');
|
|
||||||
if ($isSetup) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php if ($children): ?>
|
|
||||||
<div class="nav-dropdown">
|
|
||||||
<button class="nav-link nav-link-button" type="button"><?= e($label) ?> ▾</button>
|
|
||||||
<div class="nav-dropdown-menu">
|
|
||||||
<?php foreach ($children as $child): ?>
|
|
||||||
<?php
|
|
||||||
$childHref = (string)($child['href'] ?? '');
|
|
||||||
if ($childHref !== '' && str_starts_with($childHref, '/modules/setup/')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<a class="nav-link" href="<?= e($childHref ?: '#') ?>">
|
|
||||||
<?= e((string)($child['label'] ?? 'Link')) ?>
|
|
||||||
</a>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<a class="nav-link" href="<?= e($href ?: '#') ?>">
|
|
||||||
<?= e($label) ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="layout-body no-sidebar sidebar-collapsed"
|
|
||||||
data-sidebar-enabled="0"
|
|
||||||
data-sidebar-collapsible="0"
|
|
||||||
data-sidebar-default="collapsed">
|
|
||||||
|
|
||||||
<main class="main-content">
|
|
||||||
|
|
||||||
<button class="console-fab" type="button" data-console-fab title="Konsole öffnen">
|
|
||||||
<span>›</span>
|
|
||||||
</button>
|
|
||||||
<div class="console-modal" data-console-modal aria-hidden="true">
|
|
||||||
<div class="console-modal-card">
|
|
||||||
<div class="console-modal-header">
|
|
||||||
<strong>Konsole Tabs</strong>
|
|
||||||
<button class="icon-button" type="button" data-console-close>×</button>
|
|
||||||
</div>
|
|
||||||
<div class="console-tabs" data-console-tabs style="margin-top:12px;">
|
|
||||||
<div class="console-tab-bar" data-console-tab-bar></div>
|
|
||||||
<div class="console-tab-panels" data-console-tab-panels></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL && auth_is_admin()): ?>
|
|
||||||
<button class="debug-fab" data-debug-open title="Debug" type="button">🐞</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,3 @@
|
|||||||
(() => {
|
|
||||||
const layout = document.querySelector('.layout-body');
|
|
||||||
if (!layout) return;
|
|
||||||
|
|
||||||
const enabled = layout.dataset.sidebarEnabled === '1';
|
|
||||||
const collapsible = layout.dataset.sidebarCollapsible === '1';
|
|
||||||
const defaultState = layout.dataset.sidebarDefault || 'collapsed';
|
|
||||||
|
|
||||||
const toggles = document.querySelectorAll('[data-sidebar-toggle]');
|
|
||||||
if (!enabled || !collapsible) {
|
|
||||||
toggles.forEach((t) => t.remove());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const saved = localStorage.getItem('sidebar-state');
|
|
||||||
const initial = saved || defaultState;
|
|
||||||
if (initial === 'open') {
|
|
||||||
layout.classList.add('sidebar-open');
|
|
||||||
} else {
|
|
||||||
layout.classList.remove('sidebar-open');
|
|
||||||
}
|
|
||||||
|
|
||||||
toggles.forEach((toggle) => {
|
|
||||||
toggle.addEventListener('click', () => {
|
|
||||||
layout.classList.toggle('sidebar-open');
|
|
||||||
localStorage.setItem('sidebar-state', layout.classList.contains('sidebar-open') ? 'open' : 'collapsed');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
document.documentElement.classList.add('js');
|
document.documentElement.classList.add('js');
|
||||||
|
|
||||||
function readThemePreference(key, fallback) {
|
function readThemePreference(key, fallback) {
|
||||||
@@ -76,101 +46,3 @@ if (themeAccentSelect) {
|
|||||||
for (const element of document.querySelectorAll('[data-reveal]')) {
|
for (const element of document.querySelectorAll('[data-reveal]')) {
|
||||||
element.classList.add('reveal');
|
element.classList.add('reveal');
|
||||||
}
|
}
|
||||||
|
|
||||||
(() => {
|
|
||||||
const openBtn = document.querySelector('[data-debug-open]');
|
|
||||||
const modal = document.getElementById('debug-modal');
|
|
||||||
if (!openBtn || !modal) return;
|
|
||||||
|
|
||||||
const listEl = document.getElementById('debug-log-list');
|
|
||||||
const contentEl = document.getElementById('debug-log-content');
|
|
||||||
const closeEls = modal.querySelectorAll('[data-debug-close]');
|
|
||||||
|
|
||||||
const open = () => {
|
|
||||||
modal.classList.add('is-open');
|
|
||||||
modal.setAttribute('aria-hidden', 'false');
|
|
||||||
loadList();
|
|
||||||
startRefresh();
|
|
||||||
};
|
|
||||||
const close = () => {
|
|
||||||
modal.classList.remove('is-open');
|
|
||||||
modal.setAttribute('aria-hidden', 'true');
|
|
||||||
if (refreshTimer) {
|
|
||||||
clearInterval(refreshTimer);
|
|
||||||
refreshTimer = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let activeFile = null;
|
|
||||||
let refreshTimer = null;
|
|
||||||
|
|
||||||
const loadList = async () => {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/debug?list=1', { cache: 'no-store' });
|
|
||||||
if (!res.ok) {
|
|
||||||
listEl.innerHTML = `<li class=\"muted\">Fehler: ${res.status}</li>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const files = await res.json();
|
|
||||||
listEl.innerHTML = '';
|
|
||||||
files.forEach((f) => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = '#';
|
|
||||||
a.textContent = f;
|
|
||||||
a.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
loadFile(f);
|
|
||||||
});
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.appendChild(a);
|
|
||||||
listEl.appendChild(li);
|
|
||||||
});
|
|
||||||
if (files.includes('oidc_login.log')) {
|
|
||||||
loadFile('oidc_login.log');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
listEl.innerHTML = '<li class=\"muted\">Fehler beim Laden der Logs.</li>';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadFile = async (name) => {
|
|
||||||
activeFile = name;
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/debug?raw=1&file=${encodeURIComponent(name)}&tail=200`, { cache: 'no-store' });
|
|
||||||
if (!res.ok) {
|
|
||||||
contentEl.textContent = `Fehler: ${res.status}`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const text = await res.text();
|
|
||||||
contentEl.textContent = formatLog(text);
|
|
||||||
} catch (e) {
|
|
||||||
contentEl.textContent = 'Fehler beim Laden der Datei.';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatLog = (text) => {
|
|
||||||
const lines = text.split(/\\r?\\n/).filter(Boolean);
|
|
||||||
const pretty = lines.map((line) => {
|
|
||||||
try {
|
|
||||||
const obj = JSON.parse(line);
|
|
||||||
return JSON.stringify(obj, null, 2);
|
|
||||||
} catch (e) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return pretty.join('\\n\\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
const startRefresh = () => {
|
|
||||||
if (refreshTimer) clearInterval(refreshTimer);
|
|
||||||
refreshTimer = setInterval(() => {
|
|
||||||
if (activeFile) loadFile(activeFile);
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
openBtn.addEventListener('click', open);
|
|
||||||
closeEls.forEach((el) => el.addEventListener('click', close));
|
|
||||||
if (modal.classList.contains('is-open')) {
|
|
||||||
startRefresh();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|||||||
@@ -41,4 +41,9 @@ final class Assets
|
|||||||
public function styles(): array { return $this->styles; }
|
public function styles(): array { return $this->styles; }
|
||||||
public function headerScripts(): array { return $this->scriptsHeader; }
|
public function headerScripts(): array { return $this->scriptsHeader; }
|
||||||
public function footerScripts(): array { return $this->scriptsFooter; }
|
public function footerScripts(): array { return $this->scriptsFooter; }
|
||||||
|
|
||||||
|
public function versioned(string $path): string
|
||||||
|
{
|
||||||
|
return $path . '?v=' . rawurlencode($this->config->assetVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user