- migrations/2026-05-09e_fetch_strategy.py NEU: ALTER TABLE sources ADD COLUMN fetch_strategy. Pre-flagging fuer FT/WSJ/NZZ etc. (paywall) und Rheinische Post/Verfassungsschutz (googlebot). - shared/services/source_health.py: gesynct vom Monitor (Phase-18-Code mit Retry-Logik + Strategien default/googlebot/paywall/skip). - routers/sources.py: GlobalSourceCreate/Update um fetch_strategy (Pattern-Validation), SOURCE_UPDATE_COLUMNS + INSERT erweitert. - dashboard.html: Edit-Modal hat jetzt Dropdown sourceFetchStrategy. - sources.js: laedt + sendet fetch_strategy mit. Cache-Buster 20260509c -> 20260509d.
161 Zeilen
5.8 KiB
HTML
161 Zeilen
5.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<title>AegisSight Monitor-Verwaltung - Anmeldung</title>
|
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
|
<link rel="apple-touch-icon" href="/static/favicon.svg">
|
|
<link rel="stylesheet" href="/static/css/style.css?v=20260509d">
|
|
</head>
|
|
<body class="login-page">
|
|
<div class="login-container">
|
|
<div class="login-card">
|
|
<div class="login-header">
|
|
<h1>AegisSight</h1>
|
|
<p class="subtitle">Monitor-Verwaltung</p>
|
|
</div>
|
|
|
|
<!-- Schritt 1: Email-Eingabe -->
|
|
<form id="magicForm" class="login-form">
|
|
<div class="form-group">
|
|
<label for="email">E-Mail-Adresse</label>
|
|
<input type="email" id="email" name="email" required autocomplete="email" autofocus
|
|
placeholder="info@aegis-sight.de">
|
|
</div>
|
|
<div id="loginError" class="error-msg" style="display:none"></div>
|
|
<button type="submit" class="btn btn-primary btn-full" id="magicBtn">Login-Link anfordern</button>
|
|
<p class="form-hint" style="margin-top:14px;text-align:center;font-size:12px;color:#94a3b8;">
|
|
Wir senden dir einen einmaligen Login-Link per E-Mail.
|
|
</p>
|
|
</form>
|
|
|
|
<!-- Schritt 2: Bestätigung nach Versand -->
|
|
<div id="sentInfo" style="display:none;text-align:center;">
|
|
<div style="font-size:42px;margin:8px 0 16px 0;">✉</div>
|
|
<h2 style="font-size:17px;margin:0 0 8px 0;">E-Mail unterwegs</h2>
|
|
<p style="margin:0 0 18px 0;font-size:14px;color:#94a3b8;line-height:1.5;">
|
|
Wenn die Adresse berechtigt ist, hast du gleich einen Login-Link in deinem Posteingang.
|
|
Der Link ist 10 Minuten gültig.
|
|
</p>
|
|
<button type="button" class="btn btn-secondary btn-full" onclick="resetForm()">Andere E-Mail-Adresse</button>
|
|
</div>
|
|
|
|
<!-- Schritt 3: Verify (während Token-Prüfung) -->
|
|
<div id="verifying" style="display:none;text-align:center;">
|
|
<div class="spinner" style="margin:8px auto 16px;"></div>
|
|
<h2 style="font-size:17px;margin:0 0 8px 0;">Anmeldung wird geprüft...</h2>
|
|
<p style="margin:0;font-size:14px;color:#94a3b8;">Einen Moment bitte.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.spinner {
|
|
width: 36px; height: 36px;
|
|
border: 3px solid rgba(240,180,41,0.2);
|
|
border-top-color: #f0b429;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
.form-hint { font-size: 12px; color: #94a3b8; margin-top: 8px; }
|
|
</style>
|
|
|
|
<script>
|
|
const form = document.getElementById('magicForm');
|
|
const sentInfo = document.getElementById('sentInfo');
|
|
const verifying = document.getElementById('verifying');
|
|
const errorEl = document.getElementById('loginError');
|
|
const btn = document.getElementById('magicBtn');
|
|
|
|
function resetForm() {
|
|
sentInfo.style.display = 'none';
|
|
verifying.style.display = 'none';
|
|
form.style.display = '';
|
|
errorEl.style.display = 'none';
|
|
document.getElementById('email').value = '';
|
|
document.getElementById('email').focus();
|
|
}
|
|
|
|
function showError(msg) {
|
|
form.style.display = '';
|
|
sentInfo.style.display = 'none';
|
|
verifying.style.display = 'none';
|
|
errorEl.textContent = msg;
|
|
errorEl.style.display = 'block';
|
|
}
|
|
|
|
// --- Magic-Link anfordern ---
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
errorEl.style.display = 'none';
|
|
btn.disabled = true;
|
|
btn.textContent = 'Sende...';
|
|
|
|
try {
|
|
const res = await fetch('/api/auth/magic-link', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: document.getElementById('email').value }),
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
throw new Error(data.detail || `Fehler ${res.status}`);
|
|
}
|
|
// Erfolg (oder generisch): Bestätigungsanzeige
|
|
form.style.display = 'none';
|
|
sentInfo.style.display = '';
|
|
} catch (err) {
|
|
showError(err.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Login-Link anfordern';
|
|
}
|
|
});
|
|
|
|
// --- Token aus URL verifizieren (Schritt 3) ---
|
|
async function verifyTokenFromUrl() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const token = params.get('token');
|
|
if (!token) return;
|
|
|
|
form.style.display = 'none';
|
|
verifying.style.display = '';
|
|
|
|
try {
|
|
const res = await fetch('/api/auth/verify', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ token }),
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
throw new Error(data.detail || 'Login-Link ungültig');
|
|
}
|
|
const data = await res.json();
|
|
localStorage.setItem('token', data.access_token);
|
|
localStorage.setItem('username', data.username);
|
|
if (data.email) localStorage.setItem('email', data.email);
|
|
|
|
// Token aus URL entfernen, damit er nicht im Verlauf liegt
|
|
window.history.replaceState({}, '', '/');
|
|
window.location.href = '/dashboard';
|
|
} catch (err) {
|
|
showError(err.message);
|
|
// Token aus URL entfernen bei Fehler
|
|
window.history.replaceState({}, '', '/');
|
|
}
|
|
}
|
|
|
|
// Schon eingeloggt? -> direkt aufs Dashboard
|
|
if (localStorage.getItem('token') && !window.location.search.includes('token=')) {
|
|
window.location.href = '/dashboard';
|
|
} else {
|
|
verifyTokenFromUrl();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|