From 42ea5ac272ddb57fcbbe7f8c4733358c97496ce3 Mon Sep 17 00:00:00 2001 From: karim Date: Sun, 31 May 2026 12:17:33 +0200 Subject: [PATCH] feat(admin): CSV-Export-Button im Cockpit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lädt /admin/export/accounts.csv mit Admin-Token (fetch + Blob-Download). Co-Authored-By: Claude Opus 4.8 --- public/js/hosting-app.js | 19 ++++++++++++++++++- static/js/hosting-app.js | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/public/js/hosting-app.js b/public/js/hosting-app.js index 41dbad3..80e4fbb 100644 --- a/public/js/hosting-app.js +++ b/public/js/hosting-app.js @@ -399,7 +399,9 @@ const html = '
' + '
Cockpit
' + - '
' + + '
' + + '' + + '
' + '
' + 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(); }; diff --git a/static/js/hosting-app.js b/static/js/hosting-app.js index 41dbad3..80e4fbb 100644 --- a/static/js/hosting-app.js +++ b/static/js/hosting-app.js @@ -399,7 +399,9 @@ const html = '
' + '
Cockpit
' + - '
' + + '
' + + '' + + '
' + '
' + 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(); };