admin/system: fetch()-based tab switching, no full-page reload

Add system-fragment.php — a thin authenticated endpoint that returns only
the tab-panel HTML (toolbar + meta + log/nginx-config output) for a given
?tab=&n= combination. No page shell, no status section, no DB queries.

system.php changes:
- Tab <a> elements gain data-tab= attributes used by JS to identify the
  target without parsing hrefs.
- Tab panel content wrapped in <div id=sys-tab-panel data-tab= data-n=>
  which JS uses as both the swap target and its own state store.
- JS rewritten: tab clicks and lines-select changes call loadPanel()
  which fetch()es system-fragment.php, swaps innerHTML, updates active
  tab ARIA attributes, and pushes state via history.pushState.
- Browser back/forward handled via popstate listener.
- bindPanelControls() re-wires the lines-select and copy-to-clipboard
  button after every innerHTML swap (event delegation not feasible here
  because log-output is replaced wholesale).
- fetch() failure falls back to window.location.href (full page load).
- Tabs without JS still work: <a href> links go to system.php?tab=…
  as before.

system-fragment.php:
- Requires AdminAuth::isAuthenticated(); returns 403 on failure.
- Validates tab and n params against the same whitelist as system.php.
- All helper functions namespaced with frag_ prefix to avoid redeclaration
  if PHP ever includes both files in the same process.
- Renders identical HTML to the corresponding section in system.php.

system.css:
- #sys-tab-panel gets min-height:8rem and position:relative to prevent
  layout jump during fetch.
- .sys-panel-loading: opacity 0.4 + pointer-events:none + subtle
  diagonal-stripe ::after overlay with shimmer animation.
This commit is contained in:
Pontoporeia
2026-04-02 18:39:55 +02:00
parent c86781b9be
commit b981223ff4
4 changed files with 368 additions and 36 deletions

View File

@@ -170,6 +170,36 @@
margin-top: .25rem;
}
/* ── Tab panel loading state ──────────────────────────────────────────────── */
#sys-tab-panel {
min-height: 8rem; /* prevent layout jump while fetching */
position: relative;
}
#sys-tab-panel.sys-panel-loading {
opacity: 0.4;
pointer-events: none;
transition: opacity 0.1s;
}
#sys-tab-panel.sys-panel-loading::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
-45deg,
transparent,
transparent 6px,
rgba(255,255,255,.03) 6px,
rgba(255,255,255,.03) 12px
);
border-radius: 4px;
animation: sys-panel-shimmer 1s linear infinite;
background-size: 200% 200%;
}
@keyframes sys-panel-shimmer {
0% { background-position: 0 0; }
100% { background-position: 100% 100%; }
}
/* ── Log viewer ────────────────────────────────────────────────────────── */
.log-toolbar {
display: flex;