Fix layout regressions, nav drawer structure, and security issues

- base.css: add width:100%+min-width:0 to .lt-main so flex column body
  doesn't shrink content due to margin:0 auto from .lt-container
- layout_header.php: restructure mobile nav drawer to match web_template
  exactly (nav-drawer-links nav, direct <a> links, section div, no ul/li
  wrapper, overlay after drawer); fix lt-nav-overlay id mismatch with
  base.js; rename lt-header-username -> lt-header-user (matches CSS);
  add JSON_HEX_TAG to all inline json_encode calls (closes </script> XSS)
- base.css: add lt-kv-row/label/value aliases (display:contents pattern
  used in web_template v1.2 kv-grid); add lt-badge-sm variant
- Admin views: add missing .catch() on editField/editRecurring/loadUsers;
  add JSON_HEX_TAG to json_encode in TemplatesView/WorkflowDesignerView
- TicketView: add JSON_HEX_TAG to all ticket-data json_encode calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-28 12:43:24 -04:00
parent 51f6991f9d
commit d7775e62ec
7 changed files with 50 additions and 47 deletions
+25 -36
View File
@@ -41,15 +41,15 @@ $_lt_navActive = $activeNav ?? 'dashboard';
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>" src="/assets/js/utils.js"></script>
<!-- Inline JS globals (CSRF, timezone, user) available immediately -->
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
window.CSRF_TOKEN = <?= json_encode(CsrfMiddleware::getToken(), JSON_UNESCAPED_UNICODE) ?>;
window.APP_TIMEZONE = <?= json_encode($GLOBALS['config']['TIMEZONE'] ?? 'UTC', JSON_UNESCAPED_UNICODE) ?>;
window.APP_TIMEZONE_ABBREV = <?= json_encode($GLOBALS['config']['TIMEZONE_ABBREV'] ?? 'UTC', JSON_UNESCAPED_UNICODE) ?>;
window.CSRF_TOKEN = <?= json_encode(CsrfMiddleware::getToken(), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
window.APP_TIMEZONE = <?= json_encode($GLOBALS['config']['TIMEZONE'] ?? 'UTC', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
window.APP_TIMEZONE_ABBREV = <?= json_encode($GLOBALS['config']['TIMEZONE_ABBREV'] ?? 'UTC', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
window.APP_TIMEZONE_OFFSET = <?= (int)($GLOBALS['config']['TIMEZONE_OFFSET'] ?? 0) ?>;
window.CURRENT_USER = <?= json_encode([
'id' => (int)($GLOBALS['currentUser']['user_id'] ?? 0),
'username'=> $GLOBALS['currentUser']['username'] ?? '',
'isAdmin' => !empty($GLOBALS['currentUser']['is_admin']),
], JSON_UNESCAPED_UNICODE) ?>;
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
</script>
</head>
<body>
@@ -62,39 +62,30 @@ $_lt_navActive = $activeNav ?? 'dashboard';
<pre id="lt-boot-text" class="lt-boot-text"></pre>
</div>
<!-- MOBILE NAV DRAWER -->
<div class="lt-nav-drawer" id="lt-nav-drawer" role="dialog" aria-modal="true" aria-label="Mobile navigation">
<div class="lt-nav-drawer-backdrop" id="lt-nav-drawer-backdrop" data-action="close-nav-drawer" aria-hidden="true"></div>
<nav class="lt-nav-drawer-panel" aria-label="Mobile main navigation">
<div class="lt-nav-drawer-header">
<span class="lt-nav-drawer-title">TINKER TICKETS</span>
<button type="button"
class="lt-nav-drawer-close"
id="lt-nav-drawer-close"
data-action="close-nav-drawer"
aria-label="Close navigation">&#x2715;</button>
</div>
<ul class="lt-nav-drawer-list" role="list">
<li>
<a href="/"
class="lt-nav-drawer-link<?= $_lt_navActive === 'dashboard' ? ' active' : '' ?>"
<?= $_lt_navActive === 'dashboard' ? 'aria-current="page"' : '' ?>>
Dashboard
</a>
</li>
<!-- MOBILE NAV DRAWER — matches web_template structure exactly -->
<div id="lt-nav-drawer" class="lt-nav-drawer" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Navigation menu">
<div class="lt-nav-drawer-header">
<span class="lt-brand-title">TINKER TICKETS</span>
<button type="button" class="lt-nav-drawer-close" id="lt-nav-drawer-close" aria-label="Close navigation">&#x2715;</button>
</div>
<nav class="lt-nav-drawer-links" aria-label="Mobile navigation">
<a href="/"
class="lt-nav-drawer-link<?= $_lt_navActive === 'dashboard' ? ' active' : '' ?>"
<?= $_lt_navActive === 'dashboard' ? 'aria-current="page"' : '' ?>>Dashboard</a>
<?php if ($_lt_isAdmin): ?>
<li class="lt-nav-drawer-section-label" aria-hidden="true">Admin</li>
<li><a href="/admin/templates" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-templates' ? ' active' : '' ?>">Templates</a></li>
<li><a href="/admin/workflow" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-workflow' ? ' active' : '' ?>">Workflow</a></li>
<li><a href="/admin/recurring-tickets" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-recurring' ? ' active' : '' ?>">Recurring</a></li>
<li><a href="/admin/custom-fields" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-custom-fields' ? ' active' : '' ?>">Custom Fields</a></li>
<li><a href="/admin/user-activity" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-user-activity' ? ' active' : '' ?>">User Activity</a></li>
<li><a href="/admin/audit-log" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-audit-log' ? ' active' : '' ?>">Audit Log</a></li>
<li><a href="/admin/api-keys" class="lt-nav-drawer-link<?= $_lt_navActive === 'admin-api-keys' ? ' active' : '' ?>">API Keys</a></li>
<div class="lt-nav-drawer-section">Admin</div>
<a href="/admin/templates" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-templates' ? ' active' : '' ?>">Templates</a>
<a href="/admin/workflow" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-workflow' ? ' active' : '' ?>">Workflow</a>
<a href="/admin/recurring-tickets" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-recurring' ? ' active' : '' ?>">Recurring</a>
<a href="/admin/custom-fields" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-custom-fields' ? ' active' : '' ?>">Custom Fields</a>
<a href="/admin/user-activity" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-user-activity' ? ' active' : '' ?>">User Activity</a>
<a href="/admin/audit-log" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-audit-log' ? ' active' : '' ?>">Audit Log</a>
<a href="/admin/api-keys" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-api-keys' ? ' active' : '' ?>">API Keys</a>
<?php endif; ?>
</ul>
</nav>
</div><!-- /.lt-nav-drawer -->
<!-- Overlay: outside drawer, full-screen; JS toggles .open class -->
<div id="lt-nav-overlay" class="lt-nav-drawer-overlay"></div>
<!-- PRIMARY HEADER -->
<header class="lt-header" role="banner">
@@ -160,9 +151,7 @@ $_lt_navActive = $activeNav ?? 'dashboard';
<div class="lt-header-right">
<?php if (!empty($_lt_user)): ?>
<span class="lt-header-username">
<?= htmlspecialchars($_lt_user['display_name'] ?? $_lt_user['username'] ?? '', ENT_QUOTES, 'UTF-8') ?>
</span>
<span class="lt-header-user"><?= htmlspecialchars($_lt_user['display_name'] ?? $_lt_user['username'] ?? '', ENT_QUOTES, 'UTF-8') ?></span>
<?php if ($_lt_isAdmin): ?>
<span class="lt-badge lt-badge-admin" aria-label="Administrator">ADMIN</span>
<?php endif; ?>