Zeige Fortschritt bei der WebP-Migration
Teilt die Bestandsmigration in einen sicheren Request pro Bild auf, zeigt den tatsächlichen Fortschritt im Adminbereich und erlaubt die Wiederaufnahme nach Gateway- oder Netzwerkabbrüchen.
This commit is contained in:
@@ -6,6 +6,14 @@ require_once __DIR__ . '/functions.php';
|
||||
|
||||
$loggedIn = isset($_SESSION['admin']) && $_SESSION['admin'] === true;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['migration_step']) && !$loggedIn) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'Die Admin-Sitzung ist abgelaufen. Bitte Seite neu laden und anmelden.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Login
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'login') {
|
||||
if (($_POST['password'] ?? '') === ADMIN_PASSWORD_PLAIN) {
|
||||
@@ -30,28 +38,36 @@ if ($loggedIn) {
|
||||
$_SESSION['migration_csrf'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
$migrationCsrf = (string)$_SESSION['migration_csrf'];
|
||||
$migrationNotice = $_SESSION['migration_notice'] ?? null;
|
||||
$migrationError = null;
|
||||
unset($_SESSION['migration_notice']);
|
||||
|
||||
// BESTANDS-UPLOADS NACH WEBP MIGRIEREN
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['migrate_uploads'])) {
|
||||
// Je Request genau ein Bestandsbild migrieren, damit das Frontend echten
|
||||
// Fortschritt anzeigen kann und kein langer PHP-Request am Gateway scheitert.
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['migration_step'])) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
$submittedToken = (string)($_POST['migration_csrf'] ?? '');
|
||||
if (!hash_equals($migrationCsrf, $submittedToken)) {
|
||||
$migrationError = 'Die Migrationsanfrage ist abgelaufen. Bitte Seite neu laden und erneut versuchen.';
|
||||
} else {
|
||||
try {
|
||||
$_SESSION['migration_notice'] = migrate_uploads_to_webp();
|
||||
header('Location: admin.php?migration=done');
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
$migrationError = 'Migration fehlgeschlagen: ' . $e->getMessage();
|
||||
}
|
||||
http_response_code(403);
|
||||
echo json_encode(['ok' => false, 'error' => 'Die Migrationsanfrage ist abgelaufen. Bitte Seite neu laden.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
session_write_close();
|
||||
try {
|
||||
$result = migrate_next_upload_to_webp();
|
||||
echo json_encode(['ok' => true, 'result' => $result], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(422);
|
||||
echo json_encode(
|
||||
['ok' => false, 'error' => 'Migration fehlgeschlagen: ' . $e->getMessage()],
|
||||
JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// CREATE/UPDATE
|
||||
elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_event'])) {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_event'])) {
|
||||
$events = load_events();
|
||||
$id = $_POST['id'] ?: 'evt_' . bin2hex(random_bytes(6));
|
||||
$title = sanitize_text($_POST['title'] ?? '');
|
||||
@@ -226,36 +242,43 @@ if ($loggedIn) {
|
||||
<div class="alert alert-danger" role="alert"><?= h($migrationError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (is_array($migrationNotice)): ?>
|
||||
<?php if (!empty($migrationNotice['remaining'])): ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<?= (int)$migrationNotice['processed'] ?> Bild(er) wurden konvertiert und
|
||||
<?= (int)$migrationNotice['references'] ?> Datenbank-Verweis(e) aktualisiert.
|
||||
<?= count($migrationNotice['remaining']) ?> Originaldatei(en) konnten nicht entfernt werden und werden erneut angeboten.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-success" role="alert">
|
||||
Migration abgeschlossen: <?= (int)$migrationNotice['processed'] ?> Bild(er) konvertiert und
|
||||
<?= (int)$migrationNotice['references'] ?> Datenbank-Verweis(e) aktualisiert.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($nonWebpUploads)): ?>
|
||||
<div class="alert alert-warning d-md-flex justify-content-between align-items-center gap-3" role="alert">
|
||||
<div class="mb-3 mb-md-0">
|
||||
<div class="fw-semibold">WebP-Migration verfügbar</div>
|
||||
<div class="small">
|
||||
<?= count($nonWebpUploads) ?> vorhandene(s) Upload-Bild(er) sind noch nicht als WebP gespeichert.
|
||||
Die Veranstaltungseinträge werden automatisch aktualisiert.
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
id="webp-migration-panel"
|
||||
data-total="<?= count($nonWebpUploads) ?>"
|
||||
data-csrf="<?= h($migrationCsrf) ?>"
|
||||
role="status"
|
||||
>
|
||||
<div class="d-md-flex justify-content-between align-items-center gap-3">
|
||||
<div class="mb-3 mb-md-0">
|
||||
<div class="fw-semibold">WebP-Migration verfügbar</div>
|
||||
<div class="small" id="webp-migration-summary">
|
||||
<?= count($nonWebpUploads) ?> vorhandene(s) Upload-Bild(er) sind noch nicht als WebP gespeichert.
|
||||
Die Veranstaltungseinträge werden automatisch aktualisiert.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" onsubmit="return confirm('Alle erkannten Bestandsbilder jetzt nach WebP konvertieren?');">
|
||||
<input type="hidden" name="migration_csrf" value="<?= h($migrationCsrf) ?>">
|
||||
<button class="btn btn-warning text-nowrap" type="submit" name="migrate_uploads" value="1">
|
||||
<button class="btn btn-warning text-nowrap" type="button" id="webp-migration-start">
|
||||
In WebP konvertieren
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mt-3 d-none" id="webp-migration-progress-wrap">
|
||||
<div
|
||||
class="progress"
|
||||
role="progressbar"
|
||||
aria-label="Fortschritt der WebP-Migration"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="0"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
id="webp-migration-progress"
|
||||
style="width: 0%"
|
||||
>0%</div>
|
||||
</div>
|
||||
<div class="small mt-2" id="webp-migration-status" aria-live="polite">Migration wird vorbereitet …</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -491,6 +514,106 @@ if ($loggedIn) {
|
||||
toggle.addEventListener('change', apply);
|
||||
apply();
|
||||
})();
|
||||
|
||||
// Bestandsmigration: ein Bild pro Request, damit der Fortschritt sichtbar
|
||||
// bleibt und die Migration nach Abbruch gefahrlos fortgesetzt werden kann.
|
||||
(function () {
|
||||
const panel = document.getElementById('webp-migration-panel');
|
||||
const button = document.getElementById('webp-migration-start');
|
||||
const wrap = document.getElementById('webp-migration-progress-wrap');
|
||||
const progress = document.getElementById('webp-migration-progress');
|
||||
const progressBox = progress ? progress.parentElement : null;
|
||||
const status = document.getElementById('webp-migration-status');
|
||||
const summary = document.getElementById('webp-migration-summary');
|
||||
if (!panel || !button || !wrap || !progress || !progressBox || !status || !summary) return;
|
||||
|
||||
let total = Number.parseInt(panel.dataset.total || '0', 10);
|
||||
let completed = 0;
|
||||
let running = false;
|
||||
|
||||
const setProgress = (percent) => {
|
||||
const bounded = Math.max(0, Math.min(100, percent));
|
||||
progress.style.width = `${bounded}%`;
|
||||
progress.textContent = `${bounded}%`;
|
||||
progressBox.setAttribute('aria-valuenow', String(bounded));
|
||||
};
|
||||
|
||||
const finish = () => {
|
||||
setProgress(100);
|
||||
progress.classList.remove('progress-bar-animated');
|
||||
panel.classList.remove('alert-warning', 'alert-danger');
|
||||
panel.classList.add('alert-success');
|
||||
summary.textContent = 'WebP-Migration abgeschlossen.';
|
||||
status.textContent = 'Alle Bestandsbilder wurden konvertiert und ihre Datenbank-Verweise aktualisiert.';
|
||||
button.classList.add('d-none');
|
||||
running = false;
|
||||
};
|
||||
|
||||
const runMigration = async () => {
|
||||
if (running) return;
|
||||
if (completed === 0 && !window.confirm('Alle erkannten Bestandsbilder jetzt nach WebP konvertieren?')) return;
|
||||
|
||||
running = true;
|
||||
button.disabled = true;
|
||||
button.textContent = 'Migration läuft …';
|
||||
wrap.classList.remove('d-none');
|
||||
panel.classList.remove('alert-danger');
|
||||
panel.classList.add('alert-warning');
|
||||
progress.classList.add('progress-bar-animated');
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
status.textContent = `Konvertiere Bild ${Math.min(completed + 1, total)} von ${total} …`;
|
||||
const body = new URLSearchParams({
|
||||
migration_step: '1',
|
||||
migration_csrf: panel.dataset.csrf || ''
|
||||
});
|
||||
const response = await fetch('admin.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
|
||||
body: body.toString()
|
||||
});
|
||||
const responseText = await response.text();
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(responseText);
|
||||
} catch (error) {
|
||||
throw new Error(`Ungültige Serverantwort (HTTP ${response.status}).`);
|
||||
}
|
||||
if (!response.ok || !payload.ok) {
|
||||
throw new Error(payload.error || `Serverfehler (HTTP ${response.status}).`);
|
||||
}
|
||||
|
||||
const result = payload.result;
|
||||
if (result.processed === 0 || result.remaining_count === 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
completed += result.processed;
|
||||
total = Math.max(total, completed + result.remaining_count);
|
||||
setProgress(Math.round((completed / total) * 100));
|
||||
status.textContent = `${result.source} wurde konvertiert. Noch ${result.remaining_count} Bild(er).`;
|
||||
|
||||
if (result.deletion_failed) {
|
||||
throw new Error(`Die Originaldatei ${result.source} konnte nicht entfernt werden.`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
running = false;
|
||||
button.disabled = false;
|
||||
button.textContent = 'Migration fortsetzen';
|
||||
progress.classList.remove('progress-bar-animated');
|
||||
panel.classList.remove('alert-warning');
|
||||
panel.classList.add('alert-danger');
|
||||
summary.textContent = 'Die Migration wurde unterbrochen.';
|
||||
status.textContent = error instanceof Error ? error.message : 'Unbekannter Fehler.';
|
||||
}
|
||||
};
|
||||
|
||||
button.addEventListener('click', runMigration);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user