Unified CSS, JavaScript utilities, HTML template, and framework skeleton files for Tinker Tickets (PHP), PULSE (Node.js), and GANDALF (Flask). Includes aesthetic_diff.md documenting every divergence between the three apps with prioritised recommendations for convergence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
155 lines
6.1 KiB
PHP
155 lines
6.1 KiB
PHP
<?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
|
|
* $activeNav string Which nav link is active ('dashboard','tickets',etc.)
|
|
* $config array From config/config.php
|
|
*/
|
|
|
|
// Defensive defaults
|
|
$nonce = $nonce ?? '';
|
|
$currentUser = $currentUser ?? [];
|
|
$pageTitle = $pageTitle ?? 'Dashboard';
|
|
$activeNav = $activeNav ?? '';
|
|
$config = $config ?? [];
|
|
$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) -->
|
|
<link rel="stylesheet" href="/assets/css/app.css?v=<?php echo $config['CSS_VERSION'] ?? '20260314'; ?>">
|
|
|
|
<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">
|
|
<a href="/" class="lt-nav-link <?php echo $activeNav === 'dashboard' ? 'active' : ''; ?>">Dashboard</a>
|
|
<a href="/tickets" class="lt-nav-link <?php echo $activeNav === 'tickets' ? 'active' : ''; ?>">Tickets</a>
|
|
|
|
<?php if ($isAdmin): ?>
|
|
<div class="lt-nav-dropdown">
|
|
<a href="#" class="lt-nav-link <?php echo str_starts_with($activeNav, 'admin') ? 'active' : ''; ?>">
|
|
Admin ▾
|
|
</a>
|
|
<ul class="lt-nav-dropdown-menu">
|
|
<li><a href="/admin/templates">Templates</a></li>
|
|
<li><a href="/admin/workflow">Workflow</a></li>
|
|
<li><a href="/admin/recurring-tickets">Recurring</a></li>
|
|
<li><a href="/admin/custom-fields">Custom Fields</a></li>
|
|
<li><a href="/admin/user-activity">User Activity</a></li>
|
|
<li><a href="/admin/audit-log">Audit Log</a></li>
|
|
<li><a href="/admin/api-keys">API Keys</a></li>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
</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'; ?>,
|
|
};
|
|
</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; ?>"
|
|
src="/assets/js/app.js?v=<?php echo $config['JS_VERSION'] ?? '20260314'; ?>">
|
|
</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>
|