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