mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+FTS5 Rebuild
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,11 +3,10 @@
|
||||
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
||||
<div class="flex-col w-full">
|
||||
<a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
|
||||
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">Datenexport</h1>
|
||||
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">Einstellungen</h1>
|
||||
<div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[70ch]">
|
||||
<i class="ri-question-line"></i>
|
||||
Export von Daten u. Dateien als ZIP-Ordner. Die Exporte werden gespeichert und nach dem
|
||||
Ablauf von sieben Tagen automatisch gelöscht.
|
||||
Export von Daten u. Dateien. Verwaltung der Suchindizes und globalen Einstellungen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,43 +16,85 @@
|
||||
data-run-url="/redaktion/exports/run/"
|
||||
data-list-url="/redaktion/exports/list/"
|
||||
data-delete-url="/redaktion/exports/delete/"
|
||||
data-fts5-rebuild-url="/redaktion/exports/fts5/rebuild/"
|
||||
data-fts5-status-url="/redaktion/exports/fts5/status/"
|
||||
data-csrf="{{ $model.csrf_token }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Daten-Export erstellen</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Sichert alle Daten der Tabellen als
|
||||
XML-Dateien. Der Export läuft im Hintergrund.</p>
|
||||
{{ template "_usermessage" $model }}
|
||||
<div class="bg-slate-50 rounded-md shadow-sm border border-slate-200 p-6 mb-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Daten-Export erstellen</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Sichert alle Daten der Tabellen als
|
||||
XML-Dateien. Der Export läuft im Hintergrund.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" class="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50" data-role="run-export" data-export-type="data">
|
||||
<i class="ri-download-2-line"></i> Export starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" class="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50" data-role="run-export" data-export-type="data">
|
||||
<i class="ri-download-2-line"></i> Export starten
|
||||
</button>
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Dateien sichern</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Exportiert Bilder und Dateien als ZIP. Der
|
||||
Export kann eine Weile in Anspruch nehmen und läuft ebenfalls im Hintergrund.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" class="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50" data-role="run-export" data-export-type="files">
|
||||
<i class="ri-folder-zip-line"></i> Dateien sichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Dateien sichern</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Exportiert Bilder und Dateien als ZIP. Der
|
||||
Export kann eine Weile in Anspruch nehmen und läuft ebenfalls im Hintergrund.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" class="inline-flex items-center gap-2 rounded-md bg-slate-700 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50" data-role="run-export" data-export-type="files">
|
||||
<i class="ri-folder-zip-line"></i> Dateien sichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-slate-600" data-role="status"></div>
|
||||
<div class="text-sm text-slate-600" data-role="status"></div>
|
||||
|
||||
<div class="">
|
||||
<div class="flex items-center justify-between mb-4 border-b border-slate-200 pb-2">
|
||||
<h3 class="text-base font-semibold text-slate-900">Letzte Exporte</h3>
|
||||
<div class="text-xs text-slate-500">Aktualisiert automatisch</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3" data-role="export-list">
|
||||
{{ template "_export_list" $model }}
|
||||
<div class="">
|
||||
<div class="flex items-center justify-between mb-4 border-b border-slate-200 pb-2">
|
||||
<h3 class="text-base font-semibold text-slate-900">Letzte Exporte</h3>
|
||||
<div class="text-xs text-slate-500">Aktualisiert automatisch</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3" data-role="export-list">
|
||||
{{ template "_export_list" $model }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bg-slate-50 rounded-md shadow-sm border border-slate-200 p-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-lg font-semibold text-slate-900">Suchindex neu aufbauen</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Löscht und erstellt den Suchindex aus den
|
||||
bestehenden Einträgen neu. Kann bei Problemen bei der Suche und Auffindbarkeit von
|
||||
Bänden und Beiträgen hilfreich sein. <em>Die Datenbank sollte während dem Aufbau möglichst
|
||||
nicht verändert werden.</em> Ein automatischer Rebuild findet jeden Sonntag um 00:00 Uhr statt.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 shrink-0">
|
||||
<button type="button" class="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50 whitespace-nowrap" data-role="fts5-rebuild">
|
||||
<i class="ri-refresh-line"></i>
|
||||
<span data-role="fts5-rebuild-label">Neuaufbau starten</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{- if $model.fts5_last_rebuild_dt.IsZero | not -}}
|
||||
<div class="text-xs text-slate-500">Zuletzt aufgebaut: <span data-role="fts5-last-rebuild">{{ GermanDate $model.fts5_last_rebuild_dt }} {{ GermanTime $model.fts5_last_rebuild_dt }}</span></div>
|
||||
{{- else -}}
|
||||
<div class="text-xs text-slate-500 hidden" data-role="fts5-last-rebuild-wrap">Zuletzt aufgebaut: <span data-role="fts5-last-rebuild"></span></div>
|
||||
{{- end -}}
|
||||
<div class="mt-3 hidden rounded-md border px-3 py-2 text-sm" data-role="fts5-status"></div>
|
||||
<div class="mt-3 hidden" data-role="fts5-progress">
|
||||
<div class="flex items-center justify-between text-xs text-slate-500 mb-1">
|
||||
<span data-role="fts5-progress-text"></span>
|
||||
<span data-role="fts5-progress-percent"></span>
|
||||
</div>
|
||||
<div class="h-2 w-full bg-slate-200 rounded">
|
||||
<div class="h-2 bg-slate-700 rounded transition-all duration-200" style="width: 0%;" data-role="fts5-progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</export-manager>
|
||||
|
||||
34
views/routes/redaktion/settings/body.gohtml
Normal file
34
views/routes/redaktion/settings/body.gohtml
Normal file
@@ -0,0 +1,34 @@
|
||||
{{ $model := . }}
|
||||
|
||||
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
||||
<div class="flex-col w-full">
|
||||
<a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
|
||||
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">Einstellungen</h1>
|
||||
<div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[70ch]">
|
||||
<i class="ri-settings-3-line"></i>
|
||||
Globale Einstellungen fuer die Seite und Systemfunktionen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-normal mx-auto px-8">
|
||||
{{ template "_usermessage" $model }}
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
<section class="bg-white rounded-md shadow-sm border border-slate-200 p-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">FTS5 neu aufbauen</h2>
|
||||
<p class="text-sm text-slate-600 mt-1">Löscht und erstellt die FTS5-Tabellen neu und füllt sie aus den bestehenden Einträgen.</p>
|
||||
</div>
|
||||
<form method="post" action="/redaktion/settings/fts5/rebuild/" class="flex items-center">
|
||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-400/50">
|
||||
<i class="ri-refresh-line"></i> Neuaufbau starten
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 mt-3">Automatischer Neuaufbau: jeden Sonntag um 00:00 Uhr.</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,9 +4,16 @@ export class ExportManager extends HTMLElement {
|
||||
this.listUrl = "";
|
||||
this.runUrl = "";
|
||||
this.deleteUrl = "";
|
||||
this.fts5RebuildUrl = "";
|
||||
this.fts5StatusUrl = "";
|
||||
this.csrf = "";
|
||||
this.list = null;
|
||||
this.status = null;
|
||||
this.fts5Status = null;
|
||||
this.fts5Progress = null;
|
||||
this.fts5ProgressText = null;
|
||||
this.fts5ProgressPercent = null;
|
||||
this.fts5ProgressBar = null;
|
||||
this.pollTimer = null;
|
||||
this.pollIntervalMs = 2500;
|
||||
}
|
||||
@@ -15,13 +22,27 @@ export class ExportManager extends HTMLElement {
|
||||
this.listUrl = this.dataset.listUrl || "";
|
||||
this.runUrl = this.dataset.runUrl || "";
|
||||
this.deleteUrl = this.dataset.deleteUrl || "";
|
||||
this.fts5RebuildUrl = this.dataset.fts5RebuildUrl || "";
|
||||
this.fts5StatusUrl = this.dataset.fts5StatusUrl || "";
|
||||
this.csrf = this.dataset.csrf || "";
|
||||
this.list = this.querySelector("[data-role='export-list']");
|
||||
this.status = this.querySelector("[data-role='status']");
|
||||
this.fts5Status = this.querySelector("[data-role='fts5-status']");
|
||||
this.fts5Progress = this.querySelector("[data-role='fts5-progress']");
|
||||
this.fts5ProgressText = this.querySelector("[data-role='fts5-progress-text']");
|
||||
this.fts5ProgressPercent = this.querySelector("[data-role='fts5-progress-percent']");
|
||||
this.fts5ProgressBar = this.querySelector("[data-role='fts5-progress-bar']");
|
||||
this.fts5LastRebuild = this.querySelector("[data-role='fts5-last-rebuild']");
|
||||
this.fts5LastRebuildWrap = this.querySelector("[data-role='fts5-last-rebuild-wrap']");
|
||||
this.fts5Button = this.querySelector("[data-role='fts5-rebuild']");
|
||||
this.fts5ButtonLabel = this.querySelector("[data-role='fts5-rebuild-label']");
|
||||
this.fts5StatusValue = "idle";
|
||||
this.fts5HadRunning = false;
|
||||
|
||||
this.addEventListener("click", (event) => this.handleAction(event));
|
||||
|
||||
this.refreshList();
|
||||
this.refreshFts5Status();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -69,7 +90,7 @@ export class ExportManager extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setStatus("Export läuft im Hintergrund.");
|
||||
this.setStatus("Export läuft im Hintergrund.");
|
||||
await this.refreshList();
|
||||
this.startPolling();
|
||||
} catch (err) {
|
||||
@@ -87,6 +108,12 @@ export class ExportManager extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const fts5Target = event.target.closest("[data-role='fts5-rebuild']");
|
||||
if (fts5Target) {
|
||||
await this.handleFts5Rebuild(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target.closest("[data-action]");
|
||||
if (!target) return;
|
||||
const action = target.getAttribute("data-action");
|
||||
@@ -140,10 +167,163 @@ export class ExportManager extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
async refreshFts5Status() {
|
||||
if (!this.fts5StatusUrl) return;
|
||||
try {
|
||||
const response = await fetch(this.fts5StatusUrl, { credentials: "same-origin" });
|
||||
if (!response.ok) return;
|
||||
const json = await this.safeJson(response);
|
||||
if (!json) return;
|
||||
this.updateFts5Status(json);
|
||||
this.syncPollingState();
|
||||
} catch {
|
||||
// ignore refresh errors
|
||||
}
|
||||
}
|
||||
|
||||
updateFts5Status(data) {
|
||||
if (!this.fts5Status) return;
|
||||
const prevStatus = this.fts5StatusValue;
|
||||
const status = this.normalizeText(data.status) || "idle";
|
||||
const message = this.normalizeText(data.message || "");
|
||||
const err = this.normalizeText(data.error || "");
|
||||
const done = Number.isFinite(data.done) ? data.done : 0;
|
||||
const total = Number.isFinite(data.total) ? data.total : 0;
|
||||
const lastRebuild = this.formatGermanDateTime(this.normalizeText(data.last_rebuild || ""));
|
||||
this.fts5StatusValue = status;
|
||||
|
||||
this.fts5Status.classList.remove(
|
||||
"hidden",
|
||||
"text-slate-700",
|
||||
"text-green-800",
|
||||
"text-red-700",
|
||||
"text-amber-800",
|
||||
"bg-slate-50",
|
||||
"bg-green-50",
|
||||
"bg-red-50",
|
||||
"bg-amber-50",
|
||||
"border-slate-200",
|
||||
"border-green-200",
|
||||
"border-red-200",
|
||||
"border-amber-200",
|
||||
);
|
||||
if (status === "running" || status === "restarting") {
|
||||
this.fts5HadRunning = true;
|
||||
}
|
||||
|
||||
if (status === "complete" && !this.fts5HadRunning) {
|
||||
this.fts5Status.textContent = "";
|
||||
this.fts5Status.classList.add("hidden");
|
||||
} else if (status === "error") {
|
||||
this.fts5Status.textContent = err || "FTS5-Neuaufbau fehlgeschlagen.";
|
||||
this.fts5Status.classList.add("text-red-700", "bg-red-50", "border-red-200");
|
||||
} else if (status === "aborted") {
|
||||
this.fts5Status.textContent = message || "FTS5-Neuaufbau abgebrochen.";
|
||||
this.fts5Status.classList.add("text-red-700", "bg-red-50", "border-red-200");
|
||||
} else if (status === "complete") {
|
||||
this.fts5Status.textContent = message || "FTS5-Neuaufbau abgeschlossen.";
|
||||
this.fts5Status.classList.add("text-green-800", "bg-green-50", "border-green-200");
|
||||
} else if (status === "restarting") {
|
||||
this.fts5Status.textContent = message || "FTS5-Neuaufbau wird neu gestartet.";
|
||||
this.fts5Status.classList.add("text-amber-800", "bg-amber-50", "border-amber-200");
|
||||
} else if (status === "running") {
|
||||
this.fts5Status.textContent = message || "FTS5-Neuaufbau läuft.";
|
||||
this.fts5Status.classList.add("text-amber-800", "bg-amber-50", "border-amber-200");
|
||||
} else {
|
||||
this.fts5Status.textContent = message || "";
|
||||
if (!this.fts5Status.textContent) {
|
||||
this.fts5Status.classList.add("hidden");
|
||||
} else {
|
||||
this.fts5Status.classList.add("text-slate-700", "bg-slate-50", "border-slate-200");
|
||||
}
|
||||
}
|
||||
if (this.fts5Status.textContent) {
|
||||
this.fts5Status.classList.remove("hidden");
|
||||
}
|
||||
|
||||
if (this.fts5Progress) {
|
||||
if (status === "running" || status === "restarting") {
|
||||
this.fts5Progress.classList.remove("hidden");
|
||||
} else {
|
||||
this.fts5Progress.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fts5Button) {
|
||||
const isRunning = status === "running";
|
||||
if (this.fts5ButtonLabel) {
|
||||
this.fts5ButtonLabel.textContent = isRunning ? "Abbrechen & neu starten" : "Neuaufbau starten";
|
||||
}
|
||||
this.fts5Button.classList.toggle("bg-slate-900", !isRunning);
|
||||
this.fts5Button.classList.toggle("hover:bg-slate-800", !isRunning);
|
||||
this.fts5Button.classList.toggle("bg-amber-600", isRunning);
|
||||
this.fts5Button.classList.toggle("hover:bg-amber-700", isRunning);
|
||||
}
|
||||
|
||||
if (this.fts5LastRebuild && lastRebuild) {
|
||||
this.fts5LastRebuild.textContent = lastRebuild;
|
||||
if (this.fts5LastRebuildWrap) {
|
||||
this.fts5LastRebuildWrap.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
if (prevStatus === "running" && status !== "running") {
|
||||
window.setTimeout(() => {
|
||||
this.refreshFts5Status();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
if ((status === "running" || status === "restarting") && total > 0) {
|
||||
const percent = Math.min(100, Math.round((done / total) * 100));
|
||||
if (this.fts5ProgressText) {
|
||||
this.fts5ProgressText.textContent = `${done} / ${total}`;
|
||||
}
|
||||
if (this.fts5ProgressPercent) {
|
||||
this.fts5ProgressPercent.textContent = `${percent}%`;
|
||||
}
|
||||
if (this.fts5ProgressBar) {
|
||||
this.fts5ProgressBar.style.width = `${percent}%`;
|
||||
}
|
||||
} else if (status === "running" || status === "restarting") {
|
||||
if (this.fts5ProgressText) {
|
||||
this.fts5ProgressText.textContent = "Wird vorbereitet...";
|
||||
}
|
||||
if (this.fts5ProgressPercent) {
|
||||
this.fts5ProgressPercent.textContent = "";
|
||||
}
|
||||
if (this.fts5ProgressBar) {
|
||||
this.fts5ProgressBar.style.width = "0%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formatGermanDateTime(value) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const normalized = raw.replace(/^"+|"+$/g, "");
|
||||
const isoMatch = normalized.match(
|
||||
/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/,
|
||||
);
|
||||
if (!isoMatch) return normalized;
|
||||
const iso = normalized.replace(" ", "T");
|
||||
const date = new Date(iso);
|
||||
if (Number.isNaN(date.getTime())) return normalized;
|
||||
const weekdays = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
||||
const months = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
|
||||
const day = date.getDate();
|
||||
const month = months[date.getMonth()];
|
||||
const year = date.getFullYear();
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
return `${weekdays[date.getDay()]}, ${day}. ${month} ${year} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
syncPollingState() {
|
||||
if (!this.list) return;
|
||||
const active = this.list.querySelector("[data-export-status='running'], [data-export-status='queued']");
|
||||
if (active) {
|
||||
const hasExports = this.list
|
||||
? this.list.querySelector("[data-export-status='running'], [data-export-status='queued']")
|
||||
: null;
|
||||
const fts5Running = this.fts5Progress && !this.fts5Progress.classList.contains("hidden");
|
||||
if (hasExports || fts5Running) {
|
||||
this.startPolling();
|
||||
} else {
|
||||
this.stopPolling();
|
||||
@@ -154,6 +334,7 @@ export class ExportManager extends HTMLElement {
|
||||
if (this.pollTimer) return;
|
||||
this.pollTimer = window.setInterval(() => {
|
||||
this.refreshList();
|
||||
this.refreshFts5Status();
|
||||
}, this.pollIntervalMs);
|
||||
}
|
||||
|
||||
@@ -163,6 +344,68 @@ export class ExportManager extends HTMLElement {
|
||||
this.pollTimer = null;
|
||||
}
|
||||
|
||||
async handleFts5Rebuild(event) {
|
||||
event.preventDefault();
|
||||
if (!this.fts5RebuildUrl) return;
|
||||
const button = event.target.closest("[data-role='fts5-rebuild']");
|
||||
if (button) button.disabled = true;
|
||||
if (this.fts5Progress) {
|
||||
this.fts5Progress.classList.remove("hidden");
|
||||
}
|
||||
if (this.fts5ProgressText) {
|
||||
this.fts5ProgressText.textContent = "Wird vorbereitet...";
|
||||
}
|
||||
if (this.fts5ProgressPercent) {
|
||||
this.fts5ProgressPercent.textContent = "";
|
||||
}
|
||||
if (this.fts5ProgressBar) {
|
||||
this.fts5ProgressBar.style.width = "0%";
|
||||
}
|
||||
|
||||
const payload = new URLSearchParams();
|
||||
payload.set("csrf_token", this.getCsrfToken());
|
||||
|
||||
try {
|
||||
const response = await fetch(this.fts5RebuildUrl, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!response.ok) {
|
||||
const message = await this.extractError(response);
|
||||
if (this.fts5Status) {
|
||||
this.fts5Status.textContent = message || "FTS5-Neuaufbau konnte nicht gestartet werden.";
|
||||
this.fts5Status.classList.add("text-red-600");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const json = await this.safeJson(response);
|
||||
if (json && json.error) {
|
||||
if (this.fts5Status) {
|
||||
this.fts5Status.textContent = json.error;
|
||||
this.fts5Status.classList.add("text-red-600");
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.fts5HadRunning = true;
|
||||
if (json && json.status === "restarting") {
|
||||
this.updateFts5Status({
|
||||
status: "restarting",
|
||||
message: "FTS5-Neuaufbau wird neu gestartet.",
|
||||
});
|
||||
}
|
||||
await this.refreshFts5Status();
|
||||
this.startPolling();
|
||||
} catch {
|
||||
if (this.fts5Status) {
|
||||
this.fts5Status.textContent = "FTS5-Neuaufbau konnte nicht gestartet werden.";
|
||||
this.fts5Status.classList.add("text-red-600");
|
||||
}
|
||||
} finally {
|
||||
if (button) button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async safeJson(response) {
|
||||
try {
|
||||
return await response.json();
|
||||
@@ -195,4 +438,13 @@ export class ExportManager extends HTMLElement {
|
||||
}
|
||||
return this.csrf;
|
||||
}
|
||||
|
||||
normalizeText(value) {
|
||||
if (value == null) return "";
|
||||
let text = String(value).trim();
|
||||
if (text.length >= 2 && text.startsWith("\"") && text.endsWith("\"")) {
|
||||
text = text.slice(1, -1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,8 +339,8 @@ export class FabMenu extends HTMLElement {
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto] group">
|
||||
<a href="/redaktion/exports/" class="flex items-center px-3 py-1.5 group-hover:bg-gray-100 transition-colors no-underline text-sm">
|
||||
<i class="ri-download-2-line text-base text-gray-700 mr-2.5"></i>
|
||||
<span class="text-gray-900">Datenexport</span>
|
||||
<i class="ri-tools-line text-base text-gray-700 mr-2.5"></i>
|
||||
<span class="text-gray-900">Daten & Suche</span>
|
||||
</a>
|
||||
<a href="/redaktion/exports/" target="_blank" class="flex items-center justify-center px-2.5 py-1.5 group-hover:bg-gray-100 text-gray-700 hover:text-slate-900 transition-colors no-underline text-sm" title="In neuem Tab öffnen">
|
||||
<i class="ri-external-link-line text-base"></i>
|
||||
|
||||
Reference in New Issue
Block a user