Grotti 2.2.0: Auth, API-Tokens, MCP und Doku-Trennung.
This commit is contained in:
1 parent
b20f8a7779
commit
5a3716b26a
65 files changed
+2212
-198
No files matched your search
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf" content="{{ csrf }}">
|
||||
<title>Anmelden · Grotti</title>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='brand/favicon-32.png') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='grotti.css') }}?v=40">
|
||||
</head>
|
||||
<body class="auth-body">
|
||||
<main class="auth-card">
|
||||
<h1 class="auth-brand"><img src="{{ url_for('static', filename='brand/logo.png') }}" alt="" width="40" height="40" decoding="async"> Grotti</h1>
|
||||
<figure class="auth-quote" id="auth-quote" hidden>
|
||||
<blockquote id="auth-quote-text"></blockquote>
|
||||
<figcaption id="auth-quote-by"></figcaption>
|
||||
</figure>
|
||||
<form id="login-form" class="stack">
|
||||
<div class="field">
|
||||
<label for="username">Benutzer</label>
|
||||
<input id="username" name="username" autocomplete="username" required autofocus>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">Passwort</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<button type="submit">Anmelden</button>
|
||||
<p class="msg" id="note" hidden></p>
|
||||
</form>
|
||||
</main>
|
||||
<script>
|
||||
(function () {
|
||||
const quotes = [
|
||||
{ t: "Wissenschaftler entdecken die Welt, die ist; Ingenieure schaffen die Welt, die noch nie war.", a: "Theodore von Kármán" },
|
||||
{ t: "Perfektion ist nicht dann erreicht, wenn man nichts mehr hinzufügen, sondern wenn man nichts mehr weglassen kann.", a: "Antoine de Saint-Exupéry" },
|
||||
{ t: "Wenn man es nicht messen kann, kann man es nicht verbessern.", a: "Lord Kelvin" },
|
||||
{ t: "Einfachheit ist die höchste Form der Raffinesse.", a: "Leonardo da Vinci" },
|
||||
{ t: "Es gibt nichts Praktischeres als eine gute Theorie.", a: "Kurt Lewin" },
|
||||
{ t: "Gott steckt im Detail.", a: "Ludwig Mies van der Rohe" },
|
||||
{ t: "Erst das Problem lösen. Dann den Code schreiben.", a: "John Johnson" },
|
||||
{ t: "Qualität heißt, es von Anfang an richtig zu machen.", a: "W. Edwards Deming" },
|
||||
{ t: "Wer misst, misst Mist — wer nicht misst, steuert blind.", a: "Werkstattweisheit" },
|
||||
{ t: "Simplicity is a great virtue, but it requires hard work to achieve it.", a: "Edsger W. Dijkstra" },
|
||||
];
|
||||
const pick = quotes[Math.floor(Date.now() / 86400000) % quotes.length];
|
||||
document.getElementById("auth-quote-text").textContent = pick.t;
|
||||
document.getElementById("auth-quote-by").textContent = "— " + pick.a;
|
||||
document.getElementById("auth-quote").hidden = false;
|
||||
})();
|
||||
document.getElementById("login-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const note = document.getElementById("note");
|
||||
note.hidden = true;
|
||||
try {
|
||||
const response = await fetch("/api/v1/auth/login", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById("username").value,
|
||||
password: document.getElementById("password").value,
|
||||
}),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(payload.error || response.statusText);
|
||||
const next = new URLSearchParams(location.search).get("next") || "/";
|
||||
location.href = next.startsWith("/") ? next : "/";
|
||||
} catch (error) {
|
||||
note.hidden = false;
|
||||
note.textContent = error.message || String(error);
|
||||
note.className = "msg";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user