' +
stat("Kunden", stats.accounts) +
stat("Aktive Abos", stats.activeSubscriptions) +
diff --git a/static/js/hosting-app.js b/static/js/hosting-app.js
index bae8cfe..aca7356 100644
--- a/static/js/hosting-app.js
+++ b/static/js/hosting-app.js
@@ -126,9 +126,7 @@
const head =
'
' +
'
Mein Konto
' +
- '
' +
- (account.is_admin ? '
Admin' : "") +
- '
' +
+ '
' +
'' +
tabs.map(([id, label]) =>
@@ -276,21 +274,55 @@
});
}
- // ── Admin / Betreiber-Bereich ────────────────────────────────────────────
+ // ── Admin / Betreiber-Bereich (SEPARATES Login, eigener Token) ────────────
+ const ADMIN_KEY = "rapport_admin_token";
+ const adminTok = {
+ get: () => localStorage.getItem(ADMIN_KEY),
+ set: (t) => localStorage.setItem(ADMIN_KEY, t),
+ clear: () => localStorage.removeItem(ADMIN_KEY),
+ };
+ // wie api(), aber mit Admin-Token statt Kunden-Token.
+ async function adminApi(method, path, body) {
+ const headers = { "Content-Type": "application/json" };
+ const t = adminTok.get();
+ if (t) headers.Authorization = `Bearer ${t}`;
+ const res = await fetch(`/api${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
+ const data = await res.json().catch(() => ({}));
+ if (!res.ok) { const e = new Error(data.error || `Fehler ${res.status}`); e.status = res.status; throw e; }
+ return data;
+ }
+
+ function renderAdminLogin(errText) {
+ root.innerHTML = card(
+ '
Betreiber-Login
' +
+ '
Interner Bereich
' +
+ (errText ? '
' + esc(errText) + "
" : "") +
+ '
'
+ );
+ root.querySelector("#af").onsubmit = async (e) => {
+ e.preventDefault();
+ const sub = root.querySelector("#asub"); sub.disabled = true;
+ try {
+ const { token } = await adminApi("POST", "/admin/login", { password: root.querySelector("#apw").value });
+ adminTok.set(token); renderAdmin();
+ } catch (err) { renderAdminLogin(err.message); }
+ };
+ }
+
async function renderAdmin() {
- if (!tok.isLoggedIn) return go("/login/");
+ if (!adminTok.get()) return renderAdminLogin("");
root.innerHTML = card('
Lädt…
', true);
let stats, accounts;
try {
[stats, accounts] = await Promise.all([
- api("GET", "/admin/stats"),
- api("GET", "/admin/accounts"),
+ adminApi("GET", "/admin/stats"),
+ adminApi("GET", "/admin/accounts"),
]);
} catch (err) {
- if (/angemeldet|abgelaufen|ungültig/i.test(err.message)) { tok.clear(); return go("/login/"); }
- // 403 = eingeloggt, aber kein Admin
- root.innerHTML = card('
' + esc(err.message) + "
" +
- '
Zurück zum Konto');
+ if (err.status === 401 || err.status === 403) { adminTok.clear(); return renderAdminLogin("Bitte neu anmelden."); }
+ root.innerHTML = card('
' + esc(err.message) + "
");
return;
}
@@ -300,7 +332,7 @@
const rows = accounts.accounts.map((a) =>
"
" +
- "| " + esc(a.email) + "" + (a.is_admin ? ' Admin' : "") +
+ " | " + esc(a.email) + "" +
(a.company ? ' ' + esc(a.company) + " " : "") + " | " +
"" + (a.plan ? esc(a.plan) + ' ' + esc(a.sub_status || "") + " " : '—') + " | " +
"" + esc(a.instance_count) + " | " +
@@ -311,7 +343,7 @@
const html =
'' +
+ '' +
'' +
stat("Kunden", stats.accounts) +
stat("Aktive Abos", stats.activeSubscriptions) +