fix: make layout templates generic — remove app-specific nav and config
Lint / JS (eslint) (push) Successful in 10s
Lint / JS (eslint) (push) Successful in 10s
php/layout.php: nav is now data-driven via $navLinks array (supports top-level links, dropdowns, adminOnly flag); removed tinker_tickets hardcoded nav items; moved APP_CONFIG note to comment python/base.html: nav driven by nav_links list from context; removed gandalf-specific routes (links_page, inspector, suppressions_page); removed APP_CONFIG.ticketWebUrl from shared script block; added nav_links format documentation in header comment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+30
-15
@@ -17,8 +17,14 @@
|
||||
* $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.)
|
||||
* $activeNav string Which nav key is active — must match a $navLinks entry
|
||||
* $config array From config/config.php
|
||||
* $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'],
|
||||
* ]]
|
||||
*/
|
||||
|
||||
// Defensive defaults
|
||||
@@ -27,6 +33,7 @@ $currentUser = $currentUser ?? [];
|
||||
$pageTitle = $pageTitle ?? 'Dashboard';
|
||||
$activeNav = $activeNav ?? '';
|
||||
$config = $config ?? [];
|
||||
$navLinks = $navLinks ?? [];
|
||||
$isAdmin = $currentUser['is_admin'] ?? false;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
@@ -40,7 +47,7 @@ $isAdmin = $currentUser['is_admin'] ?? false;
|
||||
<!-- 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="stylesheet" href="/assets/css/app.css?v=<?php echo $config['CSS_VERSION'] ?? '1'; ?>">
|
||||
|
||||
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
|
||||
</head>
|
||||
@@ -67,25 +74,31 @@ $isAdmin = $currentUser['is_admin'] ?? false;
|
||||
</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 foreach ($navLinks as $link): ?>
|
||||
<?php
|
||||
$skipAdminOnly = !empty($link['adminOnly']) && !$isAdmin;
|
||||
if ($skipAdminOnly) continue;
|
||||
?>
|
||||
|
||||
<?php if ($isAdmin): ?>
|
||||
<?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 str_starts_with($activeNav, 'admin') ? 'active' : ''; ?>">
|
||||
Admin ▾
|
||||
<a href="#" class="lt-nav-link <?php echo $parentActive ? 'active' : ''; ?>">
|
||||
<?php echo htmlspecialchars($link['label']); ?> ▾
|
||||
</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>
|
||||
<?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; ?>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -130,6 +143,8 @@ $isAdmin = $currentUser['is_admin'] ?? false;
|
||||
username: <?php echo json_encode($currentUser['username'] ?? ''); ?>,
|
||||
isAdmin: <?php echo $isAdmin ? 'true' : 'false'; ?>,
|
||||
};
|
||||
// App-specific config: set window.APP_CONFIG in your app's own <script> block,
|
||||
// not here. This file is shared across all apps.
|
||||
</script>
|
||||
|
||||
<!-- Unified design system JS -->
|
||||
@@ -137,7 +152,7 @@ $isAdmin = $currentUser['is_admin'] ?? false;
|
||||
|
||||
<!-- App-specific JS (cache-busted) -->
|
||||
<script nonce="<?php echo $nonce; ?>"
|
||||
src="/assets/js/app.js?v=<?php echo $config['JS_VERSION'] ?? '20260314'; ?>">
|
||||
src="/assets/js/app.js?v=<?php echo $config['JS_VERSION'] ?? '1'; ?>">
|
||||
</script>
|
||||
|
||||
<!-- Per-page inline JS goes here in the including view, e.g.: -->
|
||||
|
||||
+35
-21
@@ -17,7 +17,8 @@
|
||||
Required Flask setup (app.py):
|
||||
- Pass `nonce` into every render_template() call via a context processor
|
||||
- Pass `user` dict from _get_user() helper
|
||||
- Pass `config` dict with APP_NAME, etc.
|
||||
- Pass `config` dict with APP_NAME, APP_SUBTITLE, etc.
|
||||
- Pass `nav_links` list of dicts defining navigation
|
||||
|
||||
Context processor example:
|
||||
@app.context_processor
|
||||
@@ -25,6 +26,16 @@
|
||||
import base64, os
|
||||
nonce = base64.b64encode(os.urandom(16)).decode()
|
||||
return dict(nonce=nonce, user=_get_user(), config=_config())
|
||||
|
||||
nav_links format (pass from route or context processor):
|
||||
nav_links = [
|
||||
{'href': url_for('index'), 'key': 'dashboard', 'label': 'Dashboard'},
|
||||
{'href': url_for('settings'), 'key': 'settings', 'label': 'Settings'},
|
||||
# Admin-only dropdown:
|
||||
{'label': 'Admin', 'key': 'admin', 'admin_only': True, 'children': [
|
||||
{'href': url_for('admin_users'), 'label': 'Users'},
|
||||
]},
|
||||
]
|
||||
#}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -64,24 +75,28 @@
|
||||
</div>
|
||||
|
||||
<nav class="lt-nav" aria-label="Main navigation">
|
||||
{# Each page sets {% block active_nav %}pagename{% endblock %} #}
|
||||
{% set active = self.active_nav() | default('') %}
|
||||
<a href="{{ url_for('index') }}"
|
||||
class="lt-nav-link {% if active == 'dashboard' %}active{% endif %}">
|
||||
Dashboard
|
||||
{% for link in nav_links | default([]) %}
|
||||
{% if not link.get('admin_only') or 'admin' in user.groups %}
|
||||
{% if link.get('children') %}
|
||||
<div class="lt-nav-dropdown">
|
||||
<a href="#" class="lt-nav-link {% if active.startswith(link.key) %}active{% endif %}">
|
||||
{{ link.label }} ▾
|
||||
</a>
|
||||
<a href="{{ url_for('links_page') }}"
|
||||
class="lt-nav-link {% if active == 'links' %}active{% endif %}">
|
||||
Link Debug
|
||||
</a>
|
||||
<a href="{{ url_for('inspector') }}"
|
||||
class="lt-nav-link {% if active == 'inspector' %}active{% endif %}">
|
||||
Inspector
|
||||
</a>
|
||||
<a href="{{ url_for('suppressions_page') }}"
|
||||
class="lt-nav-link {% if active == 'suppressions' %}active{% endif %}">
|
||||
Suppressions
|
||||
<ul class="lt-nav-dropdown-menu">
|
||||
{% for child in link.children %}
|
||||
<li><a href="{{ child.href }}">{{ child.label }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{{ link.href }}"
|
||||
class="lt-nav-link {% if active == link.key %}active{% endif %}">
|
||||
{{ link.label }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -107,17 +122,16 @@
|
||||
All <script> tags MUST carry the nonce attribute for CSP.
|
||||
========================================================= -->
|
||||
|
||||
<!-- Runtime config (no CSRF needed for Gandalf — SameSite=Strict) -->
|
||||
<!-- Runtime config injected by the server -->
|
||||
<script nonce="{{ nonce }}">
|
||||
window.APP_CONFIG = {
|
||||
ticketWebUrl: {{ config.get('ticket_api', {}).get('web_url', 'https://t.lotusguild.org/ticket/') | tojson }},
|
||||
};
|
||||
window.CURRENT_USER = {
|
||||
username: {{ user.username | tojson }},
|
||||
name: {{ (user.name or user.username) | tojson }},
|
||||
groups: {{ user.groups | tojson }},
|
||||
isAdmin: {{ ('admin' in user.groups) | lower }},
|
||||
};
|
||||
// App-specific config: set window.APP_CONFIG in your app's own template block,
|
||||
// not here. This file is shared across all apps.
|
||||
</script>
|
||||
|
||||
<!-- Unified design system JS -->
|
||||
@@ -136,6 +150,6 @@
|
||||
|
||||
{% block active_nav %}dashboard{% endblock %}
|
||||
|
||||
Values: dashboard | links | inspector | suppressions
|
||||
Value must match a 'key' in your nav_links list.
|
||||
--------------------------------------------------------------- #}
|
||||
{% block active_nav %}{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user