Settings is grouped into Access (users, sign-in, iOS app and API tokens),
Web interface (address and HTTPS), Logs & history and Upkeep
(updates, backup). Session length moved to Sign-in and no longer asks for
a restart. Log level, log size and traffic history share one card; the
country lookup is its own switch. Each card says how it saves.
The log viewer moved to a new Log page in the sidebar, with a filter for
changes only, and the Dashboard's Log link opens it.
constlevelSel=h('select',{id:'lv'},[['debug','Debug: everything'],['info','Info'],['warn','Warnings and errors'],['error','Errors only']].map(([l,t])=>h('option',{value:l,selected:s.log.level===l},t)));
constgeoStatus=s.geo&&s.geo.updated?'Database from '+fmtDate(s.geo.updated)+'.':'Not downloaded yet.';
constdiskHint=h('span',{class:'hint'});
constdrawDiskHint=()=>{
@@ -2180,10 +2203,10 @@
if(shrinks&&!awaitconfirmDialog({title:'Delete older data?',text:'The new limits are lower: older log files and traffic history beyond them are deleted. This cannot be undone.',ok:'Save and delete',danger:true}))return;
h('div',null,h('h1',null,'Settings'),h('p',{class:'sub'},'Users, web interface, API access for the iOS app, logs, data retention, backups and updates')),
h('div',null,h('h1',null,'Settings'),h('p',{class:'sub'},'Who can sign in, the web interface, logs and history, updates and backups')),
restartBox,
groupHead('g-access','Access','Who can sign in, and how.'),
h('div',null,h('h2',{id:'usr'},'Users'),h('p',{class:'lead',style:{marginBottom:'0'}},'Everyone here is an admin. You cannot delete yourself, so one user always remains.')),
h('div',null,h('h3',{id:'usr'},'Users'),h('p',{class:'lead',style:{marginBottom:'0'}},'Everyone here is an admin. You cannot delete yourself, so one user always remains.')),
h('p',{class:'lead'},'Everyone sets up two-step sign-in under My account: an authenticator app or passkeys, including on a YubiKey. Changes apply immediately.'),
h('div',{class:'cardhead'},h('h3',{id:'sgn'},'Sign-in'),h('span',{class:'saves'},'Saves right away')),
h('p',{class:'lead'},'Everyone sets up two-step sign-in under My account: an authenticator app or passkeys, including on a YubiKey.'),
h('label',{class:'check'},requireBox,h('span',null,'Require two-step sign-in for everyone',h('br'),
h('span',{class:'hint'},'Users without it are asked to set it up right after their password. To help someone who lost their phone or key, use Edit → Reset two-step sign-in.')))),
h('span',{class:'hint'},'Users without it are asked to set it up right after their password. To help someone who lost their phone or key, use Edit → Reset two-step sign-in.'))),
h('div',{class:'grid section'},
h('div',{class:'field'},h('label',{htmlFor:'st'},'Stay signed in for'),sessSel,h('span',{class:'hint'},'Applies to new sign-ins')),
h('div',null,h('h3',{id:'api'},'iOS app and API tokens'),h('p',{class:'lead',style:{marginBottom:'0'}},'For the iOS app and scripts. A token appears once when you create it, and only a hash is stored.')),
h('div',{class:'field'},h('label',{htmlFor:'hl'},'HTTP listen address'),h('input',{id:'hl',class:'mono',value:web.httpListen,placeholder:'off',onInput:(e)=>{web.httpListen=e.target.value.trim();}}),h('span',{class:'hint'},'Redirects to HTTPS and answers Let\'s Encrypt http-01 checks. Empty turns it off')),
s.fingerprint?h('div',{class:'field'},h('label',{htmlFor:'fp'},'Certificate fingerprint (SHA-256)'),h('input',{id:'fp',class:'mono',value:s.fingerprint,readOnly:true}),h('span',{class:'hint'},'The iOS app pins this when pairing')):null),
h('p',{class:'lead'},'Shows an ordinary web server page instead of this interface. The iOS app and setup links keep working. Changes apply immediately.'),
h('div',{class:'cardhead'},h('h3',{id:'dcy'},'Decoy'),h('span',{class:'saves'},'Saves right away')),
h('p',{class:'lead'},'Shows an ordinary web server page instead of this interface. The iOS app and setup links keep working.'),
h('div',null,h('h2',{id:'api'},'API tokens'),h('p',{class:'lead',style:{marginBottom:'0'}},'For the iOS app and scripts. A token appears once when you create it, and only a hash is stored.')),
h('div',{class:'field'},h('label',{htmlFor:'rs'},'Log file size (MB)'),logSize,h('span',{class:'hint'},'The log starts a new file at this size. 1–1000')),
h('div',{class:'field'},h('label',{htmlFor:'rh'},'Hourly traffic history'),hourly,h('span',{class:'hint'},'Used by the 24-hour charts')),
h('div',{class:'field'},h('label',{htmlFor:'rd'},'Daily traffic history'),daily,h('span',{class:'hint'},'Used by the 7- and 30-day charts and the connection history. All-time totals are always kept'))),
h('label',{class:'check section'},geo,h('span',null,'Show country and network of peer addresses',h('br'),
h('span',{class:'hint'},'Downloads the free DB-IP Lite databases (about 20 MB) once a month and looks addresses up on this server only. '+geoStatus))),
h('div',{class:'field'},h('label',{htmlFor:'rd'},'Daily traffic history'),daily,h('span',{class:'hint'},'Used by the 7- and 30-day charts and the connection history')),
h('div',{class:'cardhead'},h('h3',{id:'geo-h'},'Country and network lookup'),h('span',{class:'saves'},'Saves right away')),
h('label',{class:'check'},geoBox,h('span',null,'Show country and network of peer addresses',h('br'),
h('span',{class:'hint'},'Downloads the free DB-IP Lite databases (about 20 MB) once a month and looks addresses up on this server only. '+geoStatus)))),
groupHead('g-upkeep','Upkeep','New versions and copies of your settings.'),
h('p',{class:'lead'},'A backup is a copy of config.json with server key, peers, tokens and settings. Keep it safe: it contains the server\'s private key.'),
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.