Files
stackpulse/frontend/index.html
T

1086 lines
24 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="prev" href="../backend/">
<link rel="next" href="../operations/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.0">
<title>Frontend - Stackpulse Dokumentation</title>
<link rel="stylesheet" href="../assets/stylesheets/main.618322db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#frontend" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Stackpulse Dokumentation" class="md-header__button md-logo" aria-label="Stackpulse Dokumentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Stackpulse Dokumentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Frontend
</span>
</div>
</div>
</div>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Stackpulse Dokumentation" class="md-nav__button md-logo" aria-label="Stackpulse Dokumentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Stackpulse Dokumentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Start
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Benutzerhandbuch
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Benutzerhandbuch
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../user-guide/" class="md-nav__link">
<span class="md-ellipsis">
Überblick
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/setup/" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/stacks/" class="md-nav__link">
<span class="md-ellipsis">
Stacks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/logs/" class="md-nav__link">
<span class="md-ellipsis">
Logs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/maintenance/" class="md-nav__link">
<span class="md-ellipsis">
Wartung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/users/" class="md-nav__link">
<span class="md-ellipsis">
Benutzer
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/groups/" class="md-nav__link">
<span class="md-ellipsis">
Benutzergruppen & Rechte
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked>
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<span class="md-ellipsis">
Technik
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
Technik
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../architecture/" class="md-nav__link">
<span class="md-ellipsis">
Architektur
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../backend/" class="md-nav__link">
<span class="md-ellipsis">
Backend & API
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Frontend
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Frontend
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#build-skripte" class="md-nav__link">
<span class="md-ellipsis">
Build &amp; Skripte
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#projektstruktur" class="md-nav__link">
<span class="md-ellipsis">
Projektstruktur
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#routing-navigation" class="md-nav__link">
<span class="md-ellipsis">
Routing &amp; Navigation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#state-management-contexts" class="md-nav__link">
<span class="md-ellipsis">
State-Management &amp; Contexts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wichtige-seiten" class="md-nav__link">
<span class="md-ellipsis">
Wichtige Seiten
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#datenzugriff" class="md-nav__link">
<span class="md-ellipsis">
Datenzugriff
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#styling-komponenten" class="md-nav__link">
<span class="md-ellipsis">
Styling &amp; Komponenten
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#erweiterungshinweise" class="md-nav__link">
<span class="md-ellipsis">
Erweiterungshinweise
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../operations/" class="md-nav__link">
<span class="md-ellipsis">
Operations & Wartung
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#build-skripte" class="md-nav__link">
<span class="md-ellipsis">
Build &amp; Skripte
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#projektstruktur" class="md-nav__link">
<span class="md-ellipsis">
Projektstruktur
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#routing-navigation" class="md-nav__link">
<span class="md-ellipsis">
Routing &amp; Navigation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#state-management-contexts" class="md-nav__link">
<span class="md-ellipsis">
State-Management &amp; Contexts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wichtige-seiten" class="md-nav__link">
<span class="md-ellipsis">
Wichtige Seiten
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#datenzugriff" class="md-nav__link">
<span class="md-ellipsis">
Datenzugriff
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#styling-komponenten" class="md-nav__link">
<span class="md-ellipsis">
Styling &amp; Komponenten
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#erweiterungshinweise" class="md-nav__link">
<span class="md-ellipsis">
Erweiterungshinweise
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="frontend">Frontend</h1>
<p>Das Frontend basiert auf React 18, Vite und dem Material Tailwind Dashboard. Es nutzt Context Provider für Authentifizierung, Seitenstatus, Toaster und Wartungsinformationen.</p>
<h2 id="build-skripte">Build &amp; Skripte</h2>
<table>
<thead>
<tr>
<th>Befehl</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>npm run dev</code></td>
<td>Startet den Vite Dev-Server (Port 5173, HMR aktiviert).</td>
</tr>
<tr>
<td><code>npm run build</code></td>
<td>Erstellt ein Production-Bundle (<code>frontend/dist</code>). Das Dockerfile kopiert dieses Paket ins Backend (<code>backend/public</code>).</td>
</tr>
<tr>
<td><code>npm run preview</code></td>
<td>Vorschau auf das gebaute Bundle.</td>
</tr>
</tbody>
</table>
<h2 id="projektstruktur">Projektstruktur</h2>
<pre><code>frontend/src
├── App.jsx # Entry-Point, registriert Layouts &amp; Provider
├── components/ # AuthProvider, PermissionGate, Toast/Maintenance/PageProvider
├── layouts/ # Dashboard-Layout (Sidebar, Navbar) &amp; Auth-Layout
├── pages/
│ ├── auth/ # SignIn, SignUp, ForgotPassword, SignOut
│ ├── dashboard/ # Stacks, Logs, Maintenance, Users, Usergroups, Detailseiten
│ └── setup/ # Setup-Wizard &amp; Helper Screens
├── routes.jsx # zentrale Navigationsdefinition inkl. Permissions
├── widgets/, data/, configs/, assets/
└── tailwind.css # Tailwind Layer &amp; Custom Styles
</code></pre>
<h2 id="routing-navigation">Routing &amp; Navigation</h2>
<ul>
<li><code>routes.jsx</code> beschreibt sowohl Dashboard- als auch Auth-Routen. Jede Dashboard-Seite besitzt optional ein <code>permission</code>-Objekt, das vom <code>PermissionGate</code> ausgewertet wird.</li>
<li>Der Layout-Switch erfolgt über <code>layouts/index.jsx</code>, das wiederum Sidebar/Topbar-Komponenten aus dem Material Tailwind Dashboard nutzt.</li>
<li>Auth-Routen (<code>/sign-in</code>, <code>/sign-up</code>, <code>/forgot-password</code>, <code>/logout</code>) werden im Auth-Layout ohne Sidebar dargestellt.</li>
</ul>
<h2 id="state-management-contexts">State-Management &amp; Contexts</h2>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Aufgabe</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AuthProvider</code></td>
<td>Speichert User, Permissions, Session-Status. Stellt <code>refreshSession</code>, <code>logout</code>, <code>hasPermission</code> bereit.</td>
</tr>
<tr>
<td><code>PageProvider</code></td>
<td>Zentrale UI-Zustände (z.B. Ladeanzeigen, modale Dialoge) für Dashboard-Komponenten.</td>
</tr>
<tr>
<td><code>ToastProvider</code></td>
<td>Globale Notifications (z.B. Redeploy-Erfolg/Fehler).</td>
</tr>
<tr>
<td><code>MaintenanceProvider</code></td>
<td>Cached Maintenance-Status, erkennt aktive Maintenance Modes und zeigt Hinweise.</td>
</tr>
</tbody>
</table>
<h2 id="wichtige-seiten">Wichtige Seiten</h2>
<ul>
<li><strong>Stacks (<code>pages/dashboard/stacks.jsx</code>)</strong>: Hauptübersicht mit Tabellen, Suche, Filtern, Bulk- und Einzelredeploy. Bindet <code>socket.io-client</code> für Live-Updates (<code>redeployStatus</code>).</li>
<li><strong>Logs (<code>pages/dashboard/logs.jsx</code>)</strong>: Tabellenansicht mit Statusfarben, Filterchips, Export-/Delete-Aktionen. Nutzt axios-Queries gegen <code>/api/logs</code>.</li>
<li><strong>Maintenance (<code>pages/dashboard/maintenance.jsx</code>)</strong>: Formulare zur Verwaltung von Update-Skript, SSH-Konfiguration, Maintenance-Mode-Toggle und Portainer-Statusprüfung.</li>
<li><strong>Users / Usergroups</strong>: CRUD-Oberfläche für Benutzer, Gruppen, Berechtigungszuweisung inkl. Sicherheitsphrasen-Handling.</li>
<li><strong>Setup (<code>pages/setup</code>)</strong>: Schrittweiser Wizard (Server -&gt; API-Key -&gt; Superuser). Wird solange angezeigt, bis <code>/api/setup/status</code> vollständig ist.</li>
</ul>
<h2 id="datenzugriff">Datenzugriff</h2>
<ul>
<li>HTTP-Anfragen erfolgen via <code>axios</code> (global konfiguriert in <code>frontend/src/components/AuthProvider</code> bzw. direkt in den Seiten). Cookies werden automatisch vom Browser beigesteuert (<code>credentials: 'include'</code>).</li>
<li>Socket.IO wird nur im Stack-Dashboard verwendet; Verbindungsaufbau erfolgt in <code>stacks.jsx</code> (<code>io('/', { path: '/socket.io' })</code>).</li>
<li>Fehlerbehandlung: Jede Seite zeigt Toasts und/oder Inline-Warnungen. Auth-Fehler leiten zur Loginseite um.</li>
</ul>
<h2 id="styling-komponenten">Styling &amp; Komponenten</h2>
<ul>
<li>Tailwind Utility-Klassen liefert Material Tailwind bereits vordefiniert (Theme in <code>configs</code>).</li>
<li>Custom-Komponenten wie Tabellen, Filterchips oder Formulare sind überwiegend in <code>widgets/</code> ausgelagert.</li>
<li>Icons stammen aus <code>@heroicons/react</code>. Farben/Typografie folgen Creative Tim Vorgaben, können aber zentral in <code>frontend/src/configs</code> angepasst werden.</li>
</ul>
<h2 id="erweiterungshinweise">Erweiterungshinweise</h2>
<ol>
<li><strong>Neue Seiten</strong>: Route in <code>routes.jsx</code> ergänzen, optional <code>PermissionGate</code> konfigurieren und Seite unter <code>pages/dashboard</code> oder <code>pages/auth</code> anlegen.</li>
<li><strong>API-Anbindungen</strong>: Verwende <code>axios</code> und achte auf <code>try/catch</code>, damit 401/403 sauber abgefangen werden. Bei kritischen Anforderungen <code>refreshSession</code> aufrufen.</li>
<li><strong>State teilen</strong>: Nutze bestehende Provider bzw. erzeuge einen eigenen Context in <code>components/</code>.</li>
<li><strong>Deployment</strong>: <code>npm run build</code> liefert ein Self-Contained Bundle. Das Backend dient anschließend als Static Host (<code>express.static</code>).</li>
</ol>
<p>Damit bietet das Frontend eine anpassbare, klar strukturierte Oberfläche, die eng mit den Backend-Permissions verzahnt ist.</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.7a47a382.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.e71a0d61.min.js"></script>
</body>
</html>