fix(dashboard): Kachel Artikel-Uebersetzung erneut entfernt
Der Uebertrag-Commit9bf3adbhat die parallel gelandete Entfernung16a9b9cversehentlich rueckgaengig gemacht (Komplett-Datei-Upload aus einem aelteren Arbeitsstand). Diese Wiederholung stellt den gewollten Zustand wieder her, Karte + JS-Block sind wieder raus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -76,32 +76,6 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Artikel-Übersetzung -->
|
||||
<div class="card" id="translationCard" style="margin-top:16px;">
|
||||
<div class="card-header">
|
||||
<h2>Artikel-Übersetzung</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted" style="margin-top:0;">
|
||||
Die automatische Übersetzung im Monitor ist deaktiviert. Hier lassen sich
|
||||
fremdsprachige Artikel ohne deutsche Fassung manuell übersetzen.
|
||||
</p>
|
||||
<p id="translationInfo" style="margin:12px 0;">Status wird geladen…</p>
|
||||
|
||||
<div id="translationProgressWrap" style="display:none; margin:12px 0;">
|
||||
<div style="background:rgba(128,128,128,0.25); border-radius:6px; height:14px; overflow:hidden;">
|
||||
<div id="translationProgressBar" style="background:#1565c0; height:100%; width:0%; transition:width .3s;"></div>
|
||||
</div>
|
||||
<p class="text-muted" id="translationProgressText" style="margin:6px 0 0;"></p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:12px; display:flex; gap:8px;">
|
||||
<button class="btn btn-primary" id="translationRunBtn">Übersetzung starten</button>
|
||||
<button class="btn btn-danger" id="translationCancelBtn" style="display:none;">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Organizations Section -->
|
||||
|
||||
@@ -130,11 +130,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
setupNavTabs();
|
||||
setupOrgDetailTabs();
|
||||
setupForms();
|
||||
setupTranslation();
|
||||
loadDashboard();
|
||||
loadTariffInfo();
|
||||
loadDashboardTokenStats();
|
||||
loadTranslationStatus();
|
||||
loadOrgs();
|
||||
});
|
||||
|
||||
@@ -154,7 +152,6 @@ function setupNavTabs() {
|
||||
document.querySelectorAll(".app-content > .section").forEach(s => s.classList.remove("active"));
|
||||
document.getElementById(`sec-${section}`).classList.add("active");
|
||||
|
||||
if (section === "dashboard") loadTranslationStatus();
|
||||
if (section === "audit" && typeof loadAudit === "function") loadAudit();
|
||||
});
|
||||
});
|
||||
@@ -912,151 +909,6 @@ function formatDate(iso) {
|
||||
}
|
||||
|
||||
|
||||
// ===== Artikel-Übersetzung =====
|
||||
let translationPollTimer = null;
|
||||
|
||||
function setupTranslation() {
|
||||
const runBtn = document.getElementById("translationRunBtn");
|
||||
const cancelBtn = document.getElementById("translationCancelBtn");
|
||||
if (runBtn) runBtn.addEventListener("click", startTranslation);
|
||||
if (cancelBtn) cancelBtn.addEventListener("click", cancelTranslation);
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
seconds = Math.max(0, Math.round(seconds || 0));
|
||||
if (seconds < 60) return seconds + " Sek.";
|
||||
const min = Math.round(seconds / 60);
|
||||
if (min < 60) return min + " Min.";
|
||||
const h = Math.floor(min / 60), m = min % 60;
|
||||
return h + " Std. " + (m ? m + " Min." : "").trim();
|
||||
}
|
||||
|
||||
function renderTranslation(st) {
|
||||
const info = document.getElementById("translationInfo");
|
||||
const wrap = document.getElementById("translationProgressWrap");
|
||||
const bar = document.getElementById("translationProgressBar");
|
||||
const ptext = document.getElementById("translationProgressText");
|
||||
const runBtn = document.getElementById("translationRunBtn");
|
||||
const cancelBtn = document.getElementById("translationCancelBtn");
|
||||
if (!info || !runBtn) return;
|
||||
|
||||
if (st.running) {
|
||||
runBtn.style.display = "none";
|
||||
cancelBtn.style.display = "";
|
||||
wrap.style.display = "";
|
||||
const pct = st.total > 0 ? Math.round((st.done / st.total) * 100) : 0;
|
||||
bar.style.width = pct + "%";
|
||||
ptext.textContent = `${st.done} / ${st.total} verarbeitet, ${st.translated} übersetzt (${pct}%)`;
|
||||
info.textContent = "Übersetzung läuft…";
|
||||
return;
|
||||
}
|
||||
|
||||
runBtn.style.display = "";
|
||||
cancelBtn.style.display = "none";
|
||||
wrap.style.display = "none";
|
||||
|
||||
let resultLine = "";
|
||||
if (st.finished_at && (st.total > 0 || st.error)) {
|
||||
if (st.error) {
|
||||
resultLine = `Letzter Lauf mit Fehler beendet: ${st.error}. `;
|
||||
} else if (st.cancelled) {
|
||||
resultLine = `Letzter Lauf abgebrochen, ${st.translated} von ${st.total} Artikeln übersetzt. `;
|
||||
} else {
|
||||
resultLine = `Letzter Lauf abgeschlossen, ${st.translated} Artikel übersetzt. `;
|
||||
}
|
||||
}
|
||||
|
||||
if (st.pending > 0) {
|
||||
const est = st.estimate || {};
|
||||
info.textContent = resultLine +
|
||||
`${st.pending} Artikel ohne deutsche Übersetzung. ` +
|
||||
`Geschätzt: ${formatDuration(est.seconds)}, ca. $${est.cost_usd}.`;
|
||||
runBtn.disabled = false;
|
||||
} else {
|
||||
info.textContent = resultLine + "Alle Artikel sind übersetzt.";
|
||||
runBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTranslationStatus() {
|
||||
try {
|
||||
const st = await API.get("/api/translation/status");
|
||||
renderTranslation(st);
|
||||
if (st.running && !translationPollTimer) {
|
||||
translationPollTimer = setInterval(pollTranslation, 3000);
|
||||
}
|
||||
} catch (e) {
|
||||
const info = document.getElementById("translationInfo");
|
||||
if (info) info.textContent = "Status nicht abrufbar: " + (e.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
async function pollTranslation() {
|
||||
try {
|
||||
const st = await API.get("/api/translation/status");
|
||||
renderTranslation(st);
|
||||
if (!st.running) {
|
||||
clearInterval(translationPollTimer);
|
||||
translationPollTimer = null;
|
||||
if (st.error) {
|
||||
showToast("Übersetzung mit Fehler beendet", "error");
|
||||
} else if (st.cancelled) {
|
||||
showToast(`Übersetzung abgebrochen, ${st.translated} übersetzt`, "info");
|
||||
} else {
|
||||
showToast(`Übersetzung fertig: ${st.translated} Artikel`, "success");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Translation-Poll fehlgeschlagen:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function startTranslation() {
|
||||
let st;
|
||||
try {
|
||||
st = await API.get("/api/translation/status");
|
||||
} catch (e) {
|
||||
showToast(e.message || "Status nicht abrufbar", "error");
|
||||
return;
|
||||
}
|
||||
if (st.running) { showToast("Es läuft bereits eine Übersetzung", "info"); return; }
|
||||
if (!st.pending) { showToast("Es gibt nichts zu übersetzen", "info"); return; }
|
||||
|
||||
const est = st.estimate || {};
|
||||
const ok = await showConfirm(
|
||||
"Übersetzung starten",
|
||||
`${st.pending} Artikel werden ins Deutsche übersetzt. ` +
|
||||
`Geschätzte Dauer: ${formatDuration(est.seconds)}, geschätzte Kosten: ca. $${est.cost_usd}. ` +
|
||||
`Der Lauf kann jederzeit abgebrochen werden.`
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
try {
|
||||
const res = await API.post("/api/translation/run", {});
|
||||
if (res && res.status === "started") {
|
||||
showToast(`Übersetzung gestartet (${res.pending} Artikel)`, "success");
|
||||
await loadTranslationStatus();
|
||||
if (!translationPollTimer) {
|
||||
translationPollTimer = setInterval(pollTranslation, 3000);
|
||||
}
|
||||
} else {
|
||||
showToast("Es gibt nichts zu übersetzen", "info");
|
||||
loadTranslationStatus();
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e.message || "Start fehlgeschlagen", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelTranslation() {
|
||||
try {
|
||||
await API.post("/api/translation/cancel", {});
|
||||
showToast("Übersetzung wird abgebrochen…", "info");
|
||||
} catch (e) {
|
||||
showToast(e.message || "Abbruch fehlgeschlagen", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Token-Nutzung =====
|
||||
async function loadOrgTokenUsage(orgId) {
|
||||
try {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren