2026-03-14 21:08:57 -04:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* LOTUSGUILD TERMINAL DESIGN SYSTEM — PHP Base Layout
|
|
|
|
|
* Copy this into your PHP app as the base view template.
|
|
|
|
|
*
|
|
|
|
|
* Requires:
|
|
|
|
|
* - middleware/SecurityHeadersMiddleware.php (provides $nonce)
|
|
|
|
|
* - middleware/CsrfMiddleware.php (provides CSRF token)
|
|
|
|
|
* - middleware/AuthMiddleware.php (provides $currentUser)
|
|
|
|
|
* - config/config.php (provides $config)
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* Call SecurityHeadersMiddleware::apply() before any output.
|
|
|
|
|
* Then include this file (or extend it) in your view.
|
|
|
|
|
*
|
|
|
|
|
* Variables expected:
|
|
|
|
|
* $nonce string CSP nonce from SecurityHeadersMiddleware::getNonce()
|
|
|
|
|
* $currentUser array ['username', 'name', 'is_admin', 'groups']
|
|
|
|
|
* $pageTitle string Page <title> suffix
|
2026-04-18 13:53:29 -04:00
|
|
|
* $activeNav string Which nav key is active — must match a $navLinks entry
|
2026-03-14 21:08:57 -04:00
|
|
|
* $config array From config/config.php
|
2026-04-18 13:53:29 -04:00
|
|
|
* $navLinks array Navigation items:
|
|
|
|
|
* [['href' => '/path', 'key' => 'mykey', 'label' => 'My Page'], ...]
|
|
|
|
|
* Nested (dropdown):
|
|
|
|
|
* ['label' => 'Admin', 'key' => 'admin', 'adminOnly' => true, 'children' => [
|
|
|
|
|
* ['href' => '/admin/users', 'label' => 'Users'],
|
|
|
|
|
* ]]
|
2026-03-14 21:08:57 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Defensive defaults
|
|
|
|
|
$nonce = $nonce ?? '';
|
|
|
|
|
$currentUser = $currentUser ?? [];
|
|
|
|
|
$pageTitle = $pageTitle ?? 'Dashboard';
|
|
|
|
|
$activeNav = $activeNav ?? '';
|
|
|
|
|
$config = $config ?? [];
|
2026-04-18 13:53:29 -04:00
|
|
|
$navLinks = $navLinks ?? [];
|
2026-03-14 21:08:57 -04:00
|
|
|
$isAdmin = $currentUser['is_admin'] ?? false;
|
|
|
|
|
?>
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title><?php echo htmlspecialchars($pageTitle); ?> — <?php echo htmlspecialchars($config['APP_NAME'] ?? 'LotusGuild'); ?></title>
|
|
|
|
|
<meta name="description" content="LotusGuild infrastructure management">
|
|
|
|
|
|
|
|
|
|
<!-- Unified design system CSS -->
|
|
|
|
|
<link rel="stylesheet" href="/web_template/base.css">
|
|
|
|
|
<!-- App-specific CSS (extends base, never overrides variables without good reason) -->
|
2026-04-18 13:53:29 -04:00
|
|
|
<link rel="stylesheet" href="/assets/css/app.css?v=<?php echo $config['CSS_VERSION'] ?? '1'; ?>">
|
2026-03-14 21:08:57 -04:00
|
|
|
|
|
|
|
|
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
<!-- Boot overlay — runs once per session via lt.boot.run() -->
|
|
|
|
|
<div id="lt-boot" class="lt-boot-overlay"
|
|
|
|
|
data-app-name="<?php echo htmlspecialchars($config['APP_NAME'] ?? 'APP'); ?>"
|
|
|
|
|
style="display:none">
|
|
|
|
|
<pre id="lt-boot-text" class="lt-boot-text"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- =========================================================
|
|
|
|
|
HEADER
|
|
|
|
|
========================================================= -->
|
|
|
|
|
<header class="lt-header">
|
|
|
|
|
<div class="lt-header-left">
|
|
|
|
|
|
|
|
|
|
<div class="lt-brand">
|
|
|
|
|
<a href="/" class="lt-brand-title" style="text-decoration:none">
|
|
|
|
|
<?php echo htmlspecialchars($config['APP_NAME'] ?? 'APP'); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<span class="lt-brand-subtitle"><?php echo htmlspecialchars($config['APP_SUBTITLE'] ?? 'LotusGuild Infrastructure'); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<nav class="lt-nav" aria-label="Main navigation">
|
2026-04-18 13:53:29 -04:00
|
|
|
<?php foreach ($navLinks as $link): ?>
|
|
|
|
|
<?php
|
|
|
|
|
$skipAdminOnly = !empty($link['adminOnly']) && !$isAdmin;
|
|
|
|
|
if ($skipAdminOnly) continue;
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
|
|
<?php if (!empty($link['children'])): ?>
|
|
|
|
|
<?php $parentActive = str_starts_with($activeNav, $link['key']); ?>
|
|
|
|
|
<div class="lt-nav-dropdown">
|
|
|
|
|
<a href="#" class="lt-nav-link <?php echo $parentActive ? 'active' : ''; ?>">
|
|
|
|
|
<?php echo htmlspecialchars($link['label']); ?> ▾
|
|
|
|
|
</a>
|
|
|
|
|
<ul class="lt-nav-dropdown-menu">
|
|
|
|
|
<?php foreach ($link['children'] as $child): ?>
|
|
|
|
|
<li><a href="<?php echo htmlspecialchars($child['href']); ?>"><?php echo htmlspecialchars($child['label']); ?></a></li>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<a href="<?php echo htmlspecialchars($link['href']); ?>"
|
|
|
|
|
class="lt-nav-link <?php echo $activeNav === $link['key'] ? 'active' : ''; ?>">
|
|
|
|
|
<?php echo htmlspecialchars($link['label']); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<?php endforeach; ?>
|
2026-03-14 21:08:57 -04:00
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="lt-header-right">
|
|
|
|
|
<?php if (!empty($currentUser['name'])): ?>
|
|
|
|
|
<span class="lt-header-user"><?php echo htmlspecialchars($currentUser['name']); ?></span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<?php if ($isAdmin): ?>
|
|
|
|
|
<span class="lt-badge-admin">admin</span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<!-- =========================================================
|
|
|
|
|
MAIN CONTENT — provided by the including view
|
|
|
|
|
========================================================= -->
|
|
|
|
|
<main class="lt-main lt-container">
|
|
|
|
|
|
|
|
|
|
<!-- CONTENT SLOT: the including view outputs its content here -->
|
|
|
|
|
<?php
|
|
|
|
|
// If using output buffering pattern:
|
|
|
|
|
// ob_start(); include 'views/DashboardView.php'; $content = ob_get_clean();
|
|
|
|
|
// echo $content;
|
|
|
|
|
//
|
|
|
|
|
// Or simply include views inline after including this layout.
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<!-- =========================================================
|
|
|
|
|
SCRIPTS
|
|
|
|
|
All <script> tags MUST include the CSP nonce attribute.
|
|
|
|
|
========================================================= -->
|
|
|
|
|
|
|
|
|
|
<!-- Inject runtime config + CSRF token (nonce required for CSP) -->
|
|
|
|
|
<script nonce="<?php echo $nonce; ?>">
|
|
|
|
|
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
|
|
|
|
window.APP_TIMEZONE = '<?php echo htmlspecialchars($config['TIMEZONE'] ?? 'UTC'); ?>';
|
|
|
|
|
window.APP_TIMEZONE_ABBREV = '<?php echo htmlspecialchars($config['TIMEZONE_ABBREV'] ?? 'UTC'); ?>';
|
|
|
|
|
window.CURRENT_USER = {
|
|
|
|
|
id: <?php echo (int)($currentUser['user_id'] ?? 0); ?>,
|
|
|
|
|
username: <?php echo json_encode($currentUser['username'] ?? ''); ?>,
|
|
|
|
|
isAdmin: <?php echo $isAdmin ? 'true' : 'false'; ?>,
|
|
|
|
|
};
|
2026-04-18 13:53:29 -04:00
|
|
|
// App-specific config: set window.APP_CONFIG in your app's own <script> block,
|
|
|
|
|
// not here. This file is shared across all apps.
|
2026-03-14 21:08:57 -04:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- Unified design system JS -->
|
|
|
|
|
<script nonce="<?php echo $nonce; ?>" src="/web_template/base.js"></script>
|
|
|
|
|
|
|
|
|
|
<!-- App-specific JS (cache-busted) -->
|
|
|
|
|
<script nonce="<?php echo $nonce; ?>"
|
2026-04-18 13:53:29 -04:00
|
|
|
src="/assets/js/app.js?v=<?php echo $config['JS_VERSION'] ?? '1'; ?>">
|
2026-03-14 21:08:57 -04:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- Per-page inline JS goes here in the including view, e.g.: -->
|
|
|
|
|
<!--
|
|
|
|
|
<script nonce="<?php echo $nonce; ?>">
|
|
|
|
|
lt.sortTable.init('ticket-table');
|
|
|
|
|
lt.tableNav.init('ticket-table');
|
|
|
|
|
lt.keys.on('n', () => window.location.href = '/ticket/create');
|
|
|
|
|
lt.autoRefresh.start(() => fetch('/api/status').then(r=>r.json()).then(updateUI), 30000);
|
|
|
|
|
</script>
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|