feat(cockpit): Zahlungsausfall-Banner + past_due-Badge + Health-Check-Button
- Warnbanner oben wenn past_due-Abos vorhanden - past_due in der Kundentabelle rot hervorgehoben - 'Health-Check'-Button: prueft Instanzen, zeigt up/down/unknown Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -392,7 +392,9 @@
|
||||
"<td><b>" + esc(a.email) + "</b>" +
|
||||
(a.company ? '<div class="muted" style="font-size:12px">' + esc(a.company) + "</div>" : "") + "</td>" +
|
||||
"<td>" + (a.plan ? '<span class="admin-badge">' + esc(a.plan) + "</span>" +
|
||||
(a.sub_status && a.sub_status !== "active" ? ' <span class="muted" style="font-size:11px">' + esc(a.sub_status) + "</span>" : "")
|
||||
(a.sub_status && a.sub_status !== "active"
|
||||
? ' <span class="admin-badge ' + (a.sub_status === "past_due" ? "warn" : "") + '" style="font-size:10px">' + esc(a.sub_status) + "</span>"
|
||||
: "")
|
||||
: '<span class="muted">—</span>') + "</td>" +
|
||||
'<td style="text-align:center">' + esc(a.instance_count) + "</td>" +
|
||||
'<td class="muted" style="font-size:12px">' + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</td>" +
|
||||
@@ -409,8 +411,14 @@
|
||||
'<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>' +
|
||||
'<div style="display:flex;gap:14px;align-items:center">' +
|
||||
'<button class="hosting-link" id="ahealth">Health-Check</button>' +
|
||||
'<button class="hosting-link" id="aexport">CSV-Export</button>' +
|
||||
'<button class="hosting-link" id="alogout">Abmelden</button></div></div>' +
|
||||
(stats.pastDueSubscriptions
|
||||
? '<div class="hosting-msg err">⚠ ' + esc(stats.pastDueSubscriptions) +
|
||||
" Abo(s) mit fehlgeschlagener Zahlung (past_due) — Kunden kontaktieren.</div>"
|
||||
: "") +
|
||||
'<div id="healthbar"></div>' +
|
||||
'<div class="admin-stats">' +
|
||||
statCard("Kunden", stats.accounts, "+" + (stats.newAccounts30d || 0) + " (30 T.)") +
|
||||
statCard("Aktive Abos", stats.activeSubscriptions) +
|
||||
@@ -443,6 +451,22 @@
|
||||
URL.revokeObjectURL(a.href);
|
||||
} catch (err) { alert(err.message); }
|
||||
};
|
||||
root.querySelector("#ahealth").onclick = async () => {
|
||||
const bar = root.querySelector("#healthbar");
|
||||
bar.innerHTML = '<div class="hosting-msg">Prüfe Instanzen…</div>';
|
||||
try {
|
||||
const h = await adminApi("GET", "/admin/health");
|
||||
if (h.mock) {
|
||||
bar.innerHTML = '<div class="hosting-msg">Health-Check im Mock-Modus nicht aussagekräftig ' +
|
||||
"(keine echten Instanzen). " + esc(h.instances.length) + " aktive Instanz(en).</div>";
|
||||
} else if (h.down > 0) {
|
||||
bar.innerHTML = '<div class="hosting-msg err">⚠ ' + esc(h.down) + " von " + esc(h.checked) +
|
||||
" Instanzen nicht erreichbar (down).</div>";
|
||||
} else {
|
||||
bar.innerHTML = '<div class="hosting-msg ok">Alle ' + esc(h.checked) + " Instanzen erreichbar.</div>";
|
||||
}
|
||||
} catch (err) { bar.innerHTML = '<div class="hosting-msg err">' + esc(err.message) + "</div>"; }
|
||||
};
|
||||
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(); };
|
||||
|
||||
Reference in New Issue
Block a user