From 2114a54eb6335f97b0c276c4f1f224d45f46fd1a Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 12 Sep 2026 14:21:12 +0200 Subject: feat(hub): the mail state is a panel section, and a ceiling falling is an event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The figure was a line beside the settings form, which is where it is changed and not where it is watched. It sits with the other live figures under Statistics now — four cards and, above them, a banner saying which of the two ceilings has fallen. The two states are not the same to whoever is reading: one means newcomers are turned away, the other means somebody locked out of their account cannot get back in. The settings block keeps a line pointing at it. And an operator no longer has to be looking. When a global ceiling is reached the administrators are notified — in `mail.py`, in its own session, never raising, because this runs while a request is being refused and an alert that fails must not turn a refusal into a 500. Once per hour, keyed on a row rather than a flag in memory: a flood is what spends the budget, so one alert per refusal would bury the message under its own cause, and a hub that is refusing mail is a hub somebody is about to restart. `/v1/admin/mail` gains `general_exhausted` and `all_exhausted` rather than leaving the panel to compare two numbers. Labels in all ten catalogues; `.warn-msg` for the middle state, on the `--warn` token both themes already define. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01T4YmK41VsEURWFdop4EEeT --- .../src/meshbay_hub/static/admin-page.js | 50 ++++++++++++++++------ 1 file changed, 37 insertions(+), 13 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/admin-page.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js b/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js index 3cc42db..8800615 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js @@ -105,7 +105,11 @@ export function AdminPage({ token, role }) { useEffect(() => { setError(''); if (tab === 'general') loadSettings(); - else if (tab === 'stats') loadStats(); + else if (tab === 'stats') { + loadStats(); + hubFetch('/v1/admin/mail', { token }) + .then(setMailStatus).catch(() => { /* the cards still render */ }); + } else if (tab === 'users') loadUsers(userSearch); else if (tab === 'groups') loadGroups(); else if (tab === 'nodes') { @@ -225,18 +229,7 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist

${t('admin.mail_heading')}

${t('admin.mail_hint')}

- ${mailStatus && html` -
- ${t('admin.mail_this_hour')} - ${mailStatus.hourly_used} / ${mailStatus.hourly_budget} -
-

- ${t('admin.mail_remaining', { - general: mailStatus.general_remaining, - recovery: mailStatus.recovery_remaining, - })} -

- `} +

${t('admin.mail_state_is_in_stats')}

${mailDraft && MAIL_FIELDS.map(key => html`
@@ -279,6 +272,37 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist
`)} + + ${mailStatus && html` +
+

${t('admin.mail_heading')}

+ + ${mailStatus.all_exhausted && html` +
${t('admin.mail_alert_all')}
`} + ${!mailStatus.all_exhausted && mailStatus.general_exhausted && html` +
${t('admin.mail_alert_general')}
`} + +
+
+
${mailStatus.hourly_used} / ${mailStatus.hourly_budget}
+
${t('admin.mail_this_hour')}
+
+
+
${mailStatus.general_remaining}
+
${t('admin.mail_left_signups')}
+
+
+
${mailStatus.recovery_remaining}
+
${t('admin.mail_left_recovery')}
+
+
+
${mailStatus.recipients_tracked}
+
${t('admin.mail_recipients')}
+
+
+

${t('admin.mail_state_hint')}

+
+ `} `} ${tab === 'users' && html` -- cgit v1.2.3