From 8d0881db3fed340557c4a553df26def7891e19ed Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Wed, 29 Apr 2026 11:06:26 +0300 Subject: [PATCH] reload trusted proxy info UI after settings save --- CHANGELOG.md | 2 ++ .../{index-CDUQtXaP.js => index-lWorQJPS.js} | 6 ++--- ui/dist/index.html | 2 +- .../application/trustedProxyAccordion.js | 27 +++++++++++-------- 4 files changed, 22 insertions(+), 15 deletions(-) rename ui/dist/assets/{index-CDUQtXaP.js => index-lWorQJPS.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 703c68b1..9d52ba71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Added the local time zone name next to the `date` field label. +- Reload trusted proxy info UI after settings save. + ## v0.37.4 diff --git a/ui/dist/assets/index-CDUQtXaP.js b/ui/dist/assets/index-lWorQJPS.js similarity index 99% rename from ui/dist/assets/index-CDUQtXaP.js rename to ui/dist/assets/index-lWorQJPS.js index 5c440ad2..be0da6a5 100644 --- a/ui/dist/assets/index-CDUQtXaP.js +++ b/ui/dist/assets/index-lWorQJPS.js @@ -62,8 +62,8 @@ To target the newly submitted ones you can use @request.body.*.`)})],name:`updat {MMM} {DD}`,null,` {MMM} {DD}`,null,null,null,1],[60,`{h}:{mm}{aa}`,` {MMM} {DD}`,null,` -{MMM} {DD}`,null,null,null,1]],grid:{show:!0,stroke:o,width:1},ticks:{show:!0,stroke:o,width:1,size:5}},{show:!0,stroke:a,grid:{show:!0,stroke:o,width:1},ticks:{show:!0,stroke:o,width:1,size:5}}],plugins:[Rr(),Ir(r),Lr(r)]};e._uplot?.destroy(),e._uplot=new uPlot(l,n,e)}function Ir(e){let n;return{hooks:{init:r=>{r.over.ondblclick=n=>{e.zoom={}},n=watch(()=>{!e.zoom?.min||!e.zoom?.max?Pr(r):r.setScale(`x`,{min:e.zoom.min,max:e.zoom.max})})},destroy:e=>{n?.unwatch()},setSelect:n=>{n.select.width>0&&(e.zoom={min:n.posToVal(n.select.left,`x`),max:n.posToVal(n.select.left+n.select.width,`x`)})}}}}function Lr(e){let n;return{hooks:{init:r=>{let i=r.root.querySelectorAll(`.u-axis`);if(!i.length){console.warn(`xPanPlugin requires x axis to be defined`);return}i[0].addEventListener(`mousedown`,i=>{if(!e.zoom?.min)return;let a=i.clientX,{min:o,max:s}=r.scales.x,c=r.bbox.width,l=(s-o)/(c/uPlot.pxRatio),u=i=>{let c=(a-i.clientX)*l;r.setScale(`x`,{min:o+c,max:s+c}),clearTimeout(n),n=setTimeout(()=>{r?.scales?.x&&(e.zoom={min:r.scales.x.min,max:r.scales.x.max})},100)},d=e=>{document.removeEventListener(`mousemove`,u),document.removeEventListener(`mouseup`,d)};document.addEventListener(`mousemove`,u),document.addEventListener(`mouseup`,d)})},destroy:e=>{n&&clearTimeout(n)}}}}function Rr(e){let n;return{hooks:{init:e=>{let r=e.over;n=store({date:``,total:0,left:0,top:0,show:!1});let i=t.div({className:()=>`chart-tooltip ${n.show?``:`hidden`}`,onmount(e){e._positionWatcher?.unwatch(),e._positionWatcher=watch(()=>[n.left,n.top],()=>{if(!e)return;let i=e.getBoundingClientRect(),a=n.left;a<0?a=0:a+i.width>r.clientWidth&&(a=r.clientWidth-i.width),e.style.left=a+`px`;let o=n.top-i.height-5;o<0&&(o=n.top+5,o+i.high>r.clientHeight&&(o=r.clientHeight-i.height)),e.style.top=o+`px`})},onunmount(e){e._positionWatcher?.unwatch()}},t.div({className:`content-primary`},()=>`${n.total} ${n.total==1?`request`:`requests`}`),t.div({className:`content-secondary`},()=>n.date));r.appendChild(i),r.addEventListener(`mouseleave`,()=>{n&&(n.show=!1)})},destroy:()=>{n.show=!1},setCursor:e=>{if(!n)return;let r=e.data[0][e.cursor.idx]||0,i=e.data[1][e.cursor.idx]||0;if(r==0||i==0){n.show=!1;return}n.show=!0,n.total=i;let a=new Date(r*1e3),o=new Date(r*1e3+36e5),s=a.toLocaleString(`default`,{month:`short`}),c=a.getDate().toString().padStart(2,`0`);n.date=`${s} ${c} ${zr(a)}-${zr(o)}`,n.left=Math.round(e.valToPos(r,`x`)),n.top=Math.round(e.valToPos(i,`y`))}}}}function zr(e){let n=e.getHours(),r=n>=12?`pm`:`am`;return n=n%12||12,n+r}var Br=50;function Vr(e){let n=store({logs:[],lastLoadCount:0,lastPage:1,bulkSelected:{},get canLoadMore(){return n.lastLoadCount>=Br},get totalSelected(){return Object.keys(n.bulkSelected).length},get areAllSelected(){return n.logs.length&&n.logs.length==n.totalSelected}});async function r(r=!1){e.isListLoading=!0;try{let i=r?1:n.lastPage+1,a=(e.presets||[]).concat(app.utils.normalizeSearchFilter(e.filter,[`level`,`message`,`data`]));if(e.zoom?.min&&e.zoom?.max){let n=new Date(e.zoom.min*1e3);n.setSeconds(0),n.setMilliseconds(0);let r=app.utils.toRFC3339Datetime(n),i=new Date(e.zoom.max*1e3);i.setSeconds(59),i.setMilliseconds(999);let o=app.utils.toRFC3339Datetime(i);a.push(`created >= "${r}" && created <= "${o}"`)}else i>1&&a.push(`created <= "${n.logs[n.logs.length-1].created}"`);let o=await app.pb.logs.getList(i,Br,{skipTotal:1,sort:`-@rowid`,requestKey:`logs_list`,filter:a.filter(Boolean).map(e=>`(`+e+`)`).join(`&&`)});o.page==1&&(n.logs=[],n.bulkSelected={}),n.lastPage=o.page,n.lastLoadCount=o.items.length;for(let e=0;e1&&e%20==0&&await new Promise(e=>setTimeout(e,20));e.isListLoading=!1,e.isFirstLoadReady||=!0}catch(n){n.isAbort||(e.isListLoading=!1,app.checkApiError(n))}}function i(e=!0){let r={};if(e)for(let e of n.logs)r[e.id]=e;n.bulkSelected=r}function a(e){let n=[];if(!e.data)return n;if(e.data.type==`request`){for(let r of[`status`,`execTime`,`auth`,`authId`,`userIP`])e.data[r]!==void 0&&n.push({key:r});e.data.referer&&!e.data.referer.includes(window.location.host)&&n.push({key:`referer`})}else{let r=Object.keys(e.data);for(let e of r)e!=`error`&&e!=`details`&&n.length<6&&n.push({key:e})}return e.data.error&&n.push({key:`error`,label:`danger`}),e.data.details&&n.push({key:`details`,label:`warning`}),n}let o=/[-:\. ]/gi;function s(){let e=Object.values(n.bulkSelected).sort((e,n)=>e.createdn.created?-1:0);if(!e.length)return;if(e.length==1)return app.utils.downloadJSON(e[0],`log_`+e[0].created.replaceAll(o,``)+`.json`);let r=e[0].created.replaceAll(o,``),i=e[e.length-1].created.replaceAll(o,``);return app.utils.downloadJSON(e,`${e.length}_logs_${i}_to_${r}.json`)}let c=[];return t.div({pbEvent:`logsList`,className:`page-table-wrapper`,onmount(n){c.push(watch(()=>[e.filter,e.presets?.length],()=>{e.zoom={}}),watch(()=>[e.reset,e.filter,e.presets?.length,e.zoom?.min,e.zoom?.max],()=>{r(!0),n&&(n.scrollTop=0)}))},onunmount(){c.forEach(e=>e?.unwatch())}},t.table({className:()=>`logs-table ${n.logs?.length>Br?`optimize`:``}`},t.thead(null,t.tr(null,t.th({className:`col-bulk-select`},t.div({className:`field`,hidden:()=>e.isLoading},t.input({id:`logs_select_all`,type:`checkbox`,checked:()=>n.areAllSelected,onchange:e=>i(e.target.checked)}),t.label({htmlFor:`logs_select_all`})),t.span({className:`loader`,hidden:()=>!e.isLoading})),t.th({className:`col-field-name-level`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-bookmark-line`,ariaHidden:!0}),t.span({textContent:`Level`}))),t.th({className:`col-field-name-message`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-file-list-2-line`,ariaHidden:!0}),t.span({textContent:`Message`}))),t.th({className:`col-field-type-date col-field-name-created`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-calendar-line`,ariaHidden:!0}),t.span({textContent:`Created`}))),t.th({className:`col-meta`}))),t.tbody(null,()=>n.logs?.length?n.logs.map(r=>t.tr({rid:r.id,tabIndex:0,role:`button`,className:()=>`handle ${r.data.type==`request`?`log-request`:``}`,onclick:()=>{e.activeLogIdOrModel=r},onkeypress:n=>{(n.key==`Enter`||n.key==` `)&&(n.preventDefault(),e.activeLogIdOrModel=r)}},()=>[t.td({className:`col-bulk-select`,onclick:e=>e.stopPropagation(),onkeypress:e=>e.stopPropagation()},t.div({className:`field`},t.input({id:`cb_`+r.id,type:`checkbox`,checked:()=>!!n.bulkSelected[r.id],onchange:e=>{let i=JSON.parse(JSON.stringify(n.bulkSelected));e.target.checked?i[r.id]=!0:delete i[r.id],n.bulkSelected=i}}),t.label({htmlFor:`cb_`+r.id}))),t.td({className:`col-field-name-level`},dr(r)),t.td({className:`col-field-name-message`},t.div({className:`content-primary`},()=>app.utils.truncate(r.message,1e3)),t.div({className:`content-secondary`},()=>{let e=[],n=a(r);for(let i of n){let n;n=app.utils.logDataFormatters[i.key]?app.utils.logDataFormatters[i.key](r):app.utils.displayValue(r.data[i.key],80),e.push(t.span({className:`label sm ${i.label||``}`},`${i.key}: ${n}`))}return e})),t.td({className:`col-field-type-date col-field-name-created`},app.components.formattedDate({value:()=>r.created,short:!1})),t.td({className:`col-meta`},t.i({className:`ri-arrow-right-line`,ariaHidden:!0}))])):t.tr(null,t.td({colSpan:99},()=>e.isListLoading?t.span({className:`skeleton-loader`}):t.div({className:`sticky-content txt-center txt-hint`},t.p({className:`txt-bold`},`No logs found.`),t.button({hidden:()=>e.filter?.length||app.utils.isEmpty(e.zoom),type:`button`,className:`btn secondary expanded-lg`,onclick(){e.zoom={}}},t.span({className:`txt`},`Reset zoom`)),t.button({hidden:()=>!e.filter?.length,type:`button`,className:`btn secondary expanded-lg`,onclick(){e.filter=``}},t.span({className:`txt`},`Clear search`))))),t.tr({hidden:()=>!n.canLoadMore},t.td({colSpan:99},t.button({className:()=>`btn lg secondary load-more-btn ${e.isListLoading?`transparent loading`:``}`,disabled:()=>e.isListLoading,onclick:()=>r()},t.span({className:`txt`},`Load older`)))))),t.div({className:`bulkbar-wrapper`},t.div({hidden:()=>n.totalSelected==0,className:`bulkbar logs-bulkbar`},t.span({className:`txt`},`Selected `,t.strong(null,()=>n.totalSelected),()=>` ${n.totalSelected==1?`log`:`logs`}`),t.button({type:`button`,className:`btn sm secondary pill m-r-auto`,onclick:()=>i(!1)},t.span({className:`txt`},`Reset`)),t.button({type:`button`,className:`btn sm pill`,onclick:()=>s()},t.i({className:`ri-download-line`,ariaHidden:!0}),t.span({className:`txt`},`JSON`)))))}function Hr(e){app.store.title=`Logs`;let n=`logId`,r=`filter`,i=`superuserRequests`,a=`pbLogSuperuserRequests`,o=`data.auth!='_superusers'`,s=e.query[n]?.[0]||``,c=e.query[r]?.[0]||``,l=!!(e.query[i]?.[0]<<0)||!!(window.localStorage.getItem(a)<<0),u=store({reset:null,isChartLoading:!1,isListLoading:!1,isFirstLoadReady:!1,zoom:{},presets:l?[]:[o],filter:c,totalFound:null,activeLogIdOrModel:s,get hasIncludeRequestsBySuperusers(){return!u.presets.includes(o)},get isLoading(){return u.isListLoading||u.isChartLoading}});function d(e){return e?typeof e==`string`?e:e?.id:null}function f(){u.reset=Date.now()}let p=[];return[t.div({pbEvent:`logsChartContainer`,className:`logs-chart-container accent-surface`},Nr(u)),t.div({pbEvent:`pageLogs`,className:`page page-logs`,onmount(e){p.push(watch(()=>{app.utils.replaceHashQueryParams({[r]:u.filter})}),watch(()=>{let e=+!!u.hasIncludeRequestsBySuperusers;app.utils.replaceHashQueryParams({[i]:e}),window.localStorage.setItem(a,e)}),watch(()=>u.activeLogIdOrModel,()=>{app.utils.replaceHashQueryParams({[n]:d(u.activeLogIdOrModel)}),u.activeLogIdOrModel&&(app.modals.close(null,!0),app.modals.openLogPreview(u.activeLogIdOrModel,{onafterclose:()=>{u.activeLogIdOrModel=null}}))}))},onunmount(e){clearTimeout(e._chartTiemoutId),p.forEach(e=>e?.unwatch())}},t.div({className:`page-content full-height`},t.header({className:`page-header`},t.nav({className:`breadcrumbs`},t.div(null,`Logs`)),t.div({className:`inline-flex gap-sm`},t.button({className:`btn circle transparent secondary tooltip-right`,ariaLabel:app.attrs.tooltip(`Logs settings`),onclick:()=>app.modals.openLogsSettings({onsave:()=>f()})},t.i({className:`ri-settings-3-line`,ariaHidden:!0})),app.components.refreshButton({onclick:f})),app.components.searchbar({className:`logs-searchbar`,historyKey:`pbLogsSearchHistory`,placeholder:"Search term or filter like `level > 0`",value:()=>u.filter||``,onsubmit:e=>u.filter=e,autocomplete:[`id`,`level`,`message`,`created`,{value:`data.`,label:`data.*`}]}),t.div({className:`meta m-l-auto`},t.div({className:`field logs-include-superuser-requests`},t.input({type:`checkbox`,id:`logs_checkbox`,className:`switch sm`,checked:()=>u.hasIncludeRequestsBySuperusers,onchange:e=>{e.target.checked?app.utils.removeByValue(u.presets,o):app.utils.pushUnique(u.presets,o)}}),t.label({htmlFor:`logs_checkbox`},t.small({className:`txt`},`Include requests by superusers`))))),Vr(u),t.footer({className:`page-footer`},t.span({className:`txt total-logs`},`Total: `,()=>u.totalFound==null?`...`:u.totalFound),app.components.credits())))]}function $(){return app.components.pageSidebar({pbEvent:`settingsSidebar`,className:`settings-sidebar`},t.nav({className:`sidebar-content scrollable`},()=>{let e=[];for(let n in app.store.settingsNavGroups){let r=app.store.settingsNavGroups[n],i=t.details({className:`nav-group`,"html-data-group":n,open:!0},t.summary({tabIndex:-1,onfocusout:()=>!1,onclick:()=>!1,onkeyup:()=>!1},n),()=>r.map(e=>{let n=e.href.startsWith(`#/`);return t.a({href:()=>e.href,target:()=>n?void 0:`_blank`,rel:()=>n?void 0:`noopener noreferrer`,className:n=>`nav-item ${e.isActive?.(n)||app.utils.isActivePath(e.href,!1)?`active`:``}`},()=>{if(e.icon)return t.i({className:e.icon,ariaHidden:!0})},t.span({className:`txt`},()=>e.label))}));e.push(i)}return e}))}function Ur(e){return t.details({pbEvent:`batchApiAccordion`,className:`accordion batch-api-accordion`,name:`settingsAccordion`},t.summary(null,t.i({className:`ri-archive-stack-line`,ariaHidden:!0}),t.span({className:`txt`},`Batch API`),t.div({className:`flex-fill`}),()=>e.formSettings.batch.enabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.batch))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid sm`},t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`batch.enabled`,name:`batch.enabled`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.batch.enabled||!1,onchange:n=>e.formSettings.batch.enabled=n.target.checked}),t.label({htmlFor:`batch.enabled`},t.span({className:`txt`},`Enable`),t.small({className:`txt-hint`},` (experimental)`)))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.maxRequests`},t.span({className:`txt`},`Max requests in a batch`),t.i({className:`ri-information-line link-faded`,ariaDescription:app.attrs.tooltip(`Rate limiting (if enabled) also applies for the batch create/update/upsert/delete requests.`,`right`)})),t.input({id:`batch.maxRequests`,name:`batch.maxRequests`,type:`number`,min:1,step:1,required:()=>e.formSettings.batch.enabled,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.maxRequests,oninput:n=>e.formSettings.batch.maxRequests=n.target.value<<0}))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.timeout`},t.span({className:`txt`},`Max processing time (in seconds)`)),t.input({id:`batch.timeout`,name:`batch.timeout`,type:`number`,min:1,step:1,required:()=>e.formSettings.batch.enabled,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.timeout,oninput:n=>e.formSettings.batch.timeout=parseInt(n.target.value,10)}))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.maxBodySize`},t.span({className:`txt`},`Max body size (in bytes)`)),t.input({id:`batch.maxBodySize`,name:`batch.maxBodySize`,type:`number`,min:0,step:1,placeholder:`Default to 128MB`,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.maxBodySize||``,oninput:n=>e.formSettings.batch.maxBodySize=parseInt(n.target.value,10)})))))}var Wr=[{value:`*:list`},{value:`*:view`},{value:`*:create`},{value:`*:update`},{value:`*:delete`},{value:`*:file`,description:`targets the files download endpoint`},{value:`*:listAuthMethods`},{value:`*:authRefresh`},{value:`*:auth`,description:`targets all auth methods`},{value:`*:authWithPassword`},{value:`*:authWithOAuth2`},{value:`*:authWithOTP`},{value:`*:requestOTP`},{value:`*:requestPasswordReset`},{value:`*:confirmPasswordReset`},{value:`*:requestVerification`},{value:`*:confirmVerification`},{value:`*:requestEmailChange`},{value:`*:confirmEmailChange`}];function Gr(){let e=Kr();document.body.appendChild(e),app.modals.open(e)}function Kr(){return t.div({pbEvent:`rateLimitInfoModal`,className:`modal rate-limit-info-modal`,onafterclose:e=>{e?.remove()}},t.header({className:`modal-header`},t.h5(null,`Rate limit label format`)),t.div({className:`modal-content`},t.p(null,`The rate limit rules are resolved in the following order (stops on the first match):`),t.ol(null,t.li(null,`exact tag (e.g. `,t.code(null,`users:create`)),t.li(null,`wildcard tag (e.g. `,t.code(null,`*:create`)),t.li(null,`METHOD + exact path (e.g. `,t.code(null,`POST /a/b`)),t.li(null,`METHOD + prefix path (e.g. `,t.code(null,`POST /a/b`,t.strong(null,`/`))),t.li(null,`exact path (e.g. `,t.code(null,`/a/b`)),t.li(null,`prefix path (e.g. `,t.code(null,`/a/b`,t.strong(null,`/`)))),t.p(null,`In case of multiple rules with the same label but different target user audience (e.g. "guest" vs "auth"), only the matching audience rule is taken in consideration.`),t.hr(),t.p(null,`The rate limit label could be in one of the following formats:`),t.ul(null,t.li({className:`m-b-sm`},t.code(null,`[METHOD ]/my/path`),` - full exact route match (`,t.strong(null,`must be without trailing slash`),`; "METHOD" is optional).`,t.br(),`For example:`,t.ul({className:`m-0`},t.li(null,t.code(null,`/hello`),` - matches `,t.code(null,`GET /hello`),`, `,t.code(null,`POST /hello`),`, etc.`),t.li(null,t.code(null,`POST /hello`),` - matches only `,t.code(null,`POST /hello`)))),t.li({className:`m-b-sm`},t.code(null,`[METHOD ]/my/prefix`,t.strong(null,`/`)),` - path prefix (`,t.strong(null,`must end with trailing slash;`),`"METHOD" is optional). For example:`,t.ul({className:`m-0`},t.li(null,t.code(null,`/hello/`),` - matches `,t.code(null,`GET /hello`),`, `,t.code(null,`POST /hello/a/b/c`),`, etc.`),t.li(null,t.code(null,`POST /hello/`),` - matches `,t.code(null,`POST /hello`),`, `,t.code(null,`POST /hello/a/b/c`),`, etc.`))),t.li({className:`m-b-0`},t.code(null,`collectionName:predefinedTag`),` - targets a specific action of a single collection.`,` To apply the rule for all collections you can use the `,t.code(null,`*`),` wildcard. For example:`,t.code(null,`posts:create`),`, `,t.code(null,`users:listAuthMethods`),`, `,t.code(null,`*:auth`),`.`,t.br(),`The predifined collection tags are (`,t.em(null,`there should be autocomplete once you start typing`),`):`,t.ul({className:`m-0`},()=>Wr.map(e=>t.li(null,e.value.replace(`*:`,`:`),()=>{if(e.description)return t.em({className:`txt-hint`},` (`,e.description,`)`)})))))),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close()},t.span({className:`txt`},`Close`))))}function qr(e){if(!e)return;let n=[{},{}];return e.sort((e,r)=>{n[0].length=e.label.length,n[0].isTag=e.label.includes(`:`)||!e.label.includes(`/`),n[0].isWildcardTag=n[0].isTag&&e.label.startsWith(`*`),n[0].isExactTag=n[0].isTag&&!n[0].isWildcardTag,n[0].isPrefix=!n[0].isTag&&e.label.endsWith(`/`),n[0].hasMethod=!n[0].isTag&&e.label.includes(` /`),n[1].length=r.label.length,n[1].isTag=r.label.includes(`:`)||!r.label.includes(`/`),n[1].isWildcardTag=n[1].isTag&&r.label.startsWith(`*`),n[1].isExactTag=n[1].isTag&&!n[1].isWildcardTag,n[1].isPrefix=!n[1].isTag&&r.label.endsWith(`/`),n[1].hasMethod=!n[1].isTag&&r.label.includes(` /`);for(let e of n)e.priority=0,e.isTag?(e.priority+=1e3,e.isExactTag?e.priority+=10:e.priority+=5):(e.hasMethod&&(e.priority+=10),e.isPrefix||(e.priority+=5));return n[0].isPrefix&&n[1].isPrefix&&(n[0].hasMethod&&n[1].hasMethod||!n[0].hasMethod&&!n[1].hasMethod)&&(n[0].length>n[1].length?n[0].priority+=1:n[0].lengthn[1].priority?-1:+(n[0].prioritye.type==`file`)&&r.predefinedTags.push({value:n.name+`:file`}));r.predefinedTags=r.predefinedTags.concat(Wr)}function a(){Array.isArray(e.formSettings.rateLimits.rules)||(e.formSettings.rateLimits.rules=[]),e.formSettings.rateLimits.rules.push({label:``,maxRequests:200,duration:3,audience:``}),e.formSettings.rateLimits.rules.length==1&&(e.formSettings.rateLimits.enabled=!0)}function o(n){e.formSettings.rateLimits.rules.splice(n,1),e.formSettings.rateLimits.rules.length||(e.formSettings.rateLimits.enabled=!1)}let s=[];return t.details({pbEvent:`rateLimitAccordion`,className:`accordion rate-limit-accordion`,name:`settingsAccordion`,onmount:()=>{s.push(watch(()=>JSON.stringify(e.formSettings.rateLimits.rules),()=>{app.store.errors?.rateLimits?.rules&&delete app.store.errors.rateLimits}))},onunmount:()=>{s.forEach(e=>e?.unwatch())}},t.summary(null,t.i({className:`ri-pulse-fill`,ariaHidden:!0}),t.span({className:`txt`},`Rate limiting`),t.div({className:`flex-fill`}),()=>e.formSettings.rateLimits.enabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.rateLimits))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid sm`},t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`rateLimits.enabled`,name:`rateLimits.enabled`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.rateLimits.enabled||!1,onchange:n=>e.formSettings.rateLimits.enabled=n.target.checked}),t.label({htmlFor:`rateLimits.enabled`},t.span({className:`txt`},`Enable`),t.small({className:`txt-hint`},` (experimental)`)))),t.div({className:`col-lg-12`},t.div({className:`rate-limit-table-wrapper`},t.table({className:`rate-limit-table`},t.thead({hidden:()=>!e.formSettings.rateLimits.rules?.length},t.tr(null,t.th({className:`col-label`},`Rate limit label`),t.th({className:`col-requests`},`Max requests`,t.br(),t.small(null,`(per IP)`)),t.th({className:`col-duration`},`Interval`,t.br(),t.small(null,`(in seconds)`)),t.th({className:`col-audience`},`Targeted users`),t.th({className:`col-action`}))),t.tbody(null,()=>{let i=[],a=e.formSettings.rateLimits.rules||[];for(let e=0;es.label,oninput:e=>s.label=e.target.value}),t.datalist({id:`rateLimits.rules.`+e+`.label_list`},()=>r.predefinedTags.map(e=>t.option({value:e.value},e.label||``))))),t.td({className:`col-requests`},t.div({className:`field`},t.input({type:`number`,required:!0,placeholder:`Max requests*`,className:`inline-error`,min:1,step:1,name:`rateLimits.rules.`+e+`.maxRequests`,value:()=>s.maxRequests||0,oninput:e=>s.maxRequests=parseInt(e.target.value,10)}))),t.td({className:`col-duration`},t.div({className:`field`},t.input({type:`number`,required:!0,placeholder:`Interval*`,className:`inline-error`,min:1,step:1,name:`rateLimits.rules.`+e+`.duration`,value:()=>s.duration,oninput:e=>s.duration=parseInt(e.target.value,10)}))),t.td({className:`col-audience`},t.div({className:`field`},app.components.select({name:`rateLimits.rules.`+e+`.audience`,className:`inline-error`,options:n,required:!0,value:()=>s.audience||``,onchange:e=>{s.audience=e?.[0]?.value}}))),t.td({className:`col-action`},t.button({type:`button`,araiaDescription:app.attrs.tooltip(`Remove rule`),className:`btn sm secondary transparent circle`,onclick:()=>o(e)},t.i({className:`ri-close-line`})))))}return i}))),t.div({className:`flex m-t-sm`},t.button({type:`button`,className:`btn secondary sm`,onclick:()=>a()},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt`},`Add rate limit rule`)),t.button({type:`button`,className:`link-hint txt-sm m-l-auto`,onclick:()=>Gr()},t.em(null,`Learn more about the rate limit rules`))))))}function Yr(e){let n=[`X-Forwarded-For`,`Fly-Client-IP`,`CF-Connecting-IP`],r=[{label:`Use leftmost IP`,value:!0},{label:`Use rightmost IP`,value:!1}],i=store({isLoading:!1,realIP:``,possibleProxyHeader:``,get suggestedProxyHeaders(){return i.possibleProxyHeader?[i.possibleProxyHeader].concat(n.filter(e=>e!=i.possibleProxyHeader)):n},get isEnabled(){return!app.utils.isEmpty(e.formSettings.trustedProxy?.headers)}});a();async function a(){i.isLoading=!0;try{let e=await app.pb.health.check({requestKey:`loadProxyInfo`});i.realIP=e.data?.realIP||``,i.possibleProxyHeader=e.data?.possibleProxyHeader||``,i.isLoading=!1}catch(e){e.isAbort||(app.checkApiError(e),i.isLoading=!1)}}return t.details({pbEvent:`trustedProxyAccordion`,className:`accordion trusted-proxy-accordion`,name:`settingsAccordion`,open:()=>i.isLoading?!1:null},t.summary(null,t.i({className:`ri-route-line`,ariaHidden:!0}),t.span({className:`txt`},`User IP proxy headers`),()=>{if(i.isLoading)return t.span({className:`loader sm`});if(!i.isEnabled&&i.possibleProxyHeader)return t.i({className:`ri-alert-line txt-warning`,ariaDescription:app.attrs.tooltip(`Detected proxy header. -It is recommend to list it as trusted.`,`right`)});if(i.isEnabled&&i.possibleProxyHeader&&!e.formSettings.trustedProxy.headers.includes(i.possibleProxyHeader))return t.i({className:`ri-alert-line txt-hint`,ariaDescription:app.attrs.tooltip(`The configured proxy header doesn't match with the detected one.`,`right`)})},t.div({className:`flex-fill`}),()=>i.isEnabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.trustedProxy))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.p({className:`m-t-0`},`Below you should see your real IP. If not - configure the correct proxy header for your environment.`),t.div({hidden:()=>i.isLoading,className:`alert info m-b-sm`},t.div({className:`flex gap-5`},t.span(null,`Resolved user IP:`),t.strong(null,()=>i.realIP||`N/A`)),t.div({className:`flex gap-5`},t.span(null,`Detected proxy header:`),t.strong(null,()=>i.possibleProxyHeader||`N/A`))),t.div({className:`content m-b-sm`},t.p(null,` +{MMM} {DD}`,null,null,null,1]],grid:{show:!0,stroke:o,width:1},ticks:{show:!0,stroke:o,width:1,size:5}},{show:!0,stroke:a,grid:{show:!0,stroke:o,width:1},ticks:{show:!0,stroke:o,width:1,size:5}}],plugins:[Rr(),Ir(r),Lr(r)]};e._uplot?.destroy(),e._uplot=new uPlot(l,n,e)}function Ir(e){let n;return{hooks:{init:r=>{r.over.ondblclick=n=>{e.zoom={}},n=watch(()=>{!e.zoom?.min||!e.zoom?.max?Pr(r):r.setScale(`x`,{min:e.zoom.min,max:e.zoom.max})})},destroy:e=>{n?.unwatch()},setSelect:n=>{n.select.width>0&&(e.zoom={min:n.posToVal(n.select.left,`x`),max:n.posToVal(n.select.left+n.select.width,`x`)})}}}}function Lr(e){let n;return{hooks:{init:r=>{let i=r.root.querySelectorAll(`.u-axis`);if(!i.length){console.warn(`xPanPlugin requires x axis to be defined`);return}i[0].addEventListener(`mousedown`,i=>{if(!e.zoom?.min)return;let a=i.clientX,{min:o,max:s}=r.scales.x,c=r.bbox.width,l=(s-o)/(c/uPlot.pxRatio),u=i=>{let c=(a-i.clientX)*l;r.setScale(`x`,{min:o+c,max:s+c}),clearTimeout(n),n=setTimeout(()=>{r?.scales?.x&&(e.zoom={min:r.scales.x.min,max:r.scales.x.max})},100)},d=e=>{document.removeEventListener(`mousemove`,u),document.removeEventListener(`mouseup`,d)};document.addEventListener(`mousemove`,u),document.addEventListener(`mouseup`,d)})},destroy:e=>{n&&clearTimeout(n)}}}}function Rr(e){let n;return{hooks:{init:e=>{let r=e.over;n=store({date:``,total:0,left:0,top:0,show:!1});let i=t.div({className:()=>`chart-tooltip ${n.show?``:`hidden`}`,onmount(e){e._positionWatcher?.unwatch(),e._positionWatcher=watch(()=>[n.left,n.top],()=>{if(!e)return;let i=e.getBoundingClientRect(),a=n.left;a<0?a=0:a+i.width>r.clientWidth&&(a=r.clientWidth-i.width),e.style.left=a+`px`;let o=n.top-i.height-5;o<0&&(o=n.top+5,o+i.high>r.clientHeight&&(o=r.clientHeight-i.height)),e.style.top=o+`px`})},onunmount(e){e._positionWatcher?.unwatch()}},t.div({className:`content-primary`},()=>`${n.total} ${n.total==1?`request`:`requests`}`),t.div({className:`content-secondary`},()=>n.date));r.appendChild(i),r.addEventListener(`mouseleave`,()=>{n&&(n.show=!1)})},destroy:()=>{n.show=!1},setCursor:e=>{if(!n)return;let r=e.data[0][e.cursor.idx]||0,i=e.data[1][e.cursor.idx]||0;if(r==0||i==0){n.show=!1;return}n.show=!0,n.total=i;let a=new Date(r*1e3),o=new Date(r*1e3+36e5),s=a.toLocaleString(`default`,{month:`short`}),c=a.getDate().toString().padStart(2,`0`);n.date=`${s} ${c} ${zr(a)}-${zr(o)}`,n.left=Math.round(e.valToPos(r,`x`)),n.top=Math.round(e.valToPos(i,`y`))}}}}function zr(e){let n=e.getHours(),r=n>=12?`pm`:`am`;return n=n%12||12,n+r}var Br=50;function Vr(e){let n=store({logs:[],lastLoadCount:0,lastPage:1,bulkSelected:{},get canLoadMore(){return n.lastLoadCount>=Br},get totalSelected(){return Object.keys(n.bulkSelected).length},get areAllSelected(){return n.logs.length&&n.logs.length==n.totalSelected}});async function r(r=!1){e.isListLoading=!0;try{let i=r?1:n.lastPage+1,a=(e.presets||[]).concat(app.utils.normalizeSearchFilter(e.filter,[`level`,`message`,`data`]));if(e.zoom?.min&&e.zoom?.max){let n=new Date(e.zoom.min*1e3);n.setSeconds(0),n.setMilliseconds(0);let r=app.utils.toRFC3339Datetime(n),i=new Date(e.zoom.max*1e3);i.setSeconds(59),i.setMilliseconds(999);let o=app.utils.toRFC3339Datetime(i);a.push(`created >= "${r}" && created <= "${o}"`)}else i>1&&a.push(`created <= "${n.logs[n.logs.length-1].created}"`);let o=await app.pb.logs.getList(i,Br,{skipTotal:1,sort:`-@rowid`,requestKey:`logs_list`,filter:a.filter(Boolean).map(e=>`(`+e+`)`).join(`&&`)});o.page==1&&(n.logs=[],n.bulkSelected={}),n.lastPage=o.page,n.lastLoadCount=o.items.length;for(let e=0;e1&&e%20==0&&await new Promise(e=>setTimeout(e,20));e.isListLoading=!1,e.isFirstLoadReady||=!0}catch(n){n.isAbort||(e.isListLoading=!1,app.checkApiError(n))}}function i(e=!0){let r={};if(e)for(let e of n.logs)r[e.id]=e;n.bulkSelected=r}function a(e){let n=[];if(!e.data)return n;if(e.data.type==`request`){for(let r of[`status`,`execTime`,`auth`,`authId`,`userIP`])e.data[r]!==void 0&&n.push({key:r});e.data.referer&&!e.data.referer.includes(window.location.host)&&n.push({key:`referer`})}else{let r=Object.keys(e.data);for(let e of r)e!=`error`&&e!=`details`&&n.length<6&&n.push({key:e})}return e.data.error&&n.push({key:`error`,label:`danger`}),e.data.details&&n.push({key:`details`,label:`warning`}),n}let o=/[-:\. ]/gi;function s(){let e=Object.values(n.bulkSelected).sort((e,n)=>e.createdn.created?-1:0);if(!e.length)return;if(e.length==1)return app.utils.downloadJSON(e[0],`log_`+e[0].created.replaceAll(o,``)+`.json`);let r=e[0].created.replaceAll(o,``),i=e[e.length-1].created.replaceAll(o,``);return app.utils.downloadJSON(e,`${e.length}_logs_${i}_to_${r}.json`)}let c=[];return t.div({pbEvent:`logsList`,className:`page-table-wrapper`,onmount(n){c.push(watch(()=>[e.filter,e.presets?.length],()=>{e.zoom={}}),watch(()=>[e.reset,e.filter,e.presets?.length,e.zoom?.min,e.zoom?.max],()=>{r(!0),n&&(n.scrollTop=0)}))},onunmount(){c.forEach(e=>e?.unwatch())}},t.table({className:()=>`logs-table ${n.logs?.length>Br?`optimize`:``}`},t.thead(null,t.tr(null,t.th({className:`col-bulk-select`},t.div({className:`field`,hidden:()=>e.isLoading},t.input({id:`logs_select_all`,type:`checkbox`,checked:()=>n.areAllSelected,onchange:e=>i(e.target.checked)}),t.label({htmlFor:`logs_select_all`})),t.span({className:`loader`,hidden:()=>!e.isLoading})),t.th({className:`col-field-name-level`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-bookmark-line`,ariaHidden:!0}),t.span({textContent:`Level`}))),t.th({className:`col-field-name-message`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-file-list-2-line`,ariaHidden:!0}),t.span({textContent:`Message`}))),t.th({className:`col-field-type-date col-field-name-created`},t.div({className:`inline-flex gap-5`},t.i({className:`ri-calendar-line`,ariaHidden:!0}),t.span({textContent:`Created`}))),t.th({className:`col-meta`}))),t.tbody(null,()=>n.logs?.length?n.logs.map(r=>t.tr({rid:r.id,tabIndex:0,role:`button`,className:()=>`handle ${r.data.type==`request`?`log-request`:``}`,onclick:()=>{e.activeLogIdOrModel=r},onkeypress:n=>{(n.key==`Enter`||n.key==` `)&&(n.preventDefault(),e.activeLogIdOrModel=r)}},()=>[t.td({className:`col-bulk-select`,onclick:e=>e.stopPropagation(),onkeypress:e=>e.stopPropagation()},t.div({className:`field`},t.input({id:`cb_`+r.id,type:`checkbox`,checked:()=>!!n.bulkSelected[r.id],onchange:e=>{let i=JSON.parse(JSON.stringify(n.bulkSelected));e.target.checked?i[r.id]=!0:delete i[r.id],n.bulkSelected=i}}),t.label({htmlFor:`cb_`+r.id}))),t.td({className:`col-field-name-level`},dr(r)),t.td({className:`col-field-name-message`},t.div({className:`content-primary`},()=>app.utils.truncate(r.message,1e3)),t.div({className:`content-secondary`},()=>{let e=[],n=a(r);for(let i of n){let n;n=app.utils.logDataFormatters[i.key]?app.utils.logDataFormatters[i.key](r):app.utils.displayValue(r.data[i.key],80),e.push(t.span({className:`label sm ${i.label||``}`},`${i.key}: ${n}`))}return e})),t.td({className:`col-field-type-date col-field-name-created`},app.components.formattedDate({value:()=>r.created,short:!1})),t.td({className:`col-meta`},t.i({className:`ri-arrow-right-line`,ariaHidden:!0}))])):t.tr(null,t.td({colSpan:99},()=>e.isListLoading?t.span({className:`skeleton-loader`}):t.div({className:`sticky-content txt-center txt-hint`},t.p({className:`txt-bold`},`No logs found.`),t.button({hidden:()=>e.filter?.length||app.utils.isEmpty(e.zoom),type:`button`,className:`btn secondary expanded-lg`,onclick(){e.zoom={}}},t.span({className:`txt`},`Reset zoom`)),t.button({hidden:()=>!e.filter?.length,type:`button`,className:`btn secondary expanded-lg`,onclick(){e.filter=``}},t.span({className:`txt`},`Clear search`))))),t.tr({hidden:()=>!n.canLoadMore},t.td({colSpan:99},t.button({className:()=>`btn lg secondary load-more-btn ${e.isListLoading?`transparent loading`:``}`,disabled:()=>e.isListLoading,onclick:()=>r()},t.span({className:`txt`},`Load older`)))))),t.div({className:`bulkbar-wrapper`},t.div({hidden:()=>n.totalSelected==0,className:`bulkbar logs-bulkbar`},t.span({className:`txt`},`Selected `,t.strong(null,()=>n.totalSelected),()=>` ${n.totalSelected==1?`log`:`logs`}`),t.button({type:`button`,className:`btn sm secondary pill m-r-auto`,onclick:()=>i(!1)},t.span({className:`txt`},`Reset`)),t.button({type:`button`,className:`btn sm pill`,onclick:()=>s()},t.i({className:`ri-download-line`,ariaHidden:!0}),t.span({className:`txt`},`JSON`)))))}function Hr(e){app.store.title=`Logs`;let n=`logId`,r=`filter`,i=`superuserRequests`,a=`pbLogSuperuserRequests`,o=`data.auth!='_superusers'`,s=e.query[n]?.[0]||``,c=e.query[r]?.[0]||``,l=!!(e.query[i]?.[0]<<0)||!!(window.localStorage.getItem(a)<<0),u=store({reset:null,isChartLoading:!1,isListLoading:!1,isFirstLoadReady:!1,zoom:{},presets:l?[]:[o],filter:c,totalFound:null,activeLogIdOrModel:s,get hasIncludeRequestsBySuperusers(){return!u.presets.includes(o)},get isLoading(){return u.isListLoading||u.isChartLoading}});function d(e){return e?typeof e==`string`?e:e?.id:null}function f(){u.reset=Date.now()}let p=[];return[t.div({pbEvent:`logsChartContainer`,className:`logs-chart-container accent-surface`},Nr(u)),t.div({pbEvent:`pageLogs`,className:`page page-logs`,onmount(e){p.push(watch(()=>{app.utils.replaceHashQueryParams({[r]:u.filter})}),watch(()=>{let e=+!!u.hasIncludeRequestsBySuperusers;app.utils.replaceHashQueryParams({[i]:e}),window.localStorage.setItem(a,e)}),watch(()=>u.activeLogIdOrModel,()=>{app.utils.replaceHashQueryParams({[n]:d(u.activeLogIdOrModel)}),u.activeLogIdOrModel&&(app.modals.close(null,!0),app.modals.openLogPreview(u.activeLogIdOrModel,{onafterclose:()=>{u.activeLogIdOrModel=null}}))}))},onunmount(e){clearTimeout(e._chartTiemoutId),p.forEach(e=>e?.unwatch())}},t.div({className:`page-content full-height`},t.header({className:`page-header`},t.nav({className:`breadcrumbs`},t.div(null,`Logs`)),t.div({className:`inline-flex gap-sm`},t.button({className:`btn circle transparent secondary tooltip-right`,ariaLabel:app.attrs.tooltip(`Logs settings`),onclick:()=>app.modals.openLogsSettings({onsave:()=>f()})},t.i({className:`ri-settings-3-line`,ariaHidden:!0})),app.components.refreshButton({onclick:f})),app.components.searchbar({className:`logs-searchbar`,historyKey:`pbLogsSearchHistory`,placeholder:"Search term or filter like `level > 0`",value:()=>u.filter||``,onsubmit:e=>u.filter=e,autocomplete:[`id`,`level`,`message`,`created`,{value:`data.`,label:`data.*`}]}),t.div({className:`meta m-l-auto`},t.div({className:`field logs-include-superuser-requests`},t.input({type:`checkbox`,id:`logs_checkbox`,className:`switch sm`,checked:()=>u.hasIncludeRequestsBySuperusers,onchange:e=>{e.target.checked?app.utils.removeByValue(u.presets,o):app.utils.pushUnique(u.presets,o)}}),t.label({htmlFor:`logs_checkbox`},t.small({className:`txt`},`Include requests by superusers`))))),Vr(u),t.footer({className:`page-footer`},t.span({className:`txt total-logs`},`Total: `,()=>u.totalFound==null?`...`:u.totalFound),app.components.credits())))]}function $(){return app.components.pageSidebar({pbEvent:`settingsSidebar`,className:`settings-sidebar`},t.nav({className:`sidebar-content scrollable`},()=>{let e=[];for(let n in app.store.settingsNavGroups){let r=app.store.settingsNavGroups[n],i=t.details({className:`nav-group`,"html-data-group":n,open:!0},t.summary({tabIndex:-1,onfocusout:()=>!1,onclick:()=>!1,onkeyup:()=>!1},n),()=>r.map(e=>{let n=e.href.startsWith(`#/`);return t.a({href:()=>e.href,target:()=>n?void 0:`_blank`,rel:()=>n?void 0:`noopener noreferrer`,className:n=>`nav-item ${e.isActive?.(n)||app.utils.isActivePath(e.href,!1)?`active`:``}`},()=>{if(e.icon)return t.i({className:e.icon,ariaHidden:!0})},t.span({className:`txt`},()=>e.label))}));e.push(i)}return e}))}function Ur(e){return t.details({pbEvent:`batchApiAccordion`,className:`accordion batch-api-accordion`,name:`settingsAccordion`},t.summary(null,t.i({className:`ri-archive-stack-line`,ariaHidden:!0}),t.span({className:`txt`},`Batch API`),t.div({className:`flex-fill`}),()=>e.formSettings.batch.enabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.batch))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid sm`},t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`batch.enabled`,name:`batch.enabled`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.batch.enabled||!1,onchange:n=>e.formSettings.batch.enabled=n.target.checked}),t.label({htmlFor:`batch.enabled`},t.span({className:`txt`},`Enable`),t.small({className:`txt-hint`},` (experimental)`)))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.maxRequests`},t.span({className:`txt`},`Max requests in a batch`),t.i({className:`ri-information-line link-faded`,ariaDescription:app.attrs.tooltip(`Rate limiting (if enabled) also applies for the batch create/update/upsert/delete requests.`,`right`)})),t.input({id:`batch.maxRequests`,name:`batch.maxRequests`,type:`number`,min:1,step:1,required:()=>e.formSettings.batch.enabled,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.maxRequests,oninput:n=>e.formSettings.batch.maxRequests=n.target.value<<0}))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.timeout`},t.span({className:`txt`},`Max processing time (in seconds)`)),t.input({id:`batch.timeout`,name:`batch.timeout`,type:`number`,min:1,step:1,required:()=>e.formSettings.batch.enabled,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.timeout,oninput:n=>e.formSettings.batch.timeout=parseInt(n.target.value,10)}))),t.div({className:`col-lg-4`},t.div({className:`field`},t.label({htmlFor:`batch.maxBodySize`},t.span({className:`txt`},`Max body size (in bytes)`)),t.input({id:`batch.maxBodySize`,name:`batch.maxBodySize`,type:`number`,min:0,step:1,placeholder:`Default to 128MB`,disabled:()=>!e.formSettings.batch.enabled,value:()=>e.formSettings.batch.maxBodySize||``,oninput:n=>e.formSettings.batch.maxBodySize=parseInt(n.target.value,10)})))))}var Wr=[{value:`*:list`},{value:`*:view`},{value:`*:create`},{value:`*:update`},{value:`*:delete`},{value:`*:file`,description:`targets the files download endpoint`},{value:`*:listAuthMethods`},{value:`*:authRefresh`},{value:`*:auth`,description:`targets all auth methods`},{value:`*:authWithPassword`},{value:`*:authWithOAuth2`},{value:`*:authWithOTP`},{value:`*:requestOTP`},{value:`*:requestPasswordReset`},{value:`*:confirmPasswordReset`},{value:`*:requestVerification`},{value:`*:confirmVerification`},{value:`*:requestEmailChange`},{value:`*:confirmEmailChange`}];function Gr(){let e=Kr();document.body.appendChild(e),app.modals.open(e)}function Kr(){return t.div({pbEvent:`rateLimitInfoModal`,className:`modal rate-limit-info-modal`,onafterclose:e=>{e?.remove()}},t.header({className:`modal-header`},t.h5(null,`Rate limit label format`)),t.div({className:`modal-content`},t.p(null,`The rate limit rules are resolved in the following order (stops on the first match):`),t.ol(null,t.li(null,`exact tag (e.g. `,t.code(null,`users:create`)),t.li(null,`wildcard tag (e.g. `,t.code(null,`*:create`)),t.li(null,`METHOD + exact path (e.g. `,t.code(null,`POST /a/b`)),t.li(null,`METHOD + prefix path (e.g. `,t.code(null,`POST /a/b`,t.strong(null,`/`))),t.li(null,`exact path (e.g. `,t.code(null,`/a/b`)),t.li(null,`prefix path (e.g. `,t.code(null,`/a/b`,t.strong(null,`/`)))),t.p(null,`In case of multiple rules with the same label but different target user audience (e.g. "guest" vs "auth"), only the matching audience rule is taken in consideration.`),t.hr(),t.p(null,`The rate limit label could be in one of the following formats:`),t.ul(null,t.li({className:`m-b-sm`},t.code(null,`[METHOD ]/my/path`),` - full exact route match (`,t.strong(null,`must be without trailing slash`),`; "METHOD" is optional).`,t.br(),`For example:`,t.ul({className:`m-0`},t.li(null,t.code(null,`/hello`),` - matches `,t.code(null,`GET /hello`),`, `,t.code(null,`POST /hello`),`, etc.`),t.li(null,t.code(null,`POST /hello`),` - matches only `,t.code(null,`POST /hello`)))),t.li({className:`m-b-sm`},t.code(null,`[METHOD ]/my/prefix`,t.strong(null,`/`)),` - path prefix (`,t.strong(null,`must end with trailing slash;`),`"METHOD" is optional). For example:`,t.ul({className:`m-0`},t.li(null,t.code(null,`/hello/`),` - matches `,t.code(null,`GET /hello`),`, `,t.code(null,`POST /hello/a/b/c`),`, etc.`),t.li(null,t.code(null,`POST /hello/`),` - matches `,t.code(null,`POST /hello`),`, `,t.code(null,`POST /hello/a/b/c`),`, etc.`))),t.li({className:`m-b-0`},t.code(null,`collectionName:predefinedTag`),` - targets a specific action of a single collection.`,` To apply the rule for all collections you can use the `,t.code(null,`*`),` wildcard. For example:`,t.code(null,`posts:create`),`, `,t.code(null,`users:listAuthMethods`),`, `,t.code(null,`*:auth`),`.`,t.br(),`The predifined collection tags are (`,t.em(null,`there should be autocomplete once you start typing`),`):`,t.ul({className:`m-0`},()=>Wr.map(e=>t.li(null,e.value.replace(`*:`,`:`),()=>{if(e.description)return t.em({className:`txt-hint`},` (`,e.description,`)`)})))))),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close()},t.span({className:`txt`},`Close`))))}function qr(e){if(!e)return;let n=[{},{}];return e.sort((e,r)=>{n[0].length=e.label.length,n[0].isTag=e.label.includes(`:`)||!e.label.includes(`/`),n[0].isWildcardTag=n[0].isTag&&e.label.startsWith(`*`),n[0].isExactTag=n[0].isTag&&!n[0].isWildcardTag,n[0].isPrefix=!n[0].isTag&&e.label.endsWith(`/`),n[0].hasMethod=!n[0].isTag&&e.label.includes(` /`),n[1].length=r.label.length,n[1].isTag=r.label.includes(`:`)||!r.label.includes(`/`),n[1].isWildcardTag=n[1].isTag&&r.label.startsWith(`*`),n[1].isExactTag=n[1].isTag&&!n[1].isWildcardTag,n[1].isPrefix=!n[1].isTag&&r.label.endsWith(`/`),n[1].hasMethod=!n[1].isTag&&r.label.includes(` /`);for(let e of n)e.priority=0,e.isTag?(e.priority+=1e3,e.isExactTag?e.priority+=10:e.priority+=5):(e.hasMethod&&(e.priority+=10),e.isPrefix||(e.priority+=5));return n[0].isPrefix&&n[1].isPrefix&&(n[0].hasMethod&&n[1].hasMethod||!n[0].hasMethod&&!n[1].hasMethod)&&(n[0].length>n[1].length?n[0].priority+=1:n[0].lengthn[1].priority?-1:+(n[0].prioritye.type==`file`)&&r.predefinedTags.push({value:n.name+`:file`}));r.predefinedTags=r.predefinedTags.concat(Wr)}function a(){Array.isArray(e.formSettings.rateLimits.rules)||(e.formSettings.rateLimits.rules=[]),e.formSettings.rateLimits.rules.push({label:``,maxRequests:200,duration:3,audience:``}),e.formSettings.rateLimits.rules.length==1&&(e.formSettings.rateLimits.enabled=!0)}function o(n){e.formSettings.rateLimits.rules.splice(n,1),e.formSettings.rateLimits.rules.length||(e.formSettings.rateLimits.enabled=!1)}let s=[];return t.details({pbEvent:`rateLimitAccordion`,className:`accordion rate-limit-accordion`,name:`settingsAccordion`,onmount:()=>{s.push(watch(()=>JSON.stringify(e.formSettings.rateLimits.rules),()=>{app.store.errors?.rateLimits?.rules&&delete app.store.errors.rateLimits}))},onunmount:()=>{s.forEach(e=>e?.unwatch())}},t.summary(null,t.i({className:`ri-pulse-fill`,ariaHidden:!0}),t.span({className:`txt`},`Rate limiting`),t.div({className:`flex-fill`}),()=>e.formSettings.rateLimits.enabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.rateLimits))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid sm`},t.div({className:`col-lg-12`},t.div({className:`field`},t.input({id:`rateLimits.enabled`,name:`rateLimits.enabled`,type:`checkbox`,className:`switch`,checked:()=>e.formSettings.rateLimits.enabled||!1,onchange:n=>e.formSettings.rateLimits.enabled=n.target.checked}),t.label({htmlFor:`rateLimits.enabled`},t.span({className:`txt`},`Enable`),t.small({className:`txt-hint`},` (experimental)`)))),t.div({className:`col-lg-12`},t.div({className:`rate-limit-table-wrapper`},t.table({className:`rate-limit-table`},t.thead({hidden:()=>!e.formSettings.rateLimits.rules?.length},t.tr(null,t.th({className:`col-label`},`Rate limit label`),t.th({className:`col-requests`},`Max requests`,t.br(),t.small(null,`(per IP)`)),t.th({className:`col-duration`},`Interval`,t.br(),t.small(null,`(in seconds)`)),t.th({className:`col-audience`},`Targeted users`),t.th({className:`col-action`}))),t.tbody(null,()=>{let i=[],a=e.formSettings.rateLimits.rules||[];for(let e=0;es.label,oninput:e=>s.label=e.target.value}),t.datalist({id:`rateLimits.rules.`+e+`.label_list`},()=>r.predefinedTags.map(e=>t.option({value:e.value},e.label||``))))),t.td({className:`col-requests`},t.div({className:`field`},t.input({type:`number`,required:!0,placeholder:`Max requests*`,className:`inline-error`,min:1,step:1,name:`rateLimits.rules.`+e+`.maxRequests`,value:()=>s.maxRequests||0,oninput:e=>s.maxRequests=parseInt(e.target.value,10)}))),t.td({className:`col-duration`},t.div({className:`field`},t.input({type:`number`,required:!0,placeholder:`Interval*`,className:`inline-error`,min:1,step:1,name:`rateLimits.rules.`+e+`.duration`,value:()=>s.duration,oninput:e=>s.duration=parseInt(e.target.value,10)}))),t.td({className:`col-audience`},t.div({className:`field`},app.components.select({name:`rateLimits.rules.`+e+`.audience`,className:`inline-error`,options:n,required:!0,value:()=>s.audience||``,onchange:e=>{s.audience=e?.[0]?.value}}))),t.td({className:`col-action`},t.button({type:`button`,araiaDescription:app.attrs.tooltip(`Remove rule`),className:`btn sm secondary transparent circle`,onclick:()=>o(e)},t.i({className:`ri-close-line`})))))}return i}))),t.div({className:`flex m-t-sm`},t.button({type:`button`,className:`btn secondary sm`,onclick:()=>a()},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt`},`Add rate limit rule`)),t.button({type:`button`,className:`link-hint txt-sm m-l-auto`,onclick:()=>Gr()},t.em(null,`Learn more about the rate limit rules`))))))}function Yr(e){let n=[`X-Forwarded-For`,`Fly-Client-IP`,`CF-Connecting-IP`],r=[{label:`Use leftmost IP`,value:!0},{label:`Use rightmost IP`,value:!1}],i=store({isLoading:!1,realIP:``,possibleProxyHeader:``,get suggestedProxyHeaders(){return i.possibleProxyHeader?[i.possibleProxyHeader].concat(n.filter(e=>e!=i.possibleProxyHeader)):n},get isEnabled(){return!app.utils.isEmpty(e.formSettings.trustedProxy?.headers)}});async function a(){i.isLoading=!0;try{let e=await app.pb.health.check({requestKey:`loadProxyInfo`});i.realIP=e.data?.realIP||``,i.possibleProxyHeader=e.data?.possibleProxyHeader||``,i.isLoading=!1}catch(e){e.isAbort||(app.checkApiError(e),i.isLoading=!1)}}return t.details({pbEvent:`trustedProxyAccordion`,className:`accordion trusted-proxy-accordion`,name:`settingsAccordion`,onmount:e=>{e._infoWatcher?.unwatch(),e._infoWatcher=watch(()=>JSON.stringify(app.store.settings?.trustedProxy),(e,n)=>{e!=n&&a()})},onunmount:e=>{e._infoWatcher?.unwatch()}},t.summary(null,t.i({className:`ri-route-line`,ariaHidden:!0}),t.span({className:`txt`},`User IP proxy headers`),()=>{if(i.isLoading)return t.span({className:`loader sm`});if(!i.isEnabled&&i.possibleProxyHeader)return t.i({className:`ri-alert-line txt-warning`,ariaDescription:app.attrs.tooltip(`Detected proxy header. +It is recommend to list it as trusted.`,`right`)});if(i.isEnabled&&i.possibleProxyHeader&&!e.formSettings.trustedProxy.headers.includes(i.possibleProxyHeader))return t.i({className:`ri-alert-line txt-hint`,ariaDescription:app.attrs.tooltip(`The configured proxy header doesn't match with the detected one.`,`right`)})},t.div({className:`flex-fill`}),()=>i.isEnabled?t.span({className:`label success`},`Enabled`):t.span({className:`label`},`Disabled`),()=>{if(!app.utils.isEmpty(app.store.errors?.trustedProxy))return t.i({className:`ri-error-warning-fill txt-danger`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.p({className:`m-t-0`},`Below you should see your real IP. If not - configure the correct proxy header for your environment.`),t.div({className:`alert info m-b-sm`},t.div({className:`flex gap-5`},t.span(null,`Resolved user IP:`),t.strong(null,()=>i.isLoading?`...`:i.realIP||`N/A`)),t.div({className:`flex gap-5`},t.span(null,`Detected proxy header:`),t.strong(null,()=>i.isLoading?`...`:i.possibleProxyHeader||`N/A`))),t.div({className:`content m-b-sm`},t.p(null,` When PocketBase is deployed on platforms like Fly or it is accessible through proxies such as NGINX, requests from different users will originate from the same IP address (the IP of the proxy connecting to your PocketBase app). @@ -71,7 +71,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({type:`button`,className:`label sm link-hint`,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 Xr(){app.store.title=`Application settings`;let e=store({isLoading:!1,isSaving:!1,formSettings:null,originalFormSettings:null,get originalFormSettingsHash(){return JSON.stringify(e.originalFormSettings)},get hasChanges(){return e.originalFormSettingsHash!=JSON.stringify(e.formSettings)}});n();async function n(){e.isLoading=!0;try{i(await app.pb.settings.getAll()),e.isLoading=!1}catch(e){e.isAbort||app.checkApiError(e)}}async function r(){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)),app.toasts.success(`Successfully saved application settings.`)}catch(e){app.checkApiError(e)}e.isSaving=!1}}function i(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={meta:n.meta||{},batch:n.batch||{},trustedProxy:n.trustedProxy||{headers:[]},rateLimits:n.rateLimits||{rules:[]}},qr(e.originalFormSettings.rateLimits.rules),e.formSettings=JSON.parse(JSON.stringify(e.originalFormSettings))}function a(){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(),r()}},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:`url`,required:!0,value:()=>e.formSettings.meta.appURL||``,oninput:n=>e.formSettings.meta.appURL=n.target.value}))),t.div({className:`col-md-2`},()=>Zr(e,e.isSaving)),t.div({className:`col-lg-12`},()=>Yr(e),()=>Jr(e),()=>Ur(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 Xr(){app.store.title=`Application settings`;let e=store({isLoading:!1,isSaving:!1,formSettings:null,originalFormSettings:null,get originalFormSettingsHash(){return JSON.stringify(e.originalFormSettings)},get hasChanges(){return e.originalFormSettingsHash!=JSON.stringify(e.formSettings)}});n();async function n(){e.isLoading=!0;try{i(await app.pb.settings.getAll()),e.isLoading=!1}catch(e){e.isAbort||app.checkApiError(e)}}async function r(){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)),app.toasts.success(`Successfully saved application settings.`)}catch(e){app.checkApiError(e)}e.isSaving=!1}}function i(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={meta:n.meta||{},batch:n.batch||{},trustedProxy:n.trustedProxy||{headers:[]},rateLimits:n.rateLimits||{rules:[]}},qr(e.originalFormSettings.rateLimits.rules),e.formSettings=JSON.parse(JSON.stringify(e.originalFormSettings))}function a(){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(),r()}},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:`url`,required:!0,value:()=>e.formSettings.meta.appURL||``,oninput:n=>e.formSettings.meta.appURL=n.target.value}))),t.div({className:`col-md-2`},()=>Zr(e,e.isSaving)),t.div({className:`col-lg-12`},()=>Yr(e),()=>Jr(e),()=>Ur(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:a},t.span({className:`txt`},`Cancel`)),t.button({className:()=>`btn expanded ${e.isSaving?`loading`:``}`,disabled:()=>!e.hasChanges||e.isSaving},t.span({className:`txt`},`Save changes`)))))),t.footer({className:`page-footer`},app.components.credits())))}function Zr(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 Qr(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 e950524f..42857229 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.html @@ -13,7 +13,7 @@ - + diff --git a/ui/src/settings/application/trustedProxyAccordion.js b/ui/src/settings/application/trustedProxyAccordion.js index 91cf37eb..fd5d85b6 100644 --- a/ui/src/settings/application/trustedProxyAccordion.js +++ b/ui/src/settings/application/trustedProxyAccordion.js @@ -24,8 +24,6 @@ export function trustedProxyAccordion(pageData) { }, }); - loadProxyInfo(); - async function loadProxyInfo() { proxyInfo.isLoading = true; @@ -48,7 +46,17 @@ export function trustedProxyAccordion(pageData) { pbEvent: "trustedProxyAccordion", className: "accordion trusted-proxy-accordion", name: "settingsAccordion", - open: () => (proxyInfo.isLoading ? false : null), + onmount: (el) => { + el._infoWatcher?.unwatch(); + el._infoWatcher = watch(() => JSON.stringify(app.store.settings?.trustedProxy), (newHash, oldHash) => { + if (newHash != oldHash) { + loadProxyInfo(); + } + }); + }, + onunmount: (el) => { + el._infoWatcher?.unwatch(); + }, }, t.summary( null, @@ -104,19 +112,16 @@ export function trustedProxyAccordion(pageData) { "Below you should see your real IP. If not - configure the correct proxy header for your environment.", ), t.div( - { - hidden: () => proxyInfo.isLoading, - className: "alert info m-b-sm", - }, + { className: "alert info m-b-sm" }, t.div( { className: "flex gap-5" }, t.span(null, "Resolved user IP:"), - t.strong(null, () => proxyInfo.realIP || "N/A"), + t.strong(null, () => proxyInfo.isLoading ? "..." : (proxyInfo.realIP || "N/A")), ), t.div( { className: "flex gap-5" }, t.span(null, "Detected proxy header:"), - t.strong(null, () => proxyInfo.possibleProxyHeader || "N/A"), + t.strong(null, () => proxyInfo.isLoading ? "..." : (proxyInfo.possibleProxyHeader || "N/A")), ), ), t.div( @@ -198,8 +203,8 @@ export function trustedProxyAccordion(pageData) { t.div({ className: "inline-flex gap-5" }, () => { return proxyInfo.suggestedProxyHeaders.map((header) => { return t.div({ - type: "button", - className: "label sm link-hint", + role: "button", + className: "label sm link-primary", onclick: () => { pageData.formSettings.trustedProxy.headers = [header]; },