From 1b5ea9a1fa5c512b6bb24caa8fa9f5d596cb5246 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Thu, 7 May 2026 22:57:45 +0300 Subject: [PATCH] silenced the superuser ips confirmation if there is no change --- CHANGELOG.md | 5 +++++ ui/dist/assets/{index-5KAeUg7B.js => index-fOxZ9qpE.js} | 2 +- ui/dist/index.html | 2 +- ui/src/settings/application/pageApplicationSettings.js | 7 ++++++- 4 files changed, 13 insertions(+), 3 deletions(-) rename ui/dist/assets/{index-5KAeUg7B.js => index-fOxZ9qpE.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e9c4c9..245e09e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.38.1 (WIP) + +- Silenced the superuser IPs confirmation if there is no change. + + ## v0.38.0 - Fixed UI logs pagination when no custom range is specified. diff --git a/ui/dist/assets/index-5KAeUg7B.js b/ui/dist/assets/index-fOxZ9qpE.js similarity index 99% rename from ui/dist/assets/index-5KAeUg7B.js rename to ui/dist/assets/index-fOxZ9qpE.js index 904d770a..0536ceed 100644 --- a/ui/dist/assets/index-5KAeUg7B.js +++ b/ui/dist/assets/index-fOxZ9qpE.js @@ -72,7 +72,7 @@ It is recommend to list it as trusted.`,`right`)});if(i.isEnabled&&i.possiblePro In this case to retrieve the actual user IP (used for rate limiting, logging, etc.) you need to properly configure your proxy and list below the trusted headers that PocketBase could use to extract the user IP. - `),t.p({className:`txt-bold`},`When using such proxy, to avoid spoofing it is recommended to:`),t.ul({className:`txt-bold`},t.li(null,`use headers that are controlled only by the proxy and cannot be manually set by the users`),t.li(null,`make sure that the PocketBase server can be accessed ONLY through the proxy`)),t.p(null,`You can clear the headers field if PocketBase is not deployed behind a proxy.`)),t.div({className:`grid sm`},t.div({className:`col-lg-9`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`trustedProxy.headers`},`Trusted IP proxy headers`),t.input({type:`text`,id:`trustedProxy.headers`,name:`trustedProxy.headers`,placeholder:`Leave empty to disable`,value:()=>app.utils.joinNonEmpty(e.formSettings.trustedProxy.headers),oninput:n=>{let r=app.utils.splitNonEmpty(n.target.value,`,`),i=app.utils.joinNonEmpty(r);app.utils.joinNonEmpty(e.formSettings.trustedProxy.headers)!=i&&(e.formSettings.trustedProxy.headers=r)}})),t.div({className:`field addon`},t.button({type:`button`,className:()=>`btn sm secondary transparent ${app.utils.isEmpty(e.formSettings.trustedProxy.headers)?`hidden`:``}`,onclick:()=>{e.formSettings.trustedProxy.headers=[]}},t.span({className:`txt`},`Clear`)))),t.div({className:`field-help`},`Comma separated list of headers such as: `,t.div({className:`inline-flex gap-5`},()=>i.suggestedProxyHeaders.map(n=>t.div({role:`button`,className:`label sm link-primary`,onclick:()=>{e.formSettings.trustedProxy.headers=[n]},textContent:n}))))),t.div({className:`col-lg-3`},t.div({className:`field`},t.label({htmlFor:`trustedProxy.useLeftmostIP`},t.span({className:`txt`},`IP priority`),t.i({className:`ri-information-line tooltip-right`,ariaDescription:app.attrs.tooltip(`This is in case the proxy returns more than 1 IP as header value. The rightmost IP is usually considered to be the more trustworthy but this could vary depending on the proxy.`)})),app.components.select({id:`trustedProxy.useLeftmostIP`,name:`trustedProxy.useLeftmostIP`,options:r,required:!0,value:()=>e.formSettings.trustedProxy.useLeftmostIP||!1,onchange:n=>{e.formSettings.trustedProxy.useLeftmostIP=n?.[0]?.value}})))))}function Zr(){app.store.title=`Application settings`;let e=store({isLoading:!1,isSaving:!1,formSettings:null,originalFormSettings:null,get originalFormSettingsHash(){return JSON.stringify(e.originalFormSettings)},get formSettingsHash(){return JSON.stringify(e.formSettings)},get hasChanges(){return e.originalFormSettingsHash!=e.formSettingsHash}});n();async function n(){e.isLoading=!0;try{o(await app.pb.settings.getAll()),e.isLoading=!1}catch(e){e.isAbort||app.checkApiError(e)}}function r(){return JSON.stringify(e.formSettings?.superuserIPs)!=JSON.stringify(e.originalFormSettings?.superuserIPs)}async function i(){let n=app.utils.toArray(e.formSettings?.superuserIPs);return n.length?app.modals.confirm(t.div({className:`txt-center`},t.h6(null,`The ONLY allowed superuser IPs will change to: `,t.br(),t.strong(null,n.join(`, `))),t.p(null,`Please make sure that your IP is in the list or you'll be locked.`),t.p({className:`txt-hint`},`In case of lockout, you can reset the setting with the `,t.a({href:`https://pocketbase.io/docs/going-to-production/#limit-superusers-to-specific-ipssubnets`,target:`_blank`,rel:`noopener noreferrer`,className:`link-primary txt-bold txt-sm`},t.code(null,`superuser ips`,t.i({ariaHidden:!0,className:`ri-arrow-right-up-line txt-sm`}))),` console command.`)),()=>a(),null,{yesButton:`Yes, save changes`}):a()}async function a(){if(!(e.isSaving||!e.hasChanges)){e.isSaving=!0,e.formSettings.rateLimits.rules=qr(e.formSettings.rateLimits.rules);try{let n=app.utils.filterRedactedProps(e.formSettings),i=await app.pb.settings.update(n);if(r())try{await app.pb.collection(`_superusers`).authRefresh()}catch{app.pb.authStore.clear()}o(i),app.toasts.success(`Successfully saved application settings.`)}catch(e){app.checkApiError(e)}e.isSaving=!1}}function o(n={}){if(app.store.settings=JSON.parse(JSON.stringify(n)),!n.meta?.accentColor){let e=window.getComputedStyle(document.documentElement)?.getPropertyValue(`--accentColor`);e?.startsWith(`#`)&&(n.meta=n.meta||{},n.meta.accentColor=e.toLowerCase()||``)}e.originalFormSettings={superuserIPs:n.superuserIPs||[],meta:n.meta||{},batch:n.batch||{},trustedProxy:n.trustedProxy||{headers:[]},rateLimits:n.rateLimits||{excludedIPs:[],rules:[]}},qr(e.originalFormSettings.rateLimits.rules),e.formSettings=JSON.parse(JSON.stringify(e.originalFormSettings))}function s(){e.formSettings=JSON.parse(e.originalFormSettingsHash)}return t.div({pbEvent:`pageApplicationSettings`,className:`page page-application-settings`},$(),t.div({className:`page-content full-height`},t.header({className:`page-header`},t.nav({className:`breadcrumbs`},t.div({className:`breadcrumb-item`},`Settings`),t.div({className:`breadcrumb-item`},`Application`))),t.div({className:`wrapper m-b-base`},()=>e.isLoading?t.div({className:`block txt-center`},t.span({className:`loader lg`})):t.form({pbEvent:`applicationSettingsForm`,className:`grid application-settings-form`,inert:()=>e.isSaving,onsubmit:e=>{e.preventDefault(),i()}},t.div({className:`col-md-5`},t.div({className:`field`},t.label({htmlFor:`meta.appName`},`Application name`),t.input({id:`meta.appName`,name:`meta.appName`,type:`text`,required:!0,value:()=>e.formSettings.meta.appName||``,oninput:n=>e.formSettings.meta.appName=n.target.value}))),t.div({className:`col-md-5`},t.div({className:`field`},t.label({htmlFor:`meta.appURL`},`Application URL`),t.input({id:`meta.appURL`,name:`meta.appURL`,type:`text`,required:!0,value:()=>e.formSettings.meta.appURL||``,oninput:n=>e.formSettings.meta.appURL=n.target.value}))),t.div({className:`col-md-2`},()=>Qr(e,e.isSaving)),t.div({className:`col-lg-12`},()=>Ur(e),()=>Xr(e),()=>Jr(e),()=>Yr(e)),t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`meta.hideControls`,name:`meta.hideControls`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.meta.hideControls,onchange:n=>e.formSettings.meta.hideControls=n.target.checked}),t.label({htmlFor:`meta.hideControls`},t.span({className:`txt`},`Hide/Lock collection and record controls`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`To prevent accidental changes when in production environment, collections create and update buttons will be hidden. + `),t.p({className:`txt-bold`},`When using such proxy, to avoid spoofing it is recommended to:`),t.ul({className:`txt-bold`},t.li(null,`use headers that are controlled only by the proxy and cannot be manually set by the users`),t.li(null,`make sure that the PocketBase server can be accessed ONLY through the proxy`)),t.p(null,`You can clear the headers field if PocketBase is not deployed behind a proxy.`)),t.div({className:`grid sm`},t.div({className:`col-lg-9`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`trustedProxy.headers`},`Trusted IP proxy headers`),t.input({type:`text`,id:`trustedProxy.headers`,name:`trustedProxy.headers`,placeholder:`Leave empty to disable`,value:()=>app.utils.joinNonEmpty(e.formSettings.trustedProxy.headers),oninput:n=>{let r=app.utils.splitNonEmpty(n.target.value,`,`),i=app.utils.joinNonEmpty(r);app.utils.joinNonEmpty(e.formSettings.trustedProxy.headers)!=i&&(e.formSettings.trustedProxy.headers=r)}})),t.div({className:`field addon`},t.button({type:`button`,className:()=>`btn sm secondary transparent ${app.utils.isEmpty(e.formSettings.trustedProxy.headers)?`hidden`:``}`,onclick:()=>{e.formSettings.trustedProxy.headers=[]}},t.span({className:`txt`},`Clear`)))),t.div({className:`field-help`},`Comma separated list of headers such as: `,t.div({className:`inline-flex gap-5`},()=>i.suggestedProxyHeaders.map(n=>t.div({role:`button`,className:`label sm link-primary`,onclick:()=>{e.formSettings.trustedProxy.headers=[n]},textContent:n}))))),t.div({className:`col-lg-3`},t.div({className:`field`},t.label({htmlFor:`trustedProxy.useLeftmostIP`},t.span({className:`txt`},`IP priority`),t.i({className:`ri-information-line tooltip-right`,ariaDescription:app.attrs.tooltip(`This is in case the proxy returns more than 1 IP as header value. The rightmost IP is usually considered to be the more trustworthy but this could vary depending on the proxy.`)})),app.components.select({id:`trustedProxy.useLeftmostIP`,name:`trustedProxy.useLeftmostIP`,options:r,required:!0,value:()=>e.formSettings.trustedProxy.useLeftmostIP||!1,onchange:n=>{e.formSettings.trustedProxy.useLeftmostIP=n?.[0]?.value}})))))}function Zr(){app.store.title=`Application settings`;let e=store({isLoading:!1,isSaving:!1,formSettings:null,originalFormSettings:null,get originalFormSettingsHash(){return JSON.stringify(e.originalFormSettings)},get formSettingsHash(){return JSON.stringify(e.formSettings)},get hasChanges(){return e.originalFormSettingsHash!=e.formSettingsHash}});n();async function n(){e.isLoading=!0;try{o(await app.pb.settings.getAll()),e.isLoading=!1}catch(e){e.isAbort||app.checkApiError(e)}}function r(){return JSON.stringify(e.formSettings?.superuserIPs)!=JSON.stringify(e.originalFormSettings?.superuserIPs)}async function i(){let n=app.utils.toArray(e.originalFormSettings?.superuserIPs),r=app.utils.toArray(e.formSettings?.superuserIPs);return!r.length||JSON.stringify(n)==JSON.stringify(r)?a():app.modals.confirm(t.div({className:`txt-center`},t.h6(null,`The ONLY allowed superuser IPs will change to: `,t.br(),t.strong(null,r.join(`, `))),t.p(null,`Please make sure that your IP is in the list or you'll be locked.`),t.p({className:`txt-hint`},`In case of lockout, you can reset the setting with the `,t.a({href:`https://pocketbase.io/docs/going-to-production/#limit-superusers-to-specific-ipssubnets`,target:`_blank`,rel:`noopener noreferrer`,className:`link-primary txt-bold txt-sm`},t.code(null,`superuser ips`,t.i({ariaHidden:!0,className:`ri-arrow-right-up-line txt-sm`}))),` console command.`)),()=>a(),null,{yesButton:`Yes, save changes`})}async function a(){if(!(e.isSaving||!e.hasChanges)){e.isSaving=!0,e.formSettings.rateLimits.rules=qr(e.formSettings.rateLimits.rules);try{let n=app.utils.filterRedactedProps(e.formSettings),i=await app.pb.settings.update(n);if(r())try{await app.pb.collection(`_superusers`).authRefresh()}catch{app.pb.authStore.clear()}o(i),app.toasts.success(`Successfully saved application settings.`)}catch(e){app.checkApiError(e)}e.isSaving=!1}}function o(n={}){if(app.store.settings=JSON.parse(JSON.stringify(n)),!n.meta?.accentColor){let e=window.getComputedStyle(document.documentElement)?.getPropertyValue(`--accentColor`);e?.startsWith(`#`)&&(n.meta=n.meta||{},n.meta.accentColor=e.toLowerCase()||``)}e.originalFormSettings={superuserIPs:n.superuserIPs||[],meta:n.meta||{},batch:n.batch||{},trustedProxy:n.trustedProxy||{headers:[]},rateLimits:n.rateLimits||{excludedIPs:[],rules:[]}},qr(e.originalFormSettings.rateLimits.rules),e.formSettings=JSON.parse(JSON.stringify(e.originalFormSettings))}function s(){e.formSettings=JSON.parse(e.originalFormSettingsHash)}return t.div({pbEvent:`pageApplicationSettings`,className:`page page-application-settings`},$(),t.div({className:`page-content full-height`},t.header({className:`page-header`},t.nav({className:`breadcrumbs`},t.div({className:`breadcrumb-item`},`Settings`),t.div({className:`breadcrumb-item`},`Application`))),t.div({className:`wrapper m-b-base`},()=>e.isLoading?t.div({className:`block txt-center`},t.span({className:`loader lg`})):t.form({pbEvent:`applicationSettingsForm`,className:`grid application-settings-form`,inert:()=>e.isSaving,onsubmit:e=>{e.preventDefault(),i()}},t.div({className:`col-md-5`},t.div({className:`field`},t.label({htmlFor:`meta.appName`},`Application name`),t.input({id:`meta.appName`,name:`meta.appName`,type:`text`,required:!0,value:()=>e.formSettings.meta.appName||``,oninput:n=>e.formSettings.meta.appName=n.target.value}))),t.div({className:`col-md-5`},t.div({className:`field`},t.label({htmlFor:`meta.appURL`},`Application URL`),t.input({id:`meta.appURL`,name:`meta.appURL`,type:`text`,required:!0,value:()=>e.formSettings.meta.appURL||``,oninput:n=>e.formSettings.meta.appURL=n.target.value}))),t.div({className:`col-md-2`},()=>Qr(e,e.isSaving)),t.div({className:`col-lg-12`},()=>Ur(e),()=>Xr(e),()=>Jr(e),()=>Yr(e)),t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`meta.hideControls`,name:`meta.hideControls`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.meta.hideControls,onchange:n=>e.formSettings.meta.hideControls=n.target.checked}),t.label({htmlFor:`meta.hideControls`},t.span({className:`txt`},`Hide/Lock collection and record controls`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`To prevent accidental changes when in production environment, collections create and update buttons will be hidden. Records update will also require an extra unlock step before save.`)})))),t.div({className:`col-lg-12`},t.hr()),t.div({className:`col-lg-12`},t.div({className:`flex`},t.div({className:`m-r-auto`}),t.button({type:`button`,className:`btn transparent secondary`,disabled:()=>e.isSaving,hidden:()=>!e.hasChanges,onclick:s},t.span({className:`txt`},`Cancel`)),t.button({className:()=>`btn expanded-lg ${e.isSaving?`loading`:``}`,disabled:()=>!e.hasChanges||e.isSaving},t.span({className:`txt`},`Save changes`)))))),t.footer({className:`page-footer`},app.components.credits())))}function Qr(e){let n=`accent_`+app.utils.randomString(),r=store({isTooLight:!1}),i,a;function o(e){clearTimeout(a),document.documentElement.style.setProperty(`--animationSpeed`,`0`),e?document.documentElement.style.setProperty(`--accentColor`,e.toLowerCase()):document.documentElement.style.removeProperty(`--accentColor`),a=setTimeout(()=>{document.documentElement.style.removeProperty(`--animationSpeed`)},100)}let s=[watch(()=>e.formSettings?.meta?.accentColor,e=>{clearTimeout(i),i=setTimeout(()=>{o(e)},100)})];return t.div({className:`field`,ariaDescription:app.attrs.tooltip(()=>r.isTooLight?`Invalid - color is too light`:``),onunmount:()=>{clearTimeout(i),o(e.formSettings.meta.accentColor),s.forEach(e=>e?.unwatch())}},t.label({htmlFor:n},t.span({className:`txt`},`Accent`),t.i({hidden:()=>!r.isTooLight,className:`txt-warning ri-alert-line`})),app.components.colorPicker({id:n,name:`meta.accentColor`,predefinedColors:()=>app.store.predefinedAccentColors,value:()=>e.formSettings.meta.accentColor,onchange:n=>{if(r.isTooLight=!1,!app.utils.isDarkEnoughForWhiteText(n)){r.isTooLight=!0;return}e.formSettings.meta.accentColor=n}}))}function $r(e={}){let n=store({onsave:null}),r=app.utils.extendStore(n,e),i=[{cron:`0 0 * * *`,label:`Every day at 00:00h`},{cron:`0 0 * * 0`,label:`Every sunday at 00:00h`},{cron:`0 0 * * 1,3`,label:`Every Mon and Wed at 00:00h`},{cron:`0 0 1 * *`,label:`Every first day of the month at 00:00h`}],a=store({showForm:!1,isLoading:!1,isSaving:!1,formSettings:null,initSerialized:`null`,enableAutoBackups:!1,get hasChanges(){return a.initSerialized!=JSON.stringify(a.formSettings)}});async function o(){a.isLoading=!0;try{c(await app.pb.settings.getAll()),a.isLoading=!1}catch(e){e.isAbort||app.checkApiError(e)}}async function s(){if(!(a.isSaving||!a.hasChanges)){a.isSaving=!0;try{let e=app.utils.filterRedactedProps(a.formSettings),r=await app.pb.settings.update(e);n.onsave?.(r),c(r),app.toasts.success(`Successfully saved backups settings.`)}catch(e){app.checkApiError(e)}a.isSaving=!1}}function c(e={}){app.store.settings=JSON.parse(JSON.stringify(e)),a.formSettings={backups:e?.backups||{}},a.enableAutoBackups=!!a.formSettings.backups.cron,a.initSerialized=JSON.stringify(a.formSettings)}function l(){a.formSettings=JSON.parse(a.initSerialized),a.enableAutoBackups=!!a.formSettings.backups.cron}return r.push(watch(()=>{!a.enableAutoBackups&&a.formSettings?.backups?.cron&&(a.formSettings.backups.cron=``)})),t.div({className:`block backups-settings-form-wrapper`,onmount:()=>{o()},onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.button({type:`button`,className:()=>`btn secondary ${a.isLoading?`loading`:``}`,disabled:()=>a.isLoading||a.hasChanges,onclick:()=>a.showForm=!a.showForm},t.span({className:`txt`},`Backup options`),t.i({className:()=>a.showForm?`ri-arrow-up-s-line`:`ri-arrow-down-s-line`,ariaHidden:!0})),app.components.slide(()=>a.showForm,t.form({pbEvent:`backupsSettingsForm`,className:`grid backups-settings-form m-t-base`,inert:()=>a.isSaving,onsubmit:e=>{e.preventDefault(),s()}},()=>a.isLoading?t.div({className:`col-lg-12 txt-center`},t.span({className:`loader lg`})):[t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`enableAutoBackupsToggle`,type:`checkbox`,className:`switch`,checked:()=>a.enableAutoBackups,onchange:e=>{a.enableAutoBackups=e.target.checked,a.formSettings.backups.cron||(a.formSettings.backups.cron=i[0].cron)}}),t.label({htmlFor:`enableAutoBackupsToggle`},`Enable auto backups`)),app.components.slide(()=>a.enableAutoBackups,t.div({className:`grid m-t-base m-b-base`},t.div({className:`col-lg-6`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`backups.cron`},`Cron expression`),t.input({id:`backups.cron`,name:`backups.cron`,className:`txt-code`,type:`text`,placeholder:`e.g. 0 0 * * *`,required:()=>a.enableAutoBackups,value:()=>a.formSettings.backups.cron,oninput:e=>a.formSettings.backups.cron=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,className:`btn outline sm`,"html-popovertarget":`cron-presets-dropdown`},t.span({className:`txt`},`Presets`),t.i({className:`ri-arrow-drop-down-line`,ariaHidden:!0})),t.div({id:`cron-presets-dropdown`,className:`dropdown sm txt-nowrap`,popover:`auto`},()=>i.map(e=>t.button({type:`button`,className:()=>`dropdown-item ${a.formSettings.backups.cron==e.cron?`active`:``}`,textContent:e.label,onclick:n=>{a.formSettings.backups.cron=e.cron,n.target.closest(`.dropdown`).hidePopover()}}))))),t.div({className:`field-help`},`Supports numeric list, steps, ranges or `,t.strong({className:`link-hint tooltip-bottom`,ariaDescription:app.attrs.tooltip(`@yearly @annually @monthly diff --git a/ui/dist/index.html b/ui/dist/index.html index b5947572..bf67d67f 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.html @@ -13,7 +13,7 @@ - + diff --git a/ui/src/settings/application/pageApplicationSettings.js b/ui/src/settings/application/pageApplicationSettings.js index 18dd9b2f..81b13f36 100644 --- a/ui/src/settings/application/pageApplicationSettings.js +++ b/ui/src/settings/application/pageApplicationSettings.js @@ -47,9 +47,14 @@ export function pageApplicationSettings() { } async function saveWithConfirm() { + const oldSuperuserIPs = app.utils.toArray(data.originalFormSettings?.superuserIPs); const superuserIPs = app.utils.toArray(data.formSettings?.superuserIPs); - if (!superuserIPs.length) { + if ( + !superuserIPs.length + // no change + || JSON.stringify(oldSuperuserIPs) == JSON.stringify(superuserIPs) + ) { return save(); }