From 73ad8e4eb566fe682107fa7e50ef624591199e99 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 15 Sep 2026 02:16:39 +0200 Subject: feat(hub): session lifetime is an admin setting, and a browser signs out when idle Browser idle sign-out (media playback counts as activity; not the desktop app), refresh idle window and maximum session length, in hours. Sign-out now revokes on the hub, and the profile has "sign out everywhere". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XuNrwLf5EFWCMHzfoEvnpm --- .../src/meshbay_hub/static/admin-page.js | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) (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 c40d240..d5cc7a8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/admin-page.js @@ -17,6 +17,7 @@ export function AdminPage({ token, role }) { // and a rejected one never looks applied. const [mailDraft, setMailDraft] = useState(null); const [loginDraft, setLoginDraft] = useState(null); + const [sessionDraft, setSessionDraft] = useState(null); const [users, setUsers] = useState([]); const [usersTotal, setUsersTotal] = useState(0); const [userSearch, setUserSearch] = useState(''); @@ -45,6 +46,7 @@ export function AdminPage({ token, role }) { setSettings(data); setMailDraft({ ...data.mail }); setLoginDraft({ ...data.login }); + setSessionDraft({ ...data.session }); } catch (e) { setError(e.message); } try { setMailStatus(await hubFetch('/v1/admin/mail', { token })); @@ -64,6 +66,7 @@ export function AdminPage({ token, role }) { // answer rather than left showing a number that was not stored. setMailDraft({ ...data.mail }); setLoginDraft({ ...data.login }); + setSessionDraft({ ...data.session }); if (patch.mail) { try { setMailStatus(await hubFetch('/v1/admin/mail', { token })); @@ -198,6 +201,8 @@ const MAIL_FIELDS = [ const LOGIN_FIELDS = ['max_failures', 'lockout_minutes']; +const SESSION_FIELDS = ['browser_idle_hours', 'refresh_idle_hours', 'max_hours']; + // 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 @@ -296,6 +301,35 @@ const TABS = ['general', 'stats', 'users', 'groups', 'nodes', 'logs', 'blocklist `} + +
+

${t('admin.session_heading')}

+

${t('admin.session_hint')}

+ + ${sessionDraft && SESSION_FIELDS.map(key => html` +
+ ${t('admin.session_' + key)} + setSessionDraft(d => ({ ...d, [key]: e.target.value }))} /> +
+ `)} + + ${canEditSettings && sessionDraft && html` +
+ + +
+ `} +
`} `} -- cgit v1.2.3