From b79409a252489d1dfde7b3ef182e3db98b54227a Mon Sep 17 00:00:00 2001 From: karim Date: Sun, 31 May 2026 12:47:24 +0200 Subject: [PATCH] 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 --- public/js/hosting-app.js | 26 +++++++++++++++++++++++++- static/js/hosting-app.js | 26 +++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/public/js/hosting-app.js b/public/js/hosting-app.js index 8a76a1b..e78599e 100644 --- a/public/js/hosting-app.js +++ b/public/js/hosting-app.js @@ -392,7 +392,9 @@ "" + esc(a.email) + "" + (a.company ? '
' + esc(a.company) + "
" : "") + "" + "" + (a.plan ? '' + esc(a.plan) + "" + - (a.sub_status && a.sub_status !== "active" ? ' ' + esc(a.sub_status) + "" : "") + (a.sub_status && a.sub_status !== "active" + ? ' ' + esc(a.sub_status) + "" + : "") : '') + "" + '' + esc(a.instance_count) + "" + '' + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "" + @@ -409,8 +411,14 @@ '
' + '
Cockpit
' + '
' + + '' + '' + '
' + + (stats.pastDueSubscriptions + ? '
⚠ ' + esc(stats.pastDueSubscriptions) + + " Abo(s) mit fehlgeschlagener Zahlung (past_due) — Kunden kontaktieren.
" + : "") + + '
' + '
' + 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 = '
Prüfe Instanzen…
'; + try { + const h = await adminApi("GET", "/admin/health"); + if (h.mock) { + bar.innerHTML = '
Health-Check im Mock-Modus nicht aussagekräftig ' + + "(keine echten Instanzen). " + esc(h.instances.length) + " aktive Instanz(en).
"; + } else if (h.down > 0) { + bar.innerHTML = '
⚠ ' + esc(h.down) + " von " + esc(h.checked) + + " Instanzen nicht erreichbar (down).
"; + } else { + bar.innerHTML = '
Alle ' + esc(h.checked) + " Instanzen erreichbar.
"; + } + } catch (err) { bar.innerHTML = '
' + esc(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 8a76a1b..e78599e 100644 --- a/static/js/hosting-app.js +++ b/static/js/hosting-app.js @@ -392,7 +392,9 @@ "" + esc(a.email) + "" + (a.company ? '
' + esc(a.company) + "
" : "") + "" + "" + (a.plan ? '' + esc(a.plan) + "" + - (a.sub_status && a.sub_status !== "active" ? ' ' + esc(a.sub_status) + "" : "") + (a.sub_status && a.sub_status !== "active" + ? ' ' + esc(a.sub_status) + "" + : "") : '') + "" + '' + esc(a.instance_count) + "" + '' + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "" + @@ -409,8 +411,14 @@ '
' + '
Cockpit
' + '
' + + '' + '' + '
' + + (stats.pastDueSubscriptions + ? '
⚠ ' + esc(stats.pastDueSubscriptions) + + " Abo(s) mit fehlgeschlagener Zahlung (past_due) — Kunden kontaktieren.
" + : "") + + '
' + '
' + 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 = '
Prüfe Instanzen…
'; + try { + const h = await adminApi("GET", "/admin/health"); + if (h.mock) { + bar.innerHTML = '
Health-Check im Mock-Modus nicht aussagekräftig ' + + "(keine echten Instanzen). " + esc(h.instances.length) + " aktive Instanz(en).
"; + } else if (h.down > 0) { + bar.innerHTML = '
⚠ ' + esc(h.down) + " von " + esc(h.checked) + + " Instanzen nicht erreichbar (down).
"; + } else { + bar.innerHTML = '
Alle ' + esc(h.checked) + " Instanzen erreichbar.
"; + } + } catch (err) { bar.innerHTML = '
' + esc(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(); };