diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/admin-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/admin-page.js | 51 |
1 files changed, 44 insertions, 7 deletions
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 8800615..c40d240 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js @@ -16,6 +16,7 @@ export function AdminPage({ token, role }) { // Edited values live here until Save, so a half-typed number is never sent // and a rejected one never looks applied. const [mailDraft, setMailDraft] = useState(null); + const [loginDraft, setLoginDraft] = useState(null); const [users, setUsers] = useState([]); const [usersTotal, setUsersTotal] = useState(0); const [userSearch, setUserSearch] = useState(''); @@ -43,6 +44,7 @@ export function AdminPage({ token, role }) { const data = await hubFetch('/v1/admin/settings', { token }); setSettings(data); setMailDraft({ ...data.mail }); + setLoginDraft({ ...data.login }); } catch (e) { setError(e.message); } try { setMailStatus(await hubFetch('/v1/admin/mail', { token })); @@ -61,6 +63,7 @@ export function AdminPage({ token, role }) { // The hub clamps what it was given, so the draft is reset from the // answer rather than left showing a number that was not stored. setMailDraft({ ...data.mail }); + setLoginDraft({ ...data.login }); if (patch.mail) { try { setMailStatus(await hubFetch('/v1/admin/mail', { token })); @@ -193,6 +196,14 @@ const MAIL_FIELDS = [ 'email_change_cooldown', ]; +const LOGIN_FIELDS = ['max_failures', 'lockout_minutes']; + +// Only what changed, and only what is a number: an empty field is someone +// mid-edit, not a request to set zero. +const changedNumbers = (fields, draft, stored) => Object.fromEntries(fields + .filter(k => draft[k] !== '' && draft[k] !== null && Number(draft[k]) !== stored[k]) + .map(k => [k, Number(draft[k])])); + const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist']; const canEditSettings = role === 'admin'; @@ -247,12 +258,7 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist <div class="settings-row"> <button class="btn" disabled=${settingsSaving} onClick=${() => saveSettings({ - mail: Object.fromEntries(MAIL_FIELDS - // Only what changed, and only what is a number: an empty - // field is someone mid-edit, not a request to set zero. - .filter(k => mailDraft[k] !== '' && mailDraft[k] !== null - && Number(mailDraft[k]) !== settings.mail[k]) - .map(k => [k, Number(mailDraft[k])])), + mail: changedNumbers(MAIL_FIELDS, mailDraft, settings.mail), })}>${t('admin.mail_save')}</button> <button class="btn btn-secondary" disabled=${settingsSaving} onClick=${() => setMailDraft({ ...settings.mail_defaults })} @@ -260,6 +266,37 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist </div> `} </div> + + ${settings.login && html` + <div class="settings-section"> + <h3 class="settings-heading">${t('admin.login_heading')}</h3> + <p class="settings-hint">${t('admin.login_hint')}</p> + + ${loginDraft && LOGIN_FIELDS.map(key => html` + <div class="settings-row" key=${key}> + <span class="settings-label">${t('admin.login_' + key)}</span> + <input type="number" class="settings-number" + min=${(settings.login_bounds?.[key] || [0])[0]} + max=${(settings.login_bounds?.[key] || [0, 0])[1]} + value=${loginDraft[key]} + disabled=${!canEditSettings || settingsSaving} + onInput=${e => setLoginDraft(d => ({ ...d, [key]: e.target.value }))} /> + </div> + `)} + + ${canEditSettings && loginDraft && html` + <div class="settings-row"> + <button class="btn" disabled=${settingsSaving} + onClick=${() => saveSettings({ + login: changedNumbers(LOGIN_FIELDS, loginDraft, settings.login), + })}>${t('admin.login_save')}</button> + <button class="btn btn-secondary" disabled=${settingsSaving} + onClick=${() => setLoginDraft({ ...settings.login_defaults })} + >${t('admin.mail_reset_defaults')}</button> + </div> + `} + </div> + `} `} ${tab === 'stats' && stats && html` @@ -436,7 +473,7 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist loadLogs(e.target.value, 0); }}> <option value="">${t('admin.filter_all')}</option> - ${['login', 'login_fail', 'account_create', 'token_refresh', 'group_create', + ${['login', 'login_fail', 'login_locked', 'account_create', 'token_refresh', 'group_create', 'group_join', 'group_leave', 'node_announce', 'revoke_user', 'revoke_group', 'admin_user_update', 'admin_group_update'].map(ev => html` <option key=${ev} value=${ev}>${ev}</option> |