feat(admin): CSV-Export-Button im Cockpit

Lädt /admin/export/accounts.csv mit Admin-Token (fetch + Blob-Download).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 12:17:33 +02:00
parent db357b8103
commit 42ea5ac272
2 changed files with 36 additions and 2 deletions
+18 -1
View File
@@ -399,7 +399,9 @@
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">' +
'<div class="hosting-title" style="text-align:left;margin:0">Cockpit</div>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div style="display:flex;gap:14px;align-items:center">' +
'<button class="hosting-link" id="aexport">CSV-Export</button>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div></div>' +
'<div class="admin-stats">' +
statCard("Kunden", stats.accounts, "+" + (stats.newAccounts30d || 0) + " (30 T.)") +
statCard("Aktive Abos", stats.activeSubscriptions) +
@@ -417,6 +419,21 @@
root.innerHTML = card(html, true);
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
root.querySelector("#aexport").onclick = async () => {
// CSV braucht den Admin-Token im Header → per fetch holen + Blob-Download.
try {
const res = await fetch("/api/admin/export/accounts.csv", {
headers: { Authorization: "Bearer " + adminTok.get() },
});
if (!res.ok) throw new Error("Export fehlgeschlagen (" + res.status + ")");
const blob = await res.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "rapport-kunden.csv";
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(a.href);
} catch (err) { alert(err.message); }
};
const s = root.querySelector("#asearch");
s.oninput = () => { adminSearch = s.value; const pos = s.selectionStart; paintAdmin(); const n = root.querySelector("#asearch"); n.focus(); n.setSelectionRange(pos, pos); };
root.querySelector("#afilter").onchange = (e) => { adminPlanFilter = e.target.value; paintAdmin(); };
+18 -1
View File
@@ -399,7 +399,9 @@
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">' +
'<div class="hosting-title" style="text-align:left;margin:0">Cockpit</div>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div style="display:flex;gap:14px;align-items:center">' +
'<button class="hosting-link" id="aexport">CSV-Export</button>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div></div>' +
'<div class="admin-stats">' +
statCard("Kunden", stats.accounts, "+" + (stats.newAccounts30d || 0) + " (30 T.)") +
statCard("Aktive Abos", stats.activeSubscriptions) +
@@ -417,6 +419,21 @@
root.innerHTML = card(html, true);
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
root.querySelector("#aexport").onclick = async () => {
// CSV braucht den Admin-Token im Header → per fetch holen + Blob-Download.
try {
const res = await fetch("/api/admin/export/accounts.csv", {
headers: { Authorization: "Bearer " + adminTok.get() },
});
if (!res.ok) throw new Error("Export fehlgeschlagen (" + res.status + ")");
const blob = await res.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "rapport-kunden.csv";
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(a.href);
} catch (err) { alert(err.message); }
};
const s = root.querySelector("#asearch");
s.oninput = () => { adminSearch = s.value; const pos = s.selectionStart; paintAdmin(); const n = root.querySelector("#asearch"); n.focus(); n.setSelectionRange(pos, pos); };
root.querySelector("#afilter").onchange = (e) => { adminPlanFilter = e.target.value; paintAdmin(); };