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(); };