From 555a4f1a1ee004af08291092fe0975a56a4c7e9c Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Sun, 26 Apr 2026 16:32:07 +0300 Subject: [PATCH] lowered the default mfa duration and reorganized internal record pre/post handling --- apis/record_auth_with_otp_test.go | 23 +++++--- core/collection_model_auth_options.go | 2 +- core/external_auth_model.go | 38 +++++++++++++ core/mfa_model.go | 11 ++-- core/otp_model.go | 27 +++++++++ core/record_model.go | 57 ++++--------------- .../{index-CP6VPsEr.js => index-DbuYk4bQ.js} | 2 +- ui/dist/index.html | 2 +- ui/package-lock.json | 12 ++-- ui/src/collections/mfaAccordion.js | 2 +- 10 files changed, 107 insertions(+), 69 deletions(-) rename ui/dist/assets/{index-CP6VPsEr.js => index-DbuYk4bQ.js} (99%) diff --git a/apis/record_auth_with_otp_test.go b/apis/record_auth_with_otp_test.go index 8dafbc27..52183e3c 100644 --- a/apis/record_auth_with_otp_test.go +++ b/apis/record_auth_with_otp_test.go @@ -406,10 +406,10 @@ func TestRecordAuthWithOTP(t *testing.T) { "OnModelCreate": 1, "OnModelCreateExecute": 1, "OnModelAfterCreateSuccess": 1, - // OTP delete + 2 ExternalAuth delete - "OnModelDelete": 3, - "OnModelDeleteExecute": 3, - "OnModelAfterDeleteSuccess": 3, + // 2 record OTPs + 2 ExternalAuths delete + "OnModelDelete": 4, + "OnModelDeleteExecute": 4, + "OnModelAfterDeleteSuccess": 4, // user verified update "OnModelUpdate": 1, "OnModelUpdateExecute": 1, @@ -419,9 +419,9 @@ func TestRecordAuthWithOTP(t *testing.T) { "OnRecordCreate": 1, "OnRecordCreateExecute": 1, "OnRecordAfterCreateSuccess": 1, - "OnRecordDelete": 3, - "OnRecordDeleteExecute": 3, - "OnRecordAfterDeleteSuccess": 3, + "OnRecordDelete": 4, + "OnRecordDeleteExecute": 4, + "OnRecordAfterDeleteSuccess": 4, "OnRecordUpdate": 1, "OnRecordUpdateExecute": 1, "OnRecordAfterUpdateSuccess": 1, @@ -436,6 +436,15 @@ func TestRecordAuthWithOTP(t *testing.T) { t.Fatal("Expected the user to be marked as verified") } + // ensure that all pre-existing OTPs are cleared + otps, err := app.FindAllOTPsByRecord(user) + if err != nil { + t.Fatal(err) + } + if len(otps) > 0 { + t.Fatalf("Expected all OTPs to be cleared, found %d", len(otps)) + } + // ensure that all pre-existing OAuth2 links are cleared externalAuths, err := app.FindAllExternalAuthsByRecord(user) if err != nil { diff --git a/core/collection_model_auth_options.go b/core/collection_model_auth_options.go index a0198d06..231ab4cc 100644 --- a/core/collection_model_auth_options.go +++ b/core/collection_model_auth_options.go @@ -60,7 +60,7 @@ func (m *Collection) setDefaultAuthOptions() { }, MFA: MFAConfig{ Enabled: false, - Duration: 1800, // 30min + Duration: 600, // 10min }, OTP: OTPConfig{ Enabled: false, diff --git a/core/external_auth_model.go b/core/external_auth_model.go index 3623dfa7..401cff53 100644 --- a/core/external_auth_model.go +++ b/core/external_auth_model.go @@ -137,4 +137,42 @@ func (app *BaseApp) registerExternalAuthHooks() { }, Priority: 99, }) + + // delete all pre-existing external auths on verified upgrade + app.OnRecordUpdateExecute().Bind(&hook.Handler[*RecordEvent]{ + Func: func(e *RecordEvent) error { + if !e.Record.Collection().IsAuth() { + return e.Next() + } + + hasUpgradedVerified := !e.Record.Original().IsNew() && !e.Record.Original().Verified() && e.Record.Verified() + + if !hasUpgradedVerified { + return e.Next() + } + + originalApp := e.App + return e.App.RunInTransaction(func(txApp App) error { + e.App = txApp + defer func() { e.App = originalApp }() + + externalAuths, err := txApp.FindAllExternalAuthsByRecord(e.Record) + if err != nil { + return err + } + if len(externalAuths) > 0 { + // delete all pre-existing external auths + if err := txApp.DeleteAllExternalAuthsByRecord(e.Record); err != nil { + return err + } + + // force refresh tokens reset (if not already) + e.Record.RefreshTokenKey() + } + + return e.Next() + }) + }, + Priority: 99, + }) } diff --git a/core/mfa_model.go b/core/mfa_model.go index d2d2eb1b..7a1429be 100644 --- a/core/mfa_model.go +++ b/core/mfa_model.go @@ -3,6 +3,7 @@ package core import ( "context" "errors" + "fmt" "time" "github.com/pocketbase/pocketbase/tools/hook" @@ -141,11 +142,11 @@ func (app *BaseApp) registerMFAHooks() { if old != new { err = e.App.DeleteAllMFAsByRecord(e.Record) if err != nil { - e.App.Logger().Warn( - "Failed to delete all previous mfas", - "error", err, - "recordId", e.Record.Id, - "collectionId", e.Record.Collection().Id, + return fmt.Errorf( + "[%s] failed to delete all previos MFAs for record %q: %w", + e.Record.Collection().Name, + e.Record.Id, + err, ) } } diff --git a/core/otp_model.go b/core/otp_model.go index 589ce4a9..385b5013 100644 --- a/core/otp_model.go +++ b/core/otp_model.go @@ -3,8 +3,10 @@ package core import ( "context" "errors" + "fmt" "time" + "github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/types" ) @@ -124,4 +126,29 @@ func (app *BaseApp) registerOTPHooks() { app.Logger().Warn("Failed to delete expired OTP sessions", "error", err) } }) + + // delete all record OTPs on tokenKey change to minimize the risk of hijacking attacks + app.OnRecordUpdateExecute().Bind(&hook.Handler[*RecordEvent]{ + Func: func(e *RecordEvent) error { + err := e.Next() + if err != nil || !e.Record.Collection().IsAuth() { + return err + } + + if e.Record.Original().TokenKey() != e.Record.TokenKey() { + err := e.App.DeleteAllOTPsByRecord(e.Record) + if err != nil { + return fmt.Errorf( + "[%s] failed to delete all previos OTPs for record %q: %w", + e.Record.Collection().Name, + e.Record.Id, + err, + ) + } + } + + return nil + }, + Priority: 99, + }) } diff --git a/core/record_model.go b/core/record_model.go index 443d06ef..7d578f2d 100644 --- a/core/record_model.go +++ b/core/record_model.go @@ -1427,10 +1427,7 @@ func onRecordValidate(e *RecordEvent) error { } func onRecordSaveExecute(e *RecordEvent) error { - var needToDeleteExternalAuths bool - if e.Record.Collection().IsAuth() { - // auth resets to prevent (pre)hijacking vulnerabilities if !e.Record.IsNew() { lastSavedRecord, err := e.App.FindRecordById(e.Record.Collection(), e.Record.Id) if err != nil { @@ -1443,15 +1440,10 @@ func onRecordSaveExecute(e *RecordEvent) error { lastSavedRecord.Email() != e.Record.Email()) { e.Record.RefreshTokenKey() } - - // in case upgrading from "unverified" -> "verified" mark all pre-existing OAuth2 links - // for deletion since there is no reliable way to verify that they weren't created by an attacker - if !lastSavedRecord.Verified() && e.Record.Verified() { - needToDeleteExternalAuths = true - } } - // cross-check that the auth record id is unique across all auth collections + // loosely cross-check that the auth record id is unique across all auth collections + // to minimize impact of mistakes in API rules when multiple auth collections are used authCollections, err := e.App.FindAllCollections(CollectionTypeAuth) if err != nil { return fmt.Errorf("unable to fetch the auth collections for cross-id unique check: %w", err) @@ -1469,45 +1461,16 @@ func onRecordSaveExecute(e *RecordEvent) error { } } - finalizer := func() error { - err := e.Next() - if err == nil { - return nil - } - - return validators.NormalizeUniqueIndexError( - err, - e.Record.Collection().Name, - e.Record.Collection().Fields.FieldNames(), - ) + err := e.Next() + if err == nil { + return nil } - if needToDeleteExternalAuths { - originalApp := e.App - - return e.App.RunInTransaction(func(txApp App) error { - e.App = txApp - defer func() { e.App = originalApp }() - - externalAuths, err := txApp.FindAllExternalAuthsByRecord(e.Record) - if err != nil { - return err - } - if len(externalAuths) > 0 { - // delete all pre-existing external auths - if err := txApp.DeleteAllExternalAuthsByRecord(e.Record); err != nil { - return err - } - - // force refresh tokens reset (if not already) - e.Record.RefreshTokenKey() - } - - return finalizer() - }) - } - - return finalizer() + return validators.NormalizeUniqueIndexError( + err, + e.Record.Collection().Name, + e.Record.Collection().Fields.FieldNames(), + ) } func onRecordDeleteExecute(e *RecordEvent) error { diff --git a/ui/dist/assets/index-CP6VPsEr.js b/ui/dist/assets/index-DbuYk4bQ.js similarity index 99% rename from ui/dist/assets/index-CP6VPsEr.js rename to ui/dist/assets/index-DbuYk4bQ.js index 50ba6d88..27e67f61 100644 --- a/ui/dist/assets/index-CP6VPsEr.js +++ b/ui/dist/assets/index-DbuYk4bQ.js @@ -54,7 +54,7 @@ This field is disabled if "Except domains" is set.`)})),t.input({type:`text`,id: `,` `).split(` `);e.field.values=r,app.utils.deleteByPath(app.store.errors,`fields.${e.fieldIndex}.values`)},onchange:n=>{let r=new Set,i=n.target.value.split(` -`);for(let e of i)e!=``&&r.add(e);e.field.values=Array.from(r)},onblur:e=>{(!e.relatedTarget||!i.contains(e.relatedTarget))&&i.hidePopover()}}))),a=[watch(()=>{e.field.values?.length&&e.field.maxSelect>e.field.values.length&&(e.field.maxSelect=e.field.values.length)})];return app.components.fieldSettings(e,{header:[t.div({className:`field header-select field-select-choices-input`,onunmount:()=>{a.forEach(e=>e?.unwatch())}},t.input({type:`text`,placeholder:`Add choices*`,className:`txt-left inline-error`,value:()=>e.field.values?.join(` • `)||``,name:()=>`fields.${e.fieldIndex}.values`,onfocus:e=>(i?.showPopover({source:e.target}),i.querySelector(`textarea`)?.focus(),!1)}),i),t.div({className:`field header-select single-multiple-select`},app.components.select({required:!0,options:r,value:()=>e.field.maxSelect>1,onchange:n=>{n?.[0]?.value?e.field.maxSelect=e.field.values.length||2:e.field.maxSelect=1}}))],content:()=>t.div({className:`grid sm`},()=>{if(e.field.maxSelect>1)return t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.maxSelect`},`Max select`),t.input({type:`number`,id:n+`.maxSelect`,placeholder:`Default to single`,step:1,min:2,max:()=>e.field.values?.length||2,name:()=>`fields.${e.fieldIndex}.maxSelect`,value:()=>e.field.maxSelect||``,onchange:n=>{let r=parseInt(n.target.value,10);r>1?e.field.maxSelect=r:e.field.maxSelect=1}})))},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`,textContent:()=>e.field.maxSelect>1?`(!=[])`:`(!='')`}),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(()=>`Requires the field value to be nonempty ${e.field.maxSelect>1?`array`:`string`}.`)})))]})}function An(e){return t.div({className:`record-field-view field-type-select`},t.div({className:`inline-flex gap-5`},()=>{let n=app.utils.toArray(e.record[e.field.name],!1);return n.length?n.map(e=>t.span({className:`label`,title:e,textContent:app.utils.truncate(e,100)})):t.span({className:`missing-value`})}))}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.select={icon:`ri-list-check`,label:`Select`,settings:kn,input:On,view:An,filterModifiers:e=>e.maxSelect>1?[`each`,`length`]:[],dummyData:(e,n=!1)=>e.maxSelect>1?e.values?.slice(0,2)||[]:e.values?.[0]||``};function jn(e){let n=`json_`+app.utils.randomString(),r=store({value:``}),i=[watch(()=>e.record[e.field.name],(n,i)=>{if(!(n!==``&&n===r.value)){if(typeof n==`string`&&!n.startsWith(`"`)&&!n.endsWith(`"`)){r.value=JSON.stringify(n===void 0?null:n),e.record[e.field.name]=r.value;return}typeof n==`string`&&n.startsWith(`"`)&&n.endsWith(`"`)?r.value=n:n===null?r.value=`null`:r.value=JSON.stringify(n===void 0?null:n,null,2)}})];function a(){let n=r.value.trim();if(n===``){e.record[e.field.name]=null;return}try{let r=JSON.parse(n);typeof r==`string`?e.record[e.field.name]=JSON.stringify(r):e.record[e.field.name]=r}catch{e.record[e.field.name]=n}}return t.div({className:`record-field-input field-type-json`},t.div({className:`field`,onunmount:()=>{clearTimeout(void 0),i.forEach(e=>e?.unwatch())}},t.label({htmlFor:n},t.i({className:app.fieldTypes.json.icon,ariaHidden:!0}),t.span({className:`txt`},()=>e.field.name),t.span({hidden:()=>Mn(r.value.trim()),className:`json-state`,ariaDescription:app.attrs.tooltip(`Invalid JSON`,`left`)},t.i({className:`ri-error-warning-fill txt-danger`,ariaHidden:!0})),t.span({hidden:()=>!Mn(r.value.trim()),className:`json-state`,ariaDescription:app.attrs.tooltip(`Valid JSON`,`left`)},t.i({className:`ri-checkbox-circle-fill txt-success`,ariaHidden:!0}))),app.components.codeEditor({language:`js`,id:n,name:()=>e.field.name,required:()=>e.field.required,value:()=>r.value,oninput:e=>r.value=e,onblur:()=>a()})),()=>{if(e.field.help)return t.div({className:`field-help`},e.field.help)})}function Mn(e){if(e===``)return!0;try{return JSON.parse(e),!0}catch{return!1}}function Nn(e){try{let n=e.record[e.field.name];typeof n==`string`&&JSON.parse(n)}catch(r){throw new n({status:400,response:{message:`Invalid JSON data`,data:{[e.field.name]:{code:`invalid_json`,message:r.toString()}}}})}}function Pn(e){let n=`f_`+app.utils.randomString(),r=store({showInfo:!1});return app.components.fieldSettings(e,{content:()=>t.div({className:`grid sm`},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.maxSize`},t.span(null,`Max size `),t.small(null,`(bytes)`)),t.input({type:`number`,id:n+`.maxSize`,name:()=>`fields.${e.fieldIndex}.maxSize`,min:0,step:1,max:2**53-1,placeholder:`Default to max ~1MB`,value:()=>e.field.maxSize||``,oninput:n=>{e.field.maxSize=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value}))),t.div({className:`col-sm-12`},t.button({type:`button`,className:()=>`btn sm secondary ${r.showInfo?``:`transparent`}`,onclick:()=>r.showInfo=!r.showInfo},t.span({className:`txt`},`String value normalizations`),t.i({className:()=>r.showInfo?`ri-arrow-up-s-line`:`ri-arrow-down-s-line`,ariaHidden:!0})),app.components.slide(()=>r.showInfo,t.div({className:`alert m-t-10 info`},t.div({className:`content`},`In order to support seamlessly both `,t.code(null,`application/json`),` and `,t.code(null,`multipart/form-data`),`requests, the following normalization rules are applied if the `,t.code(null,`json`),` field is a plain string:`,t.ul(null,t.li(null,`"true" is converted to the json `,t.code(null,`true`)),t.li(null,`"false" is converted to the json `,t.code(null,`false`)),t.li(null,`"null" is converted to the json `,t.code(null,`null`)),t.li(null,`"[1,2,3]" is converted to the json `,t.code(null,`[1,2,3]`)),t.li(null,`'{"a":1,"b":2}' is converted to the json `,t.code(null,`{"a":1,"b":2}`)),t.li(null,`numeric strings are converted to json number`),t.li(null,`double quoted strings are left as they are (aka. without normalizations)`),t.li(null,`any other string (empty string too) is double quoted`)),`Alternatively, if you want to avoid the string value normalizations, you can wrap your data inside an object, eg. `,t.code(null,`{"data": anything}`),`.`))))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value NOT to be null, '', [], {}`)})))]})}function Fn(e){return t.div({className:`record-field-view field-type-json`},()=>{let n=e.record[e.field.name];return e.short?t.span({className:`txt-code txt-ellipsis`,textContent:app.utils.truncate(app.utils.trimQuotedValue(JSON.stringify(n))||``)}):app.components.codeBlock({value:()=>JSON.stringify(n,null,2)})})}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.json={icon:`ri-braces-line`,label:`JSON`,settings:Pn,input:jn,view:Fn,onrecordsave:Nn,dummyData:(e,n=!1)=>({example:123})};function In(e){let n=`geo_`+app.utils.randomString(),r=store({showMap:!1});return t.div({className:`record-field-input field-type-geoPoint`},t.div({className:()=>`field-list ${e.field.required?`required`:``}`},t.label({htmlFor:n},t.i({className:app.fieldTypes.geoPoint.icon,ariaHidden:!0}),t.span({className:`txt`},()=>e.field.name)),t.div({className:`field-list-content`},t.div({className:`field-list-item p-0`},t.div({className:`fields`},t.div({className:`field addon`},t.label({htmlFor:n+`.lon`},`Longitude:`)),t.div({className:`field`},t.input({id:n+`.lon`,type:`number`,step:`any`,min:-180,max:180,placeholder:0,name:()=>e.field.name,required:()=>e.field.required,value:()=>e.record[e.field.name]?.lon||``,onchange:n=>{e.record[e.field.name]=e.record[e.field.name]||{},e.record[e.field.name].lon=Number(n.target.value)}})),t.span({className:`delimiter`}),t.div({className:`field addon`},t.label({htmlFor:n+`.lat`},`Latitude:`)),t.div({className:`field`},t.input({id:n+`.lat`,type:`number`,step:`any`,min:-90,max:90,placeholder:0,name:()=>e.field.name,required:()=>e.field.required,value:()=>e.record[e.field.name]?.lat||``,onchange:n=>{e.record[e.field.name]=e.record[e.field.name]||{},e.record[e.field.name].lat=Number(n.target.value)}})),t.span({className:`delimiter`}),t.div({className:`field addon p-5`},t.button({type:`button`,className:()=>`btn sm circle secondary ${r.showMap?``:`transparent`}`,onclick:()=>r.showMap=!r.showMap},t.i({className:`ri-map-2-line`}))))),()=>{if(r.showMap)return t.div({className:`field-list-item p-0`,style:`height: 250px`},app.components.leaflet({point:()=>e.record[e.field.name]||{lat:0,lon:0},onchange:n=>{e.record[e.field.name]=structuredClone(n)}}))})),()=>{if(e.field.help)return t.div({className:`field-help`},e.field.help)})}function Ln(e){let n=`f_`+app.utils.randomString();return app.components.fieldSettings(e,{content:()=>t.div({className:`grid sm`},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`},`(=true)`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value to be true.`)})))]})}function Rn(e){return t.div({className:`record-field-view field-type-geoPoint`},t.span({className:`label`},()=>{let n=e.record[e.field.name];return`${n?.lon||0}, ${n?.lat||0}`}))}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.geoPoint={icon:`ri-map-pin-2-line`,label:`Geo Point`,settings:Ln,input:In,view:Rn,identifierExtractor:function(e,n=``){return[n+e.name+`.lon`,n+e.name+`.lat`]},dummyData:(e,n=!1)=>({lon:0,lat:0})};function zn(e){let n=`f_`+app.utils.randomString();return app.components.fieldSettings(e,{showHidden:!1,showPresentable:!1,showDuplicate:!1,content:t.div({className:`grid sm`},t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.min`},t.span({className:`txt`},`Min length`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 for no limit.`)})),t.input({type:`number`,id:n+`.min`,name:()=>`fields.${e.fieldIndex}.min`,step:1,min:0,max:71,placeholder:`No min limit`,value:()=>e.field.min||``,oninput:n=>{e.field.min=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.max`},t.span({className:`txt`},`Max length`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 to fallback to the default limit (71).`)})),t.input({type:`number`,id:n+`.max`,name:()=>`fields.${e.fieldIndex}.max`,step:1,min:()=>e.field.min||0,max:71,placeholder:`Up to 71 chars`,value:()=>e.field.max||``,oninput:n=>{e.field.max=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.max`},t.span({className:`txt`},`Bcrypt cost`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 to fallback to the default (10).`)})),t.input({type:`number`,id:n+`.cost`,name:()=>`fields.${e.fieldIndex}.cost`,step:1,min:4,max:31,placeholder:`Default to 10`,value:()=>e.field.cost||``,oninput:n=>{e.field.cost=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.pattern`},t.span({className:`txt`},`Validation pattern`)),t.input({type:`text`,id:n+`.pattern`,placeholder:`ex. ^\\w+$`,name:()=>`fields.${e.fieldIndex}.pattern`,value:()=>e.field.pattern||``,oninput:n=>e.field.pattern=n.target.value}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>{if(!(e.collection?.type==`auth`&&e.field.name==`password`))return[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`},`(!='')`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value to be nonempty string`)})))]}})}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.password={icon:`ri-lock-password-line`,label:`Password`,settings:zn},window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openIndexUpsert=function(e,n=``,r={onsave:()=>{},ondelete:()=>{}}){let i=Y(e,n,r);i&&(document.body.appendChild(i),app.modals.open(i))};function Y(e,n=``,r={}){if(!e){console.warn(`[indexUpsertModal] missing required collection argument`);return}let i,a=app.utils.randomString(),o=store({originalIndex:``,index:``,get isNew(){return o.originalIndex==``},get indexParts(){return app.utils.parseIndex(o.index)},get lowerCasedIndexColumnNames(){return o.indexParts.columns.map(e=>e.name.toLowerCase())},get canSave(){return o.lowerCasedIndexColumnNames.length>0}}),s=e?.fields?.filter(e=>!e[`@toDelete`]&&e.name!=`id`)?.map(e=>e.name)||[];function c(n){if(o.originalIndex=n||``,!n){let r=app.utils.parseIndex(``);r.tableName=e?.name||``,n=app.utils.buildIndex(r)}o.index=n}function l(){if(!e||!o.canSave){console.warn(`[saveIndex] no collection or invalid save state:`,e,o.canSave);return}e.indexes=e.indexes||[];let n=e.indexes.findIndex(e=>e==o.originalIndex);n>=0?(e.indexes[n]=o.index,app.utils.deleteByPath(app.store.errors,`indexes.`+n)):e.indexes.push(o.index),typeof r?.onsave==`function`&&r.onsave({collection:e,index:o.index,oldIndex:o.originalIndex}),f(),app.modals.close(i)}function u(){if(!e||!o.originalIndex){console.warn(`[deleteIndex] no collection or index:`,e,o.originalIndex);return}let n=e.indexes?.findIndex(e=>e==o.originalIndex);if(n==-1){console.warn(`[deleteIndex] missing index:`,o.originalIndex);return}e.indexes.splice(n,1),app.utils.deleteByPath(app.store.errors,`indexes.`+n),typeof r?.ondelete==`function`&&r.ondelete({collection:e,position:n,index:o.originalIndex}),f(),app.modals.close(i)}function d(n){let r=JSON.parse(JSON.stringify(o.indexParts));r.tableName=e?.name||``;let i=n.toLowerCase(),a=r.columns.findIndex(e=>e.name.toLowerCase()==i);a>=0?r.columns.splice(a,1):app.utils.pushUnique(r.columns,{name:n}),o.index=app.utils.buildIndex(r),f()}function f(){if(app.store.errors?.indexes){let n=e.indexes.findIndex(e=>e==o.originalIndex);app.utils.deleteByPath(app.store.errors,`indexes.`+n)}}return i=t.div({className:`modal popup index-upsert-modal`,onbeforeopen:()=>{c(n)},onafteropen:()=>{app.store.errors?.indexes&&(app.store.errors.indexes=JSON.parse(JSON.stringify(app.store.errors.indexes)))},onafterclose:e=>{e?.remove()}},t.header({className:`modal-header`},t.h6({className:`modal-title`},t.span({className:`txt`},()=>o.isNew?`Create index`:`Update index`))),t.div({className:`modal-content`},t.form({id:a+`form`,className:`grid sm index-upsert-form`,onsubmit:e=>{e.preventDefault(),l()}},t.div({className:`col-12`},t.div({className:`field`},t.input({type:`checkbox`,className:`switch`,id:a+`checkbox_unique`,checked:()=>o.indexParts.unique,onchange:n=>{let r=JSON.parse(JSON.stringify(o.indexParts));r.unique=n.target.checked,r.tableName=r.tableName||e?.name||``,o.index=app.utils.buildIndex(r)}}),t.label({htmlFor:a+`checkbox_unique`},`Unique`))),t.div({className:`col-12`},t.div({className:`field`},app.components.codeEditor({required:!0,className:`collection-index-input pre-wrap`,name:()=>`indexes.`+e.indexes?.findIndex(e=>e==o.originalIndex),placeholder:()=>`e.g. CREATE INDEX idx_test on ${e?.name||`X`} (created)`,value:()=>o.index,oninput:e=>o.index=e})),t.div({hidden:()=>!s.length,className:`field-help m-t-sm`},t.div({className:`flex flex-wrap gap-5`},t.span({className:`txt`,textContent:`Presets:`}),()=>s?.map(e=>{let n=o.lowerCasedIndexColumnNames.includes(e.toLowerCase());return t.button({type:`button`,textContent:e,className:()=>`label handle ${n?`success`:``}`,onclick:()=>d(e)})})))))),t.footer({className:`modal-footer gap-base`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close(i)},t.span({className:`txt`},`Close`)),t.button({hidden:()=>o.isNew,type:`button`,className:()=>`btn sm circle transparent secondary`,ariaLabel:app.attrs.tooltip(`Delete index`,`left`),onclick:()=>{app.modals.confirm(`Do you really want to remove the selected index from the collection?`,u)}},t.i({className:`ri-delete-bin-7-line`,ariaHidden:!0})),t.button({type:`submit`,"html-form":a+`form`,disabled:()=>!o.canSave,className:()=>`btn expanded`},t.span({className:`txt`},`Set index`)))),i}function Bn(e,n,r={}){let i=`emailTemplate`+app.utils.randomString(),a=store({title:`Email template`,placeholders:[]}),o=app.utils.extendStore(a,r),s=store({get config(){let r=app.utils.getByPath(e,n);return r||(r={subject:``,body:``},app.utils.setByPath(e,n,r)),r},get tokensList(){return[]}}),c=()=>{if(a.placeholders?.length)return t.div({className:`field-help`},t.div({className:`flex flex-wrap gap-5`},t.span({className:`txt`},`Placeholders:`),()=>a.placeholders.map(e=>t.span({className:`label sm`},app.components.copyButton(e,e)))))};return t.details({pbEvent:`emailTemplateAccordion`,className:`accordion email-template-accordion`,name:`email-template`,onunmount:()=>{o.forEach(e=>e?.unwatch())}},t.summary(null,t.i({className:`ri-draft-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:()=>a.title}),()=>{if(app.utils.getByPath(app.store.errors,n))return t.i({ariaHidden:!0,className:`ri-error-warning-fill txt-danger m-l-auto`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.subject`,textContent:`Subject`}),app.components.codeEditor({id:i+`.subject`,name:n+`.subject`,required:!0,singleLine:!0,language:`text`,autocomplete:a.placeholders,value:()=>s.config.subject||``,oninput:e=>s.config.subject=e})),c),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.body`,textContent:`Body (HTML)`}),app.components.codeEditor({id:i+`.body`,name:n+`.body`,required:!0,language:`html`,className:`pre-wrap`,autocomplete:a.placeholders,value:()=>s.config.body||``,oninput:e=>s.config.body=e})),c)))}function Vn(e){let n=`mfa_`+app.utils.randomString(),r=store({get config(){return e.mfa||={enabled:!1,duration:900,rule:``},e.mfa},get isSuperusers(){return e.system&&e.name==`_superusers`}});return t.details({pbEvent:`mfaAccordion`,name:`auth-methods`,className:`accordion mfa-accordion`},t.summary(null,t.i({className:`ri-shield-check-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Multi-factor authentication (MFA)`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.mfa)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-sm-12`},t.div({className:`alert info`},t.div({className:`content`},t.p(null,`Multi-factor authentication (MFA) requires the user to authenticate with any 2 different auth methods (otp, identity/password, oauth2) before issuing an auth token. `,t.a({href:`https://pocketbase.io/docs/authentication#multi-factor-authentication`,className:`link-hint`,target:`_blank`,rel:`noopener noreferrer`,textContent:`Learn more.`}))))),t.div({className:`col-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`mfa.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:n=>{r.config.enabled=n.target.checked,r.isSuperusers&&(e.otp.enabled=n.target.checked)}}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.duration`,textContent:`Max duration between 2 authentications (in seconds)`}),t.input({type:`number`,id:n+`.duration`,name:`mfa.duration`,min:1,step:1,required:!0,value:()=>r.config.duration||``,oninput:e=>r.config.duration=parseInt(e.target.value,10)}))),t.div({className:`col-sm-12`},app.components.ruleField({label:`MFA rule`,id:n+`.rule`,name:`mfa.rule`,nullable:!1,placeholder:`Leave empty to require MFA for everyone`,autocomplete:n=>app.utils.collectionAutocompleteKeys(e,n),value:()=>r.config.rule||``,oninput:e=>r.config.rule=e}),t.div({className:`field-help`},t.p(null,`This optional rule could be used to enable/disable MFA per account basis.`),t.p(null,`For example, to require MFA only for accounts with non-empty email you can set it to `,t.code(null,`email != ''`),`.`),t.p(null,`Leave the rule empty to require MFA for everyone.`)))))}var Hn=[`id`,`email`,`emailVisibility`,`verified`,`tokenKey`,`password`],Un=[`text`,`editor`,`url`,`email`,`json`];function Wn(e){let n=`oauth2_`+app.utils.randomString(),r=store({get config(){return e.oauth2||={enabled:!1,mappedFields:{},providers:[]},e.oauth2},get regularFieldOptions(){return e.fields?.filter(e=>Un.includes(e.type)&&!Hn.includes(e.name)).map(e=>({value:e.name}))},get regularAndFileFieldOptions(){return e.fields?.filter(e=>(e.type==`file`||Un.includes(e.type))&&!Hn.includes(e.name)).map(e=>({value:e.name}))},showMapping:!1});function i(e){app.utils.deleteByPath(app.store.errors,`oauth2.providers.`+e)}return t.details({pbEvent:`oauth2Accordion`,name:`auth-methods`,className:`accordion oauth2-accordion`},t.summary(null,t.i({className:`ri-profile-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`OAuth2`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(!app.utils.isEmpty(app.store.errors?.oauth2))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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`oauth2.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:e=>r.config.enabled=e.target.checked}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),()=>r.config.providers.map((e,a)=>{let o=n+e.name,s=app.store.oauth2Providers?.find(n=>n.name==e.name)||{};return t.div({className:`col-sm-6`},t.div({className:()=>{let e=`provider-card`;return app.utils.isEmpty(app.store.errors?.oauth2?.providers?.[a])||(e+=` error`),e}},t.figure({className:`provider-logo`},()=>s.logo?t.img({src:`data:image/svg+xml;base64,`+btoa(s.logo),alt:e.name+` logo`}):t.i({className:app.utils.fallbackProviderIcon,ariaHidden:!0})),t.div({className:`content`},t.span({className:`primary-txt`},()=>e.displayName||s.displayName||s.name),t.span({className:`secondary-txt`},()=>e.name||s.name)),t.div({className:`actions`},t.button({type:`button`,title:`Options`,className:`btn secondary transparent sm circle`,"html-popovertarget":o+`dropdown`},t.i({className:`ri-more-2-line`,ariaHidden:!0})),t.div({id:o+`dropdown`,className:`dropdown sm`,popover:`auto`},t.button({type:`button`,className:`dropdown-item`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.modals.openProviderSettings(e,{namePrefix:`oauth2.providers.`+a,onsubmit:(e,n)=>{r.config.providers[a]=n,i(a)}})}},t.span({className:`txt`},`Settings`)),t.hr(),t.button({type:`button`,className:`dropdown-item`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.modals.confirm(`Do you really want to remove provider "${e.displayName||s.displayName||s.name}"?`,()=>{i(a),r.config.providers.splice(a,1),r.config.providers.length==0&&(r.config.enabled=!1)})}},t.span({className:`txt`},`Remove`))))))}),t.div({className:`col-sm-6`},t.button({type:`button`,className:`btn lg block secondary add-provider-btn`,onclick:()=>{app.modals.openProviderPicker({exclude:r.config.providers.map(e=>e.name),onselect:e=>{app.modals.openProviderSettings({name:e.name},{onsubmit:(e,n)=>{r.config.providers.length==0&&(r.config.enabled=!0),r.config.providers.push(n)}})}})}},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt `},`Add provider`))),t.div({className:`col-sm-12`},t.button({type:`button`,className:()=>`btn secondary sm ${r.showMapping?``:`transparent`}`,onclick:()=>r.showMapping=!r.showMapping},t.span({className:`txt`},`Optional users create fields mapping`),t.i({className:()=>r.showMapping?`ri-arrow-drop-up-line`:`ri-arrow-drop-down-line`,ariaHidden:!0})),app.components.slide(()=>r.showMapping,t.div({className:`grid sm m-t-sm`},t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.name`},`OAuth2 full name`),app.components.select({id:n+`.mappedFields.name`,name:`oauth2.mappedFields.name`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.name,onchange:n=>{e.oauth2.mappedFields.name=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.avatarURL`},`OAuth2 avatar`),app.components.select({id:n+`.mappedFields.avatarURL`,name:`oauth2.mappedFields.avatarURL`,placeholder:`Select field`,options:()=>r.regularAndFileFieldOptions,value:()=>e.oauth2.mappedFields.avatarURL,onchange:n=>{e.oauth2.mappedFields.avatarURL=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.id`},`OAuth2 id`),app.components.select({id:n+`.mappedFields.id`,name:`oauth2.mappedFields.id`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.id,onchange:n=>{e.oauth2.mappedFields.id=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.username`},`OAuth2 username`),app.components.select({id:n+`.mappedFields.username`,name:`oauth2.mappedFields.username`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.username,onchange:n=>{e.oauth2.mappedFields.username=n?.[0]?.value||``}}))))))))}function Gn(e){let n=`otp_`+app.utils.randomString(),r=store({get config(){return e.otp||={enabled:!1,duration:300,length:8},e.otp},get isSuperusers(){return e.system&&e.name==`_superusers`}});return t.details({pbEvent:`otpAccordion`,name:`auth-methods`,className:`accordion otp-accordion`},t.summary(null,t.i({className:`ri-time-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`One-time password (OTP)`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.otp)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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`otp.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:n=>{r.config.enabled=n.target.checked,r.isSuperusers&&(e.mfa.enabled=n.target.checked)}}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}),()=>{if(r.isSuperusers)return t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Superusers can have OTP only as part of Two-factor authentication.`)})})),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.duration`,textContent:`Duration (in seconds)`}),t.input({type:`number`,id:n+`.duration`,name:`otp.duration`,min:1,step:1,required:!0,value:()=>r.config.duration||``,oninput:e=>r.config.duration=parseInt(e.target.value,10)}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.length`,textContent:`Generated password length`}),t.input({type:`number`,id:n+`.length`,name:`otp.length`,min:1,step:1,required:!0,value:()=>r.config.length||``,oninput:e=>r.config.length=parseInt(e.target.value,10)})))))}function Kn(e){let n=`passwordAuth_`+app.utils.randomString(),r=store({get config(){return e.passwordAuth||={enabled:!0,identityFields:[`email`]},e.passwordAuth},get identityFieldOptions(){let n=[{value:`email`}],r=e?.fields||[],i=e?.indexes||[];for(let e of i){let i=app.utils.parseIndex(e);if(!i.unique||i.columns.length!=1||i.columns[0].name==`email`)continue;let a=r.find(e=>!e.hidden&&e.name.toLowerCase()==i.columns[0].name.toLowerCase());a&&n.push({value:a.name})}return n}});return t.details({pbEvent:`passwordAuthAccordion`,name:`auth-methods`,className:`accordion password-auth-accordion`},t.summary(null,t.i({className:`ri-lock-password-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Identity/Password`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.passwordAuth)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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`passwordAuth.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:e=>r.config.enabled=e.target.checked}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.identityFields`,textContent:`Identity fields`}),app.components.select({id:n+`.identityFields`,name:`passwordAuth.identityFields`,max:99,required:!0,options:()=>r.identityFieldOptions,value:()=>r.config.identityFields,onchange:e=>{r.config.identityFields=e.map(e=>e.value)}})),t.div({className:`field-help`},`Only non-hidden fields with UNIQUE index constraint can be selected.`))))}function qn(e){let n=`token_`+app.utils.randomString(),r=store({get tokensList(){return e?.name===`_superusers`?[{key:`authToken`,label:`Auth`},{key:`passwordResetToken`,label:`Password reset`},{key:`fileToken`,label:`Protected file`}]:[{key:`authToken`,label:`Auth`},{key:`verificationToken`,label:`Email verification`},{key:`passwordResetToken`,label:`Password reset`},{key:`emailChangeToken`,label:`Email change`},{key:`fileToken`,label:`Protected file`}]}});return t.details({pbEvent:`tokenOptionsAccordion`,name:`other`,className:`accordion token-options-accordion`},t.summary(null,t.i({className:`ri-key-2-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Token options (invalidate, duration)`})),t.div({className:`grid sm`},()=>r.tokensList.map(r=>{let i=n+r.key;return t.div({className:`col-sm-6`},t.div({className:`field token-field`},t.label({htmlFor:i,textContent:()=>r.label+` duration (in seconds)`}),t.input({id:i,type:`number`,min:1,step:1,required:!0,name:()=>r.key+`.duration`,value:()=>e[r.key].duration,oninput:n=>e[r.key].duration=parseInt(n.target.value,10)})),t.div({className:`field-help m-b-10`},t.button({type:`button`,className:()=>`link-hint ${e[r.key].secret?`txt-success`:``}`,textContent:`Invalidate all previously issued tokens`,onclick:()=>{e[r.key].secret?delete e[r.key].secret:e[r.key].secret=app.utils.randomSecret(50)}})))})))}function Jn(e){let n=`options_`+app.utils.randomString();return t.div({className:`collection-tab-content collection-options-tab-content`},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Auth methods`),t.div({className:`flex-fill`}),t.div({className:`field`},t.input({id:n+`.authAlert`,name:`authAlert.enabled`,type:`checkbox`,className:`switch sm`,checked:()=>!!e.collection.authAlert?.enabled,onchange:n=>{e.collection.authAlert=e.collection.authAlert||{},e.collection.authAlert.enabled=n.target.checked}}),t.label({htmlFor:n+`.authAlert`},`Send email alert for new logins`))),Kn(e.collection),()=>{if(e.originalCollection?.name!=`_superusers`)return Wn(e.collection)},Gn(e.collection),Vn(e.collection)),t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Mail templates`),t.button({tabIndex:-1,type:`buttton`,className:`m-l-auto label handle txt-bold`,textContent:`Send test email`,onclick:()=>app.modals.openMailTest(e.collection?.name)})),Bn(e.collection,`verificationTemplate`,{title:`Default Verification email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`resetPasswordTemplate`,{title:`Default Password reset email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`confirmEmailChangeTemplate`,{title:`Default Confirm email change email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`otp.emailTemplate`,{title:`Default OTP email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{OTP}`,`{OTP_ID}`]}),Bn(e.collection,`authAlert.emailTemplate`,{title:`Default Login alert email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{ALERT_INFO}`]})),t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Other`)),qn(e.collection))))}function X(e){return t.div({className:`collection-tab-content collection-fields-tab-content`},t.div({className:`collection-fields-list`},app.components.sortable({handle:`.sort-handle`,data:()=>(e.collection?.fields||[])?.filter(e=>!!app.fieldTypes[e.type]?.settings),dataItem:(n,r)=>app.fieldTypes[n.type].settings({field:n,get originalCollection(){return e.originalCollection},get collection(){return e.collection},get originalField(){return e.originalCollection?.fields?.find(e=>n.id&&e.id==n.id)},get fieldIndex(){return e.collection.fields?.findIndex(e=>n.id?e.id==n.id:e==n)}}),onchange:(n,r,i)=>{e.collection.fields=n}})),()=>app.components.addCollectionFieldButton(e.collection),t.hr(),t.p({className:`txt-bold`},`Unique constraints and indexes (`,()=>e.collection.indexes?.length,`)`),app.components.sortable({className:`indexes-list`,data:()=>e.collection.indexes||[],onchange:function(n){e.collection.indexes=n},dataItem:(n,r)=>{let i=app.utils.parseIndex(n);return t.button({type:`button`,className:()=>`label handle ${app.store.errors?.indexes?.[r]?.message?`danger error`:`success`}`,ariaDescription:app.attrs.tooltip(()=>app.store.errors?.indexes?.[r]?.message||``),onclick:()=>app.modals.openIndexUpsert(e.collection,n)},()=>{if(i.unique)return t.strong(null,`Unique:`)},t.span({className:`txt`},()=>i.columns?.map(e=>e.name).join(`, `)))},after:()=>t.button({type:`button`,className:`label handle`,onclick:()=>app.modals.openIndexUpsert(e.collection)},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt`},`New index`))}))}function Yn(e){let n=store({showRulesInfo:!1,showAuthRules:!1}),r=()=>app.attrs.tooltip(e.originalCollection?.system?`System collection rule cannot be changed.`:null,`top-left`);function i(n){return app.utils.collectionAutocompleteKeys(e.collection,n)}return t.div({className:`collection-tab-content collection-rules-tab-content`},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`flex txt-hint txt-sm`},t.span({className:`txt`},`All rules follow the `,t.a({target:`_blank`,rel:`noopener noreferrer`,href:`https://pocketbase.io/docs/api-rules-and-filters`,textContent:`PocketBase filter syntax and operators`}),`.`),t.strong({tabIndex:-1,className:`m-l-auto link-hint`,textContent:()=>n.showRulesInfo?`Hide available fields`:`Show available fields`,onclick:()=>n.showRulesInfo=!n.showRulesInfo})),app.components.slide(()=>n.showRulesInfo,t.div({className:`alert warning m-t-sm`},t.div({className:`content`},t.p(null,`The following record fields are available:`),t.div({className:`flex flex-wrap gap-5`},()=>app.utils.getAllCollectionIdentifiers(e.collection).map(e=>t.code(null,e))),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`The request fields could be accessed with the special `,t.strong(null,`@request`),` fields:`),t.div({className:`flex flex-wrap gap-5`},t.code(null,`@request.headers.*`),t.code(null,`@request.query.*`),t.code(null,`@request.body.*`),t.code(null,`@request.auth.*`)),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`You could also add constraints and query other collections using the `,t.strong(null,`@collection`),` field:`),t.div({className:`flex flex-wrap gap-5`},t.code(null,`@collection.ANY_COLLECTION_NAME.*`)),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`Example rule:`),()=>{let n=e.collection.fields?.find(e=>e.type==`date`||e.type==`autodate`);return n?t.code(null,`@request.auth.id != "" && ${n.name} > "2022-01-01 00:00:00.000Z"`):t.code(null,`@request.auth.id != ""`)})))),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`List/Search rule`,name:`listRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.listRule,oninput:n=>e.collection.listRule=n})),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`View rule`,name:`viewRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.viewRule,oninput:n=>e.collection.viewRule=n})),()=>{if(e.collection.type!=`view`)return[t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:[t.span({className:`txt`,textContent:`Create rule`}),t.i({hidden:()=>e.collection.createRule==null,className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`The main record fields hold the values that are going to be inserted in the database.`)})],name:`createRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.createRule,oninput:n=>e.collection.createRule=n})),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:[t.span({className:`txt`,textContent:`Update rule`}),t.i({hidden:()=>e.collection.updateRule==null,className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`The main record fields hold the old/existing record field values. +`);for(let e of i)e!=``&&r.add(e);e.field.values=Array.from(r)},onblur:e=>{(!e.relatedTarget||!i.contains(e.relatedTarget))&&i.hidePopover()}}))),a=[watch(()=>{e.field.values?.length&&e.field.maxSelect>e.field.values.length&&(e.field.maxSelect=e.field.values.length)})];return app.components.fieldSettings(e,{header:[t.div({className:`field header-select field-select-choices-input`,onunmount:()=>{a.forEach(e=>e?.unwatch())}},t.input({type:`text`,placeholder:`Add choices*`,className:`txt-left inline-error`,value:()=>e.field.values?.join(` • `)||``,name:()=>`fields.${e.fieldIndex}.values`,onfocus:e=>(i?.showPopover({source:e.target}),i.querySelector(`textarea`)?.focus(),!1)}),i),t.div({className:`field header-select single-multiple-select`},app.components.select({required:!0,options:r,value:()=>e.field.maxSelect>1,onchange:n=>{n?.[0]?.value?e.field.maxSelect=e.field.values.length||2:e.field.maxSelect=1}}))],content:()=>t.div({className:`grid sm`},()=>{if(e.field.maxSelect>1)return t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.maxSelect`},`Max select`),t.input({type:`number`,id:n+`.maxSelect`,placeholder:`Default to single`,step:1,min:2,max:()=>e.field.values?.length||2,name:()=>`fields.${e.fieldIndex}.maxSelect`,value:()=>e.field.maxSelect||``,onchange:n=>{let r=parseInt(n.target.value,10);r>1?e.field.maxSelect=r:e.field.maxSelect=1}})))},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`,textContent:()=>e.field.maxSelect>1?`(!=[])`:`(!='')`}),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(()=>`Requires the field value to be nonempty ${e.field.maxSelect>1?`array`:`string`}.`)})))]})}function An(e){return t.div({className:`record-field-view field-type-select`},t.div({className:`inline-flex gap-5`},()=>{let n=app.utils.toArray(e.record[e.field.name],!1);return n.length?n.map(e=>t.span({className:`label`,title:e,textContent:app.utils.truncate(e,100)})):t.span({className:`missing-value`})}))}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.select={icon:`ri-list-check`,label:`Select`,settings:kn,input:On,view:An,filterModifiers:e=>e.maxSelect>1?[`each`,`length`]:[],dummyData:(e,n=!1)=>e.maxSelect>1?e.values?.slice(0,2)||[]:e.values?.[0]||``};function jn(e){let n=`json_`+app.utils.randomString(),r=store({value:``}),i=[watch(()=>e.record[e.field.name],(n,i)=>{if(!(n!==``&&n===r.value)){if(typeof n==`string`&&!n.startsWith(`"`)&&!n.endsWith(`"`)){r.value=JSON.stringify(n===void 0?null:n),e.record[e.field.name]=r.value;return}typeof n==`string`&&n.startsWith(`"`)&&n.endsWith(`"`)?r.value=n:n===null?r.value=`null`:r.value=JSON.stringify(n===void 0?null:n,null,2)}})];function a(){let n=r.value.trim();if(n===``){e.record[e.field.name]=null;return}try{let r=JSON.parse(n);typeof r==`string`?e.record[e.field.name]=JSON.stringify(r):e.record[e.field.name]=r}catch{e.record[e.field.name]=n}}return t.div({className:`record-field-input field-type-json`},t.div({className:`field`,onunmount:()=>{clearTimeout(void 0),i.forEach(e=>e?.unwatch())}},t.label({htmlFor:n},t.i({className:app.fieldTypes.json.icon,ariaHidden:!0}),t.span({className:`txt`},()=>e.field.name),t.span({hidden:()=>Mn(r.value.trim()),className:`json-state`,ariaDescription:app.attrs.tooltip(`Invalid JSON`,`left`)},t.i({className:`ri-error-warning-fill txt-danger`,ariaHidden:!0})),t.span({hidden:()=>!Mn(r.value.trim()),className:`json-state`,ariaDescription:app.attrs.tooltip(`Valid JSON`,`left`)},t.i({className:`ri-checkbox-circle-fill txt-success`,ariaHidden:!0}))),app.components.codeEditor({language:`js`,id:n,name:()=>e.field.name,required:()=>e.field.required,value:()=>r.value,oninput:e=>r.value=e,onblur:()=>a()})),()=>{if(e.field.help)return t.div({className:`field-help`},e.field.help)})}function Mn(e){if(e===``)return!0;try{return JSON.parse(e),!0}catch{return!1}}function Nn(e){try{let n=e.record[e.field.name];typeof n==`string`&&JSON.parse(n)}catch(r){throw new n({status:400,response:{message:`Invalid JSON data`,data:{[e.field.name]:{code:`invalid_json`,message:r.toString()}}}})}}function Pn(e){let n=`f_`+app.utils.randomString(),r=store({showInfo:!1});return app.components.fieldSettings(e,{content:()=>t.div({className:`grid sm`},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.maxSize`},t.span(null,`Max size `),t.small(null,`(bytes)`)),t.input({type:`number`,id:n+`.maxSize`,name:()=>`fields.${e.fieldIndex}.maxSize`,min:0,step:1,max:2**53-1,placeholder:`Default to max ~1MB`,value:()=>e.field.maxSize||``,oninput:n=>{e.field.maxSize=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value}))),t.div({className:`col-sm-12`},t.button({type:`button`,className:()=>`btn sm secondary ${r.showInfo?``:`transparent`}`,onclick:()=>r.showInfo=!r.showInfo},t.span({className:`txt`},`String value normalizations`),t.i({className:()=>r.showInfo?`ri-arrow-up-s-line`:`ri-arrow-down-s-line`,ariaHidden:!0})),app.components.slide(()=>r.showInfo,t.div({className:`alert m-t-10 info`},t.div({className:`content`},`In order to support seamlessly both `,t.code(null,`application/json`),` and `,t.code(null,`multipart/form-data`),`requests, the following normalization rules are applied if the `,t.code(null,`json`),` field is a plain string:`,t.ul(null,t.li(null,`"true" is converted to the json `,t.code(null,`true`)),t.li(null,`"false" is converted to the json `,t.code(null,`false`)),t.li(null,`"null" is converted to the json `,t.code(null,`null`)),t.li(null,`"[1,2,3]" is converted to the json `,t.code(null,`[1,2,3]`)),t.li(null,`'{"a":1,"b":2}' is converted to the json `,t.code(null,`{"a":1,"b":2}`)),t.li(null,`numeric strings are converted to json number`),t.li(null,`double quoted strings are left as they are (aka. without normalizations)`),t.li(null,`any other string (empty string too) is double quoted`)),`Alternatively, if you want to avoid the string value normalizations, you can wrap your data inside an object, eg. `,t.code(null,`{"data": anything}`),`.`))))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value NOT to be null, '', [], {}`)})))]})}function Fn(e){return t.div({className:`record-field-view field-type-json`},()=>{let n=e.record[e.field.name];return e.short?t.span({className:`txt-code txt-ellipsis`,textContent:app.utils.truncate(app.utils.trimQuotedValue(JSON.stringify(n))||``)}):app.components.codeBlock({value:()=>JSON.stringify(n,null,2)})})}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.json={icon:`ri-braces-line`,label:`JSON`,settings:Pn,input:jn,view:Fn,onrecordsave:Nn,dummyData:(e,n=!1)=>({example:123})};function In(e){let n=`geo_`+app.utils.randomString(),r=store({showMap:!1});return t.div({className:`record-field-input field-type-geoPoint`},t.div({className:()=>`field-list ${e.field.required?`required`:``}`},t.label({htmlFor:n},t.i({className:app.fieldTypes.geoPoint.icon,ariaHidden:!0}),t.span({className:`txt`},()=>e.field.name)),t.div({className:`field-list-content`},t.div({className:`field-list-item p-0`},t.div({className:`fields`},t.div({className:`field addon`},t.label({htmlFor:n+`.lon`},`Longitude:`)),t.div({className:`field`},t.input({id:n+`.lon`,type:`number`,step:`any`,min:-180,max:180,placeholder:0,name:()=>e.field.name,required:()=>e.field.required,value:()=>e.record[e.field.name]?.lon||``,onchange:n=>{e.record[e.field.name]=e.record[e.field.name]||{},e.record[e.field.name].lon=Number(n.target.value)}})),t.span({className:`delimiter`}),t.div({className:`field addon`},t.label({htmlFor:n+`.lat`},`Latitude:`)),t.div({className:`field`},t.input({id:n+`.lat`,type:`number`,step:`any`,min:-90,max:90,placeholder:0,name:()=>e.field.name,required:()=>e.field.required,value:()=>e.record[e.field.name]?.lat||``,onchange:n=>{e.record[e.field.name]=e.record[e.field.name]||{},e.record[e.field.name].lat=Number(n.target.value)}})),t.span({className:`delimiter`}),t.div({className:`field addon p-5`},t.button({type:`button`,className:()=>`btn sm circle secondary ${r.showMap?``:`transparent`}`,onclick:()=>r.showMap=!r.showMap},t.i({className:`ri-map-2-line`}))))),()=>{if(r.showMap)return t.div({className:`field-list-item p-0`,style:`height: 250px`},app.components.leaflet({point:()=>e.record[e.field.name]||{lat:0,lon:0},onchange:n=>{e.record[e.field.name]=structuredClone(n)}}))})),()=>{if(e.field.help)return t.div({className:`field-help`},e.field.help)})}function Ln(e){let n=`f_`+app.utils.randomString();return app.components.fieldSettings(e,{content:()=>t.div({className:`grid sm`},t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`},`(=true)`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value to be true.`)})))]})}function Rn(e){return t.div({className:`record-field-view field-type-geoPoint`},t.span({className:`label`},()=>{let n=e.record[e.field.name];return`${n?.lon||0}, ${n?.lat||0}`}))}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.geoPoint={icon:`ri-map-pin-2-line`,label:`Geo Point`,settings:Ln,input:In,view:Rn,identifierExtractor:function(e,n=``){return[n+e.name+`.lon`,n+e.name+`.lat`]},dummyData:(e,n=!1)=>({lon:0,lat:0})};function zn(e){let n=`f_`+app.utils.randomString();return app.components.fieldSettings(e,{showHidden:!1,showPresentable:!1,showDuplicate:!1,content:t.div({className:`grid sm`},t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.min`},t.span({className:`txt`},`Min length`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 for no limit.`)})),t.input({type:`number`,id:n+`.min`,name:()=>`fields.${e.fieldIndex}.min`,step:1,min:0,max:71,placeholder:`No min limit`,value:()=>e.field.min||``,oninput:n=>{e.field.min=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.max`},t.span({className:`txt`},`Max length`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 to fallback to the default limit (71).`)})),t.input({type:`number`,id:n+`.max`,name:()=>`fields.${e.fieldIndex}.max`,step:1,min:()=>e.field.min||0,max:71,placeholder:`Up to 71 chars`,value:()=>e.field.max||``,oninput:n=>{e.field.max=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.max`},t.span({className:`txt`},`Bcrypt cost`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Clear the field or set it to 0 to fallback to the default (10).`)})),t.input({type:`number`,id:n+`.cost`,name:()=>`fields.${e.fieldIndex}.cost`,step:1,min:4,max:31,placeholder:`Default to 10`,value:()=>e.field.cost||``,oninput:n=>{e.field.cost=parseInt(n.target.value,10)}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.pattern`},t.span({className:`txt`},`Validation pattern`)),t.input({type:`text`,id:n+`.pattern`,placeholder:`ex. ^\\w+$`,name:()=>`fields.${e.fieldIndex}.pattern`,value:()=>e.field.pattern||``,oninput:n=>e.field.pattern=n.target.value}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.help`},`Help text`),t.input({type:`text`,id:n+`.help`,name:()=>`fields.${e.fieldIndex}.help`,value:()=>e.field.help||``,oninput:n=>e.field.help=n.target.value})))),footer:()=>{if(!(e.collection?.type==`auth`&&e.field.name==`password`))return[t.div({className:`field`},t.input({className:`sm`,type:`checkbox`,id:n+`.required`,name:()=>`fields.${e.fieldIndex}.required`,checked:()=>!!e.field.required,onchange:n=>e.field.required=n.target.checked}),t.label({htmlFor:n+`.required`},t.span({className:`txt`},`Required`),t.small({className:`txt-hint`},`(!='')`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Requires the field value to be nonempty string`)})))]}})}window.app=window.app||{},window.app.fieldTypes=window.app.fieldTypes||{},window.app.fieldTypes.password={icon:`ri-lock-password-line`,label:`Password`,settings:zn},window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openIndexUpsert=function(e,n=``,r={onsave:()=>{},ondelete:()=>{}}){let i=Y(e,n,r);i&&(document.body.appendChild(i),app.modals.open(i))};function Y(e,n=``,r={}){if(!e){console.warn(`[indexUpsertModal] missing required collection argument`);return}let i,a=app.utils.randomString(),o=store({originalIndex:``,index:``,get isNew(){return o.originalIndex==``},get indexParts(){return app.utils.parseIndex(o.index)},get lowerCasedIndexColumnNames(){return o.indexParts.columns.map(e=>e.name.toLowerCase())},get canSave(){return o.lowerCasedIndexColumnNames.length>0}}),s=e?.fields?.filter(e=>!e[`@toDelete`]&&e.name!=`id`)?.map(e=>e.name)||[];function c(n){if(o.originalIndex=n||``,!n){let r=app.utils.parseIndex(``);r.tableName=e?.name||``,n=app.utils.buildIndex(r)}o.index=n}function l(){if(!e||!o.canSave){console.warn(`[saveIndex] no collection or invalid save state:`,e,o.canSave);return}e.indexes=e.indexes||[];let n=e.indexes.findIndex(e=>e==o.originalIndex);n>=0?(e.indexes[n]=o.index,app.utils.deleteByPath(app.store.errors,`indexes.`+n)):e.indexes.push(o.index),typeof r?.onsave==`function`&&r.onsave({collection:e,index:o.index,oldIndex:o.originalIndex}),f(),app.modals.close(i)}function u(){if(!e||!o.originalIndex){console.warn(`[deleteIndex] no collection or index:`,e,o.originalIndex);return}let n=e.indexes?.findIndex(e=>e==o.originalIndex);if(n==-1){console.warn(`[deleteIndex] missing index:`,o.originalIndex);return}e.indexes.splice(n,1),app.utils.deleteByPath(app.store.errors,`indexes.`+n),typeof r?.ondelete==`function`&&r.ondelete({collection:e,position:n,index:o.originalIndex}),f(),app.modals.close(i)}function d(n){let r=JSON.parse(JSON.stringify(o.indexParts));r.tableName=e?.name||``;let i=n.toLowerCase(),a=r.columns.findIndex(e=>e.name.toLowerCase()==i);a>=0?r.columns.splice(a,1):app.utils.pushUnique(r.columns,{name:n}),o.index=app.utils.buildIndex(r),f()}function f(){if(app.store.errors?.indexes){let n=e.indexes.findIndex(e=>e==o.originalIndex);app.utils.deleteByPath(app.store.errors,`indexes.`+n)}}return i=t.div({className:`modal popup index-upsert-modal`,onbeforeopen:()=>{c(n)},onafteropen:()=>{app.store.errors?.indexes&&(app.store.errors.indexes=JSON.parse(JSON.stringify(app.store.errors.indexes)))},onafterclose:e=>{e?.remove()}},t.header({className:`modal-header`},t.h6({className:`modal-title`},t.span({className:`txt`},()=>o.isNew?`Create index`:`Update index`))),t.div({className:`modal-content`},t.form({id:a+`form`,className:`grid sm index-upsert-form`,onsubmit:e=>{e.preventDefault(),l()}},t.div({className:`col-12`},t.div({className:`field`},t.input({type:`checkbox`,className:`switch`,id:a+`checkbox_unique`,checked:()=>o.indexParts.unique,onchange:n=>{let r=JSON.parse(JSON.stringify(o.indexParts));r.unique=n.target.checked,r.tableName=r.tableName||e?.name||``,o.index=app.utils.buildIndex(r)}}),t.label({htmlFor:a+`checkbox_unique`},`Unique`))),t.div({className:`col-12`},t.div({className:`field`},app.components.codeEditor({required:!0,className:`collection-index-input pre-wrap`,name:()=>`indexes.`+e.indexes?.findIndex(e=>e==o.originalIndex),placeholder:()=>`e.g. CREATE INDEX idx_test on ${e?.name||`X`} (created)`,value:()=>o.index,oninput:e=>o.index=e})),t.div({hidden:()=>!s.length,className:`field-help m-t-sm`},t.div({className:`flex flex-wrap gap-5`},t.span({className:`txt`,textContent:`Presets:`}),()=>s?.map(e=>{let n=o.lowerCasedIndexColumnNames.includes(e.toLowerCase());return t.button({type:`button`,textContent:e,className:()=>`label handle ${n?`success`:``}`,onclick:()=>d(e)})})))))),t.footer({className:`modal-footer gap-base`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close(i)},t.span({className:`txt`},`Close`)),t.button({hidden:()=>o.isNew,type:`button`,className:()=>`btn sm circle transparent secondary`,ariaLabel:app.attrs.tooltip(`Delete index`,`left`),onclick:()=>{app.modals.confirm(`Do you really want to remove the selected index from the collection?`,u)}},t.i({className:`ri-delete-bin-7-line`,ariaHidden:!0})),t.button({type:`submit`,"html-form":a+`form`,disabled:()=>!o.canSave,className:()=>`btn expanded`},t.span({className:`txt`},`Set index`)))),i}function Bn(e,n,r={}){let i=`emailTemplate`+app.utils.randomString(),a=store({title:`Email template`,placeholders:[]}),o=app.utils.extendStore(a,r),s=store({get config(){let r=app.utils.getByPath(e,n);return r||(r={subject:``,body:``},app.utils.setByPath(e,n,r)),r},get tokensList(){return[]}}),c=()=>{if(a.placeholders?.length)return t.div({className:`field-help`},t.div({className:`flex flex-wrap gap-5`},t.span({className:`txt`},`Placeholders:`),()=>a.placeholders.map(e=>t.span({className:`label sm`},app.components.copyButton(e,e)))))};return t.details({pbEvent:`emailTemplateAccordion`,className:`accordion email-template-accordion`,name:`email-template`,onunmount:()=>{o.forEach(e=>e?.unwatch())}},t.summary(null,t.i({className:`ri-draft-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:()=>a.title}),()=>{if(app.utils.getByPath(app.store.errors,n))return t.i({ariaHidden:!0,className:`ri-error-warning-fill txt-danger m-l-auto`,ariaDescription:app.attrs.tooltip(`Has errors`,`left`)})}),t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.subject`,textContent:`Subject`}),app.components.codeEditor({id:i+`.subject`,name:n+`.subject`,required:!0,singleLine:!0,language:`text`,autocomplete:a.placeholders,value:()=>s.config.subject||``,oninput:e=>s.config.subject=e})),c),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.body`,textContent:`Body (HTML)`}),app.components.codeEditor({id:i+`.body`,name:n+`.body`,required:!0,language:`html`,className:`pre-wrap`,autocomplete:a.placeholders,value:()=>s.config.body||``,oninput:e=>s.config.body=e})),c)))}function Vn(e){let n=`mfa_`+app.utils.randomString(),r=store({get config(){return e.mfa||={enabled:!1,duration:600,rule:``},e.mfa},get isSuperusers(){return e.system&&e.name==`_superusers`}});return t.details({pbEvent:`mfaAccordion`,name:`auth-methods`,className:`accordion mfa-accordion`},t.summary(null,t.i({className:`ri-shield-check-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Multi-factor authentication (MFA)`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.mfa)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-sm-12`},t.div({className:`alert info`},t.div({className:`content`},t.p(null,`Multi-factor authentication (MFA) requires the user to authenticate with any 2 different auth methods (otp, identity/password, oauth2) before issuing an auth token. `,t.a({href:`https://pocketbase.io/docs/authentication#multi-factor-authentication`,className:`link-hint`,target:`_blank`,rel:`noopener noreferrer`,textContent:`Learn more.`}))))),t.div({className:`col-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`mfa.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:n=>{r.config.enabled=n.target.checked,r.isSuperusers&&(e.otp.enabled=n.target.checked)}}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.duration`,textContent:`Max duration between 2 authentications (in seconds)`}),t.input({type:`number`,id:n+`.duration`,name:`mfa.duration`,min:1,step:1,required:!0,value:()=>r.config.duration||``,oninput:e=>r.config.duration=parseInt(e.target.value,10)}))),t.div({className:`col-sm-12`},app.components.ruleField({label:`MFA rule`,id:n+`.rule`,name:`mfa.rule`,nullable:!1,placeholder:`Leave empty to require MFA for everyone`,autocomplete:n=>app.utils.collectionAutocompleteKeys(e,n),value:()=>r.config.rule||``,oninput:e=>r.config.rule=e}),t.div({className:`field-help`},t.p(null,`This optional rule could be used to enable/disable MFA per account basis.`),t.p(null,`For example, to require MFA only for accounts with non-empty email you can set it to `,t.code(null,`email != ''`),`.`),t.p(null,`Leave the rule empty to require MFA for everyone.`)))))}var Hn=[`id`,`email`,`emailVisibility`,`verified`,`tokenKey`,`password`],Un=[`text`,`editor`,`url`,`email`,`json`];function Wn(e){let n=`oauth2_`+app.utils.randomString(),r=store({get config(){return e.oauth2||={enabled:!1,mappedFields:{},providers:[]},e.oauth2},get regularFieldOptions(){return e.fields?.filter(e=>Un.includes(e.type)&&!Hn.includes(e.name)).map(e=>({value:e.name}))},get regularAndFileFieldOptions(){return e.fields?.filter(e=>(e.type==`file`||Un.includes(e.type))&&!Hn.includes(e.name)).map(e=>({value:e.name}))},showMapping:!1});function i(e){app.utils.deleteByPath(app.store.errors,`oauth2.providers.`+e)}return t.details({pbEvent:`oauth2Accordion`,name:`auth-methods`,className:`accordion oauth2-accordion`},t.summary(null,t.i({className:`ri-profile-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`OAuth2`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(!app.utils.isEmpty(app.store.errors?.oauth2))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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`oauth2.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:e=>r.config.enabled=e.target.checked}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),()=>r.config.providers.map((e,a)=>{let o=n+e.name,s=app.store.oauth2Providers?.find(n=>n.name==e.name)||{};return t.div({className:`col-sm-6`},t.div({className:()=>{let e=`provider-card`;return app.utils.isEmpty(app.store.errors?.oauth2?.providers?.[a])||(e+=` error`),e}},t.figure({className:`provider-logo`},()=>s.logo?t.img({src:`data:image/svg+xml;base64,`+btoa(s.logo),alt:e.name+` logo`}):t.i({className:app.utils.fallbackProviderIcon,ariaHidden:!0})),t.div({className:`content`},t.span({className:`primary-txt`},()=>e.displayName||s.displayName||s.name),t.span({className:`secondary-txt`},()=>e.name||s.name)),t.div({className:`actions`},t.button({type:`button`,title:`Options`,className:`btn secondary transparent sm circle`,"html-popovertarget":o+`dropdown`},t.i({className:`ri-more-2-line`,ariaHidden:!0})),t.div({id:o+`dropdown`,className:`dropdown sm`,popover:`auto`},t.button({type:`button`,className:`dropdown-item`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.modals.openProviderSettings(e,{namePrefix:`oauth2.providers.`+a,onsubmit:(e,n)=>{r.config.providers[a]=n,i(a)}})}},t.span({className:`txt`},`Settings`)),t.hr(),t.button({type:`button`,className:`dropdown-item`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.modals.confirm(`Do you really want to remove provider "${e.displayName||s.displayName||s.name}"?`,()=>{i(a),r.config.providers.splice(a,1),r.config.providers.length==0&&(r.config.enabled=!1)})}},t.span({className:`txt`},`Remove`))))))}),t.div({className:`col-sm-6`},t.button({type:`button`,className:`btn lg block secondary add-provider-btn`,onclick:()=>{app.modals.openProviderPicker({exclude:r.config.providers.map(e=>e.name),onselect:e=>{app.modals.openProviderSettings({name:e.name},{onsubmit:(e,n)=>{r.config.providers.length==0&&(r.config.enabled=!0),r.config.providers.push(n)}})}})}},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt `},`Add provider`))),t.div({className:`col-sm-12`},t.button({type:`button`,className:()=>`btn secondary sm ${r.showMapping?``:`transparent`}`,onclick:()=>r.showMapping=!r.showMapping},t.span({className:`txt`},`Optional users create fields mapping`),t.i({className:()=>r.showMapping?`ri-arrow-drop-up-line`:`ri-arrow-drop-down-line`,ariaHidden:!0})),app.components.slide(()=>r.showMapping,t.div({className:`grid sm m-t-sm`},t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.name`},`OAuth2 full name`),app.components.select({id:n+`.mappedFields.name`,name:`oauth2.mappedFields.name`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.name,onchange:n=>{e.oauth2.mappedFields.name=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.avatarURL`},`OAuth2 avatar`),app.components.select({id:n+`.mappedFields.avatarURL`,name:`oauth2.mappedFields.avatarURL`,placeholder:`Select field`,options:()=>r.regularAndFileFieldOptions,value:()=>e.oauth2.mappedFields.avatarURL,onchange:n=>{e.oauth2.mappedFields.avatarURL=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.id`},`OAuth2 id`),app.components.select({id:n+`.mappedFields.id`,name:`oauth2.mappedFields.id`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.id,onchange:n=>{e.oauth2.mappedFields.id=n?.[0]?.value||``}}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.mappedFields.username`},`OAuth2 username`),app.components.select({id:n+`.mappedFields.username`,name:`oauth2.mappedFields.username`,placeholder:`Select field`,options:()=>r.regularFieldOptions,value:()=>e.oauth2.mappedFields.username,onchange:n=>{e.oauth2.mappedFields.username=n?.[0]?.value||``}}))))))))}function Gn(e){let n=`otp_`+app.utils.randomString(),r=store({get config(){return e.otp||={enabled:!1,duration:300,length:8},e.otp},get isSuperusers(){return e.system&&e.name==`_superusers`}});return t.details({pbEvent:`otpAccordion`,name:`auth-methods`,className:`accordion otp-accordion`},t.summary(null,t.i({className:`ri-time-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`One-time password (OTP)`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.otp)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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`otp.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:n=>{r.config.enabled=n.target.checked,r.isSuperusers&&(e.mfa.enabled=n.target.checked)}}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}),()=>{if(r.isSuperusers)return t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Superusers can have OTP only as part of Two-factor authentication.`)})})),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.duration`,textContent:`Duration (in seconds)`}),t.input({type:`number`,id:n+`.duration`,name:`otp.duration`,min:1,step:1,required:!0,value:()=>r.config.duration||``,oninput:e=>r.config.duration=parseInt(e.target.value,10)}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:n+`.length`,textContent:`Generated password length`}),t.input({type:`number`,id:n+`.length`,name:`otp.length`,min:1,step:1,required:!0,value:()=>r.config.length||``,oninput:e=>r.config.length=parseInt(e.target.value,10)})))))}function Kn(e){let n=`passwordAuth_`+app.utils.randomString(),r=store({get config(){return e.passwordAuth||={enabled:!0,identityFields:[`email`]},e.passwordAuth},get identityFieldOptions(){let n=[{value:`email`}],r=e?.fields||[],i=e?.indexes||[];for(let e of i){let i=app.utils.parseIndex(e);if(!i.unique||i.columns.length!=1||i.columns[0].name==`email`)continue;let a=r.find(e=>!e.hidden&&e.name.toLowerCase()==i.columns[0].name.toLowerCase());a&&n.push({value:a.name})}return n}});return t.details({pbEvent:`passwordAuthAccordion`,name:`auth-methods`,className:`accordion password-auth-accordion`},t.summary(null,t.i({className:`ri-lock-password-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Identity/Password`}),t.span({className:()=>`label m-l-auto ${r.config.enabled?`success`:``}`,textContent:()=>r.config.enabled?`Enabled`:`Disabled`}),()=>{if(app.store.errors?.passwordAuth)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-sm-12`},t.div({className:`field`},t.input({type:`checkbox`,id:n+`.enabled`,name:`passwordAuth.enabled`,className:`switch`,checked:()=>r.config.enabled,onchange:e=>r.config.enabled=e.target.checked}),t.label({htmlFor:n+`.enabled`,textContent:`Enable`}))),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:n+`.identityFields`,textContent:`Identity fields`}),app.components.select({id:n+`.identityFields`,name:`passwordAuth.identityFields`,max:99,required:!0,options:()=>r.identityFieldOptions,value:()=>r.config.identityFields,onchange:e=>{r.config.identityFields=e.map(e=>e.value)}})),t.div({className:`field-help`},`Only non-hidden fields with UNIQUE index constraint can be selected.`))))}function qn(e){let n=`token_`+app.utils.randomString(),r=store({get tokensList(){return e?.name===`_superusers`?[{key:`authToken`,label:`Auth`},{key:`passwordResetToken`,label:`Password reset`},{key:`fileToken`,label:`Protected file`}]:[{key:`authToken`,label:`Auth`},{key:`verificationToken`,label:`Email verification`},{key:`passwordResetToken`,label:`Password reset`},{key:`emailChangeToken`,label:`Email change`},{key:`fileToken`,label:`Protected file`}]}});return t.details({pbEvent:`tokenOptionsAccordion`,name:`other`,className:`accordion token-options-accordion`},t.summary(null,t.i({className:`ri-key-2-line`,ariaHidden:!0}),t.span({className:`txt`,textContent:`Token options (invalidate, duration)`})),t.div({className:`grid sm`},()=>r.tokensList.map(r=>{let i=n+r.key;return t.div({className:`col-sm-6`},t.div({className:`field token-field`},t.label({htmlFor:i,textContent:()=>r.label+` duration (in seconds)`}),t.input({id:i,type:`number`,min:1,step:1,required:!0,name:()=>r.key+`.duration`,value:()=>e[r.key].duration,oninput:n=>e[r.key].duration=parseInt(n.target.value,10)})),t.div({className:`field-help m-b-10`},t.button({type:`button`,className:()=>`link-hint ${e[r.key].secret?`txt-success`:``}`,textContent:`Invalidate all previously issued tokens`,onclick:()=>{e[r.key].secret?delete e[r.key].secret:e[r.key].secret=app.utils.randomSecret(50)}})))})))}function Jn(e){let n=`options_`+app.utils.randomString();return t.div({className:`collection-tab-content collection-options-tab-content`},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Auth methods`),t.div({className:`flex-fill`}),t.div({className:`field`},t.input({id:n+`.authAlert`,name:`authAlert.enabled`,type:`checkbox`,className:`switch sm`,checked:()=>!!e.collection.authAlert?.enabled,onchange:n=>{e.collection.authAlert=e.collection.authAlert||{},e.collection.authAlert.enabled=n.target.checked}}),t.label({htmlFor:n+`.authAlert`},`Send email alert for new logins`))),Kn(e.collection),()=>{if(e.originalCollection?.name!=`_superusers`)return Wn(e.collection)},Gn(e.collection),Vn(e.collection)),t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Mail templates`),t.button({tabIndex:-1,type:`buttton`,className:`m-l-auto label handle txt-bold`,textContent:`Send test email`,onclick:()=>app.modals.openMailTest(e.collection?.name)})),Bn(e.collection,`verificationTemplate`,{title:`Default Verification email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`resetPasswordTemplate`,{title:`Default Password reset email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`confirmEmailChangeTemplate`,{title:`Default Confirm email change email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{TOKEN}`]}),Bn(e.collection,`otp.emailTemplate`,{title:`Default OTP email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{OTP}`,`{OTP_ID}`]}),Bn(e.collection,`authAlert.emailTemplate`,{title:`Default Login alert email template`,placeholders:[`{APP_NAME}`,`{APP_URL}`,`{RECORD:*}`,`{ALERT_INFO}`]})),t.div({className:`col-12`},t.div({className:`section-heading`},t.strong(null,`Other`)),qn(e.collection))))}function X(e){return t.div({className:`collection-tab-content collection-fields-tab-content`},t.div({className:`collection-fields-list`},app.components.sortable({handle:`.sort-handle`,data:()=>(e.collection?.fields||[])?.filter(e=>!!app.fieldTypes[e.type]?.settings),dataItem:(n,r)=>app.fieldTypes[n.type].settings({field:n,get originalCollection(){return e.originalCollection},get collection(){return e.collection},get originalField(){return e.originalCollection?.fields?.find(e=>n.id&&e.id==n.id)},get fieldIndex(){return e.collection.fields?.findIndex(e=>n.id?e.id==n.id:e==n)}}),onchange:(n,r,i)=>{e.collection.fields=n}})),()=>app.components.addCollectionFieldButton(e.collection),t.hr(),t.p({className:`txt-bold`},`Unique constraints and indexes (`,()=>e.collection.indexes?.length,`)`),app.components.sortable({className:`indexes-list`,data:()=>e.collection.indexes||[],onchange:function(n){e.collection.indexes=n},dataItem:(n,r)=>{let i=app.utils.parseIndex(n);return t.button({type:`button`,className:()=>`label handle ${app.store.errors?.indexes?.[r]?.message?`danger error`:`success`}`,ariaDescription:app.attrs.tooltip(()=>app.store.errors?.indexes?.[r]?.message||``),onclick:()=>app.modals.openIndexUpsert(e.collection,n)},()=>{if(i.unique)return t.strong(null,`Unique:`)},t.span({className:`txt`},()=>i.columns?.map(e=>e.name).join(`, `)))},after:()=>t.button({type:`button`,className:`label handle`,onclick:()=>app.modals.openIndexUpsert(e.collection)},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt`},`New index`))}))}function Yn(e){let n=store({showRulesInfo:!1,showAuthRules:!1}),r=()=>app.attrs.tooltip(e.originalCollection?.system?`System collection rule cannot be changed.`:null,`top-left`);function i(n){return app.utils.collectionAutocompleteKeys(e.collection,n)}return t.div({className:`collection-tab-content collection-rules-tab-content`},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`flex txt-hint txt-sm`},t.span({className:`txt`},`All rules follow the `,t.a({target:`_blank`,rel:`noopener noreferrer`,href:`https://pocketbase.io/docs/api-rules-and-filters`,textContent:`PocketBase filter syntax and operators`}),`.`),t.strong({tabIndex:-1,className:`m-l-auto link-hint`,textContent:()=>n.showRulesInfo?`Hide available fields`:`Show available fields`,onclick:()=>n.showRulesInfo=!n.showRulesInfo})),app.components.slide(()=>n.showRulesInfo,t.div({className:`alert warning m-t-sm`},t.div({className:`content`},t.p(null,`The following record fields are available:`),t.div({className:`flex flex-wrap gap-5`},()=>app.utils.getAllCollectionIdentifiers(e.collection).map(e=>t.code(null,e))),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`The request fields could be accessed with the special `,t.strong(null,`@request`),` fields:`),t.div({className:`flex flex-wrap gap-5`},t.code(null,`@request.headers.*`),t.code(null,`@request.query.*`),t.code(null,`@request.body.*`),t.code(null,`@request.auth.*`)),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`You could also add constraints and query other collections using the `,t.strong(null,`@collection`),` field:`),t.div({className:`flex flex-wrap gap-5`},t.code(null,`@collection.ANY_COLLECTION_NAME.*`)),t.hr({className:`m-t-10 m-b-10`}),t.p(null,`Example rule:`),()=>{let n=e.collection.fields?.find(e=>e.type==`date`||e.type==`autodate`);return n?t.code(null,`@request.auth.id != "" && ${n.name} > "2022-01-01 00:00:00.000Z"`):t.code(null,`@request.auth.id != ""`)})))),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`List/Search rule`,name:`listRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.listRule,oninput:n=>e.collection.listRule=n})),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`View rule`,name:`viewRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.viewRule,oninput:n=>e.collection.viewRule=n})),()=>{if(e.collection.type!=`view`)return[t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:[t.span({className:`txt`,textContent:`Create rule`}),t.i({hidden:()=>e.collection.createRule==null,className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`The main record fields hold the values that are going to be inserted in the database.`)})],name:`createRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.createRule,oninput:n=>e.collection.createRule=n})),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:[t.span({className:`txt`,textContent:`Update rule`}),t.i({hidden:()=>e.collection.updateRule==null,className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`The main record fields hold the old/existing record field values. To target the newly submitted ones you can use @request.body.*.`)})],name:`updateRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.updateRule,oninput:n=>e.collection.updateRule=n})),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`Delete rule`,name:`deleteRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.deleteRule,oninput:n=>e.collection.deleteRule=n}))]}),()=>{if(e.collection.type==`auth`)return[t.hr({className:`m-t-base m-b-base`}),t.button({type:`button`,onmount:()=>{n.showAuthRules=e.collection.manageRule!==null||e.collection.authRule!==``},className:()=>`btn secondary sm ${n.showAuthRules?``:`transparent`}`,onclick:()=>{n.showAuthRules=!n.showAuthRules}},t.span({className:`txt`},`Additional auth collection rules`),t.i({ariaHidden:!0,className:()=>n.showAuthRules?`ri-arrow-drop-up-line`:`ri-arrow-drop-down-line`})),app.components.slide(()=>n.showAuthRules,t.div({className:`grid sm m-t-sm`},t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`Authentication rule`,name:`authRule`,placeholder:``,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.authRule,oninput:n=>e.collection.authRule=n}),t.div({className:`field-help`},t.p(null,`This rule is executed every time `,t.strong(null,`before authentication`),` allowing you to restrict who can authenticate.`),t.p(null,`For example, to allow only verified users you can set it to `,t.code(null,`verified = true`),`.`),t.p(null,`Leave it empty to allow anyone with an account to authenticate.`),t.p(null,`To disable authentication entirely you can change it to "Set superusers only".`))),t.div({className:`col-12`,ariaDescription:r()},app.components.ruleField({label:`Manage rule`,name:`manageRule`,autocomplete:i,disabled:()=>e.originalCollection?.system,value:()=>e.collection.manageRule,oninput:n=>e.collection.manageRule=n}),t.div({className:`field-help`},t.p(null,`This rule is executed in addition to the `,t.strong(null,`create`),` and `,t.strong(null,`update`),` API rules.`),t.p(null,`It enables superuser-like permissions to allow fully managing the auth record(s), eg. changing the password without requiring to enter the old one, directly updating the verified state or email, etc.`)))))]})}var Xn=`test_view_query`;function Zn(e){let n=`query_`+app.utils.randomString(),r=`SELECT.FROM.WHERE.LEFT JOIN.INNER JOIN.ON.AS.GROUP BY.HAVING.ORDER BY.ASC.DESC.LIMIT.OFFSET.WITH.NOT.IN.AND.OR.EXISTS.LIKE.CAST.REAL.DECIMAL.NUMERIC.INT.TEXT.BOOL`.split(`.`),i=store({testRecords:[],testError:``,isTesting:!1});async function a(n){if(i.isTesting=!0,i.testRecords=[],(app.store.errors?.viewQuery||app.store.errors?.fields)&&(delete app.store.errors.viewQuery,delete app.store.errors.fields),!n){i.testError=``,i.isTesting=!1;return}try{let r=await app.pb.send(`/api/collections/meta/dry-run-view`,{method:`POST`,body:{query:n},requestKey:Xn});e.collection?.id?i.testRecords=r.sample.map(n=>(n.collectionId=e.collection?.id,n.collectionName=e.collection?.name,n)):i.testRecords=r.sample,i.testError=``,i.isTesting=!1}catch(e){e.isAbort||(i.testError=e.message||`Invalid query.`,i.isTesting=!1)}}let o,s=[watch(()=>e.collection?.viewQuery,e=>{clearTimeout(o),o=setTimeout(()=>a(e),200)})];return t.div({pbEvent:`collectionViewQueryTabContent`,className:`collection-tab-content collection-view-query-tab-content`,onunmount:()=>{clearTimeout(o),app.pb.cancelRequest(Xn),s.forEach(e=>e?.unwatch())}},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`txt-right txt-sm m-b-10`},t.button({type:`button`,className:`txt-bold link-hint`,"html-popovertarget":n+`caveats_dropdown`},()=>`Query caveats`)),t.div({id:n+`caveats_dropdown`,className:`dropdown sm query-caveats-dropdown`,popover:`auto`},t.ul(null,t.li(null,`Wildcard columns (*) are not supported.`),t.li(null,`The query must have a unique `,t.code(null,`id`),` column.`,t.br(),`If your query doesn't have a suitable one, you can use the universal `,t.code(null,`(ROW_NUMBER() OVER()) as id`),`.`),t.li(null,`Expressions must be aliased with a valid formatted field name, e.g. `,t.code(null,`MAX(balance) as maxBalance`),`.`),t.li(null,`Combined/multi-spaced expressions must be wrapped in parenthesis, e.g. `,t.code(null,`(MAX(balance) + 1) as maxBalance`),`.`),t.li(null,`UNION expressions are supported but the entire query must be wrapped in parenthesis.`))),t.div({className:`field`},t.label({htmlFor:n+`.viewQuery`},t.span({className:`txt`},`Select query`),t.span({hidden:()=>!i.testError,className:`query-state`,ariaDescription:app.attrs.tooltip(`Invalid query`,`left`)},t.i({className:`ri-error-warning-fill txt-danger`,ariaHidden:!0})),t.span({hidden:()=>!!i.testError,className:`query-state`,ariaDescription:app.attrs.tooltip(`Valid query`,`left`)},t.i({className:`ri-checkbox-circle-fill txt-success`,ariaHidden:!0}))),app.components.codeEditor({id:n+`.viewQuery`,name:`viewQuery`,language:`sql`,required:!0,autocomplete:r,className:`inline-error`,value:()=>e.collection.viewQuery||``,oninput:n=>{e.collection.viewQuery=n}}))),t.div({className:`col-12`},t.p({className:`txt-sm txt-bold`},`Sample output:`),t.div({className:`view-query-sample-wrapper`},t.span({hidden:()=>!i.isTesting,className:`loader sm`}),app.components.codeBlock({language:()=>i.testError?`plain`:`js`,className:()=>`view-query-sample ${i.testError?`txt-danger`:``}`,value:()=>i.testRecords?.length?JSON.stringify(i.testRecords,null,2):i.testError||`N/A`})))))}window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openCollectionUpsert=function(e={},n={onbeforeopen:null,onafteropen:null,onbeforeclose:null,onafterclose:null,onsave:null,ondelete:null,onduplicate:null,ontruncate:null}){app.store.errors=null;let r=Qn(e||{},n||{});document.body.appendChild(r),app.modals.open(r)},window.app.collectionTypes={base:{icon:`ri-folder-2-line`,tabs:{Fields:X,"API rules":Yn}},view:{icon:`ri-table-line`,tabs:{Query:Zn,"API rules":Yn}},auth:{icon:`ri-group-line`,tabs:{Fields:X,"API rules":Yn,Options:Jn}}};function Qn(e,n){let r,i=`collection_upsert_`+app.utils.randomString(),a=store({isSaving:!1,originalCollection:{},collection:{},selectedTab:``,get activeTab(){return app.collectionTypes[a.collection.type]?.tabs&&(!a.selectedTab||!app.collectionTypes[a.collection.type].tabs?.[a.selectedTab])?Object.keys(app.collectionTypes[a.collection.type].tabs)?.[0]||``:a.selectedTab},get isNew(){return app.utils.isEmpty(a.originalCollection?.id)},get collectionTypeOptions(){return Object.keys(app.collectionTypes).map(e=>({value:e,label:app.utils.sentenize(e,!1)+` collection`}))},get collectionHash(){return Object.keys(a.collection).length,JSON.stringify(a.collection)},get originalCollectionHash(){return JSON.stringify(a.originalCollection)},get hasChanges(){return a.originalCollectionHash!=a.collectionHash},get canSave(){return!a.isSaving&&(a.isNew||a.hasChanges)}});async function o(e){app.utils.isEmpty(e)&&(e=JSON.parse(JSON.stringify(app.store.collectionScaffolds.base))||{type:`base`,fields:[]},e.fields.push({type:`autodate`,name:`created`,onCreate:!0}),e.fields.push({type:`autodate`,name:`updated`,onCreate:!0,onUpdate:!0})),a.originalCollection=JSON.parse(JSON.stringify(e)),a.collection=JSON.parse(JSON.stringify(e))}async function s(e=!0){a.canSave&&(a.isSaving=!0,app.modals.openCollectionChangesConfirmation(a.originalCollection,a.collection,()=>l(e),()=>{a.isSaving=!1}))}function c(){let e=JSON.parse(JSON.stringify(a.collection));e.fields=e.fields||[];for(let n=e.fields.length-1;n>=0;n--)if(e.fields[n][`@toDelete`]){e.fields.splice(n,1);continue}return e}async function l(e=!0){a.isSaving=!0;try{let i=c(),o=app.utils.isEmpty(a.originalCollection?.id),s;s=o?app.pb.collections.create(i):app.pb.collections.update(a.originalCollection.id,i);let l=JSON.stringify(await s);a.originalCollection=JSON.parse(l),a.collection=JSON.parse(l),app.store.addOrUpdateCollection(JSON.parse(l)),n?.onsave?.(JSON.parse(l),o),a.isSaving=!1,app.toasts.success(o?`Successfully created collection "${a.collection.name}".`:`Successfully updated collection "${a.collection.name}".`,{key:`collectionSave`}),app.store.errors=null,e&&app.modals.close(r,!0)}catch(e){e?.isAbort||(a.isSaving=!1,app.checkApiError(e,!1),app.toasts.error(e.message||`Failed to save collection.`,{key:`collectionSave`}))}}function u(){a.collection=JSON.parse(JSON.stringify(a.originalCollection))}async function d(){let e=a.originalCollection?JSON.parse(JSON.stringify(a.originalCollection)):{};return e.id=``,e.system=!1,e.name+=`_duplicate`,e.created=``,e.updated=``,e.indexes=e.indexes?.map(n=>app.utils.replaceIndexFields(n,n=>({indexName:n.indexName+app.utils.randomString(3),tableName:e.name})))||[],await n.onduplicate?.(e),o(e)}async function f(e){a.selectedTab=e,await new Promise(e=>setTimeout(e,0)),app.store.errors&&(app.store.errors=JSON.parse(JSON.stringify(app.store.errors)))}return r=t.div({pbEvent:`collectionUpsertModal`,"html-data-collectionId":()=>a.originalCollection?.id,"html-data-collectionName":()=>a.originalCollection?.name,className:`modal collection-upsert-modal`,inert:()=>a.isSaving,onkeydown:e=>{if((e.ctrlKey||e.metaKey)&&e.code==`KeyS`){e.preventDefault();let n=document.activeElement;n?.blur(),s(!1),n?.focus()}},onbeforeopen:()=>(o(e),n.onbeforeopen?.(el)),onafteropen:e=>{n.onafteropen?.(e)},onbeforeclose:(e,r)=>r?n.onbeforeclose?.(e):a.isSaving?!1:a.hasChanges?new Promise(r=>{app.modals.confirm(`You have unsaved changes. Do you really want to discard them?`,()=>r(n.onbeforeclose?.(e)),()=>r(!1))}):n.onbeforeclose?.(e),onafterclose:e=>{n.onafterclose?.(e),e?.remove()},onmount:e=>{e._watchers?.forEach(e=>e?.unwatch()),e._watchers=[watch(()=>a.collection.type,(e,n)=>{if(!n||e==n||!app.store.collectionScaffolds[e])return;app.utils.deleteByPath(app.store.errors,`fields`);let r=JSON.parse(JSON.stringify(app.store.collectionScaffolds[e]));a.collection=Object.assign(structuredClone(r),JSON.parse(JSON.stringify(a.collection))),a.originalCollection=r,$n(a.collection)}),watch(()=>a.collection.name,(n,r)=>{n=app.utils.slugify(n),a.collection.name=n,!(r===void 0||!n||n==r)&&(clearTimeout(e.__collectionRenameTimeoutId),e.__collectionRenameTimeoutId=setTimeout(()=>{a.collection.indexes=a.collection.indexes?.map(e=>app.utils.replaceIndexFields(e,{tableName:a.collection.name}))},150))})]},onunmount:e=>{clearTimeout(e?.__collectionRenameTimeoutId),e?._watchers?.forEach(e=>e?.unwatch())}},t.header({className:`modal-header isolated`},t.div({className:`grid sm`},t.div({className:`col-12 flex`},t.h6({className:`modal-title`},t.span(null,()=>a.isNew?`Create `:`Edit `),t.strong({hidden:()=>a.isNew,className:`txt-ellipsis collection-name`},()=>a.originalCollection?.name),t.span(null,` collection`)),t.div({className:`flex-fill`}),()=>{if(!app.utils.isEmpty(a.originalCollection?.id))return[t.button({type:`button`,className:`btn sm circle transparent`,title:`More options`,"html-popovertarget":i+`modal-header-dropdown`},t.i({className:`ri-more-line`,ariaHidden:!0})),t.div({id:i+`modal-header-dropdown`,className:`dropdown nowrap modal-header-dropdown`,popover:`auto`},t.button({type:`button`,className:`dropdown-item`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover(),app.utils.copyToClipboard(JSON.stringify(a.originalCollection,null,2)),app.toasts.success(`Collection copied to clipboard!`)}},t.i({className:`ri-braces-line`,ariaHidden:!0}),t.span({className:`txt`},`Copy JSON`)),t.button({type:`button`,className:`dropdown-item`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover(),a.hasChanges?app.modals.confirm(`You have unsaved changes. Do you really want to discard them?`,d,null,{yesButton:`Yes, discard`}):d()}},t.i({className:`ri-file-copy-line`,ariaHidden:!0}),t.span({className:`txt`},`Duplicate`)),t.hr(),()=>{if(a.collection.type!=`view`)return er(a,n)},()=>{if(!a.collection.system)return tr(a,n)})]}),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:i+`col_name`,textContent:()=>`Name${a.collection?.system?` (system)`:``}`}),t.input({id:i+`col_name`,type:`text`,name:`name`,required:!0,spellcheck:!1,placeholder:`e.g. posts`,autofocus:()=>a.isNew,disabled:()=>!a.isNew&&a.collection?.system,value:()=>a.collection.name||``,onmount:e=>{e.addEventListener(`compositionend`,e=>{a.collection.name=e.target.value})},oninput:e=>{e.isComposing||(a.collection.name=e.target.value)}})),t.div({className:`field addon`},t.button({type:`button`,disabled:()=>!a.isNew,className:()=>`btn sm collection-type-select ${a.isNew?`outline`:`transparent`}`,"html-popovertarget":i+`col_type_dropdown`},t.span({className:`txt`},`Type: `,()=>app.utils.sentenize(a.collection.type,!1)||`N/A`),t.i({hidden:()=>!a.isNew,ariaHidden:!0,className:`ri-arrow-drop-down-line m-l-auto`})),t.div({id:i+`col_type_dropdown`,className:`dropdown nowrap collection-type-dropdown`,popover:`auto`},()=>{let e=[];for(let n of a.collectionTypeOptions)e.push(t.button({type:`button`,className:()=>`dropdown-item ${n.value==a.collection.type?`active`:``}`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover(),a.collection.type=n.value}},t.i({ariaHidden:!0,className:app.collectionTypes[n.value]?.icon||app.utils.fallbackCollectionIcon}),t.span({className:`txt`},n.label||n.value)));return e})))),t.div({className:`col-12`},t.nav({className:`tabs-header equal-width`},()=>{let e=[],n=app.collectionTypes[a.collection.type]?.tabs||{};for(let r in n)e.push(t.button({type:`button`,disabled:()=>a.isSaving,className:()=>`tab-item ${a.activeTab==r?`active`:``}`,onclick:()=>f(r)},t.span({className:`txt`},r)));return e})))),t.div({className:`modal-content`},()=>app.collectionTypes[a.collection.type]?.tabs?.[a.activeTab]?.(a)),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,disabled:()=>a.isSaving,onclick:()=>app.modals.close(r)},t.span({className:`txt`},`Close`)),()=>{let e=JSON.stringify(app.store.errors);if(!(e==``||e==`null`||e==`{}`||e==`[]`))return t.i({className:`ri-alert-line txt-danger`,ariaDescription:app.attrs.tooltip(()=>`Raw error: `+e)})},t.div({className:`btns`},t.button({type:`button`,className:()=>`btn expanded-lg ${a.isSaving?`loading`:``}`,disabled:()=>!a.canSave,onclick:()=>s(!0)},t.span({className:`txt`},()=>a.isNew?`Create`:`Save changes`)),t.button({type:`button`,title:`Save options`,className:()=>`btn p-5`,disabled:()=>!a.canSave,"html-popovertarget":i+`save_options`},t.i({className:`ri-arrow-up-s-line`,ariaHidden:!0})),t.div({id:i+`save_options`,className:`dropdown nowrap`,popover:`auto`},t.button({type:`button`,className:`dropdown-item`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover(),s(!1)}},t.span({className:`txt`},`Save and continue`),t.small({className:`txt-hint`},`(Ctrl+S)`)),t.hr(),t.button({type:`button`,className:`dropdown-item`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover(),u()}},t.span({className:`txt`},`Reset form`)))))),r}function $n(e){let n=JSON.parse(JSON.stringify(app.store.collectionScaffolds[e.type])),r=JSON.parse(JSON.stringify(e.fields))||[],i=r.filter(e=>!e.system);e.fields=n.fields||[];for(let n of r){if(!n.system)continue;let r=e.fields.find(e=>e.name==n.name);r&&Object.assign(r,n)}for(let n of i)e.fields.push(n);if(e.indexes=e.indexes||[],e.indexes.length){let r=n?.indexes||[];indexesLoop:for(let n=e.indexes.length-1;n>=0;n--){let i=app.utils.parseIndex(e.indexes[n]),a=i.indexName.toLowerCase();for(let i of r)if(a==app.utils.parseIndex(i).indexName.toLowerCase()){e.indexes.splice(n,1);continue indexesLoop}for(let r of i.columns)if(!e.fields.find(e=>e.name.toLowerCase()==r.name.toLowerCase())){e.indexes.splice(n,1);continue indexesLoop}}}app.utils.mergeUnique(e.indexes,n.indexes)}function er(e,n){let r=`truncate_`+app.utils.randomString(),i=store({isSubmitting:!1,nameConfirm:``});async function a(){if(i.isSubmitting||!e.originalCollection?.name||e.originalCollection.name!=i.nameConfirm)return!1;i.isSubmitting=!0;try{return await app.pb.collections.truncate(e.originalCollection.name),n.ontruncate?.(JSON.parse(JSON.stringify(e.originalCollection))),app.toasts.success(`Successfully truncated collection "${e.originalCollection.name}".`),i.isSubmitting=!1,!0}catch(e){i.isSubmitting=!1,app.checkApiError(e)}return!1}return t.button({type:`button`,className:`dropdown-item txt-danger`,disabled:()=>i.isSubmitting,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.modals.confirm(t.div(null,t.h6({className:`block txt-center`},`Do you really want to delete all records of the collection?`),t.div({className:`confirm-collection-label txt-bold m-t-sm m-b-sm`},`Type the collection name `,t.div({className:`label`},()=>e.originalCollection.name,app.components.copyButton(()=>e.originalCollection?.name)),` to confirm:`),t.div({className:`field`},t.label({htmlFor:r+`.confirm_name`},`Collection name`),t.input({id:r+`.confirm_name`,type:`text`,required:!0,pattern:()=>RegExp.escape(e.originalCollection.name),value:()=>i.nameConfirm,oninput:e=>i.nameConfirm=e.target.value}))),async()=>{if(document.getElementById(r+`.confirm_name`)?.reportValidity(),!await a())return!1;app.modals.close(n.target.closest(`.modal`))},()=>{i.nameConfirm=``})}},t.i({className:`ri-eraser-line`,ariaHidden:!0}),t.span({className:`txt`},`Truncate`))}function tr(e,n){let r=`delete_`+app.utils.randomString(),i=store({isSubmitting:!1,nameConfirm:``});async function a(){if(i.isSubmitting||!e.originalCollection?.name||e.originalCollection.name!=i.nameConfirm)return!1;i.isSubmitting=!0;try{return await app.pb.collections.delete(e.originalCollection.name),n.ondelete?.(JSON.parse(JSON.stringify(e.originalCollection))),app.utils.removeByKey(app.store.collections,`id`,e.originalCollection.id),app.toasts.success(`Successfully deleted collection "${e.originalCollection.name}".`),i.isSubmitting=!1,!0}catch(e){i.isSubmitting=!1,app.checkApiError(e)}return!1}return t.button({type:`button`,className:`dropdown-item txt-danger`,disabled:()=>i.isSubmitting,onclick:n=>{n.target.closest(`.dropdown`).hidePopover();let o=n.target.closest(`.modal`);app.modals.confirm(t.div({className:`block`},t.h6({className:`block txt-center`},()=>e.originalCollection.type==`view`?`Do you really want to delete the selected collection?`:`Do you really want to delete the selected collection and all its records`),t.div({className:`confirm-collection-label txt-bold m-t-sm m-b-sm`},`Type the collection name `,t.div({className:`label`},()=>e.originalCollection.name,app.components.copyButton(()=>e.originalCollection?.name)),` to confirm:`),t.div({className:`field`},t.label({htmlFor:r+`.confirm_name`},`Collection name`),t.input({id:r+`.confirm_name`,type:`text`,required:!0,pattern:()=>RegExp.escape(e.originalCollection.name),value:()=>i.nameConfirm,oninput:e=>i.nameConfirm=e.target.value}))),async()=>{if(document.getElementById(r+`.confirm_name`)?.reportValidity(),!await a())return!1;app.modals.close(o)},()=>{i.nameConfirm=``})}},t.i({className:`ri-delete-bin-7-line`,ariaHidden:!0}),t.span({className:`txt`},`Delete`))}window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.addCollectionFieldButton=function(e){let n=`new_field_`+app.utils.randomString();function r(n){let r={id:``,name:i(n),type:n,system:!1,hidden:!1,presentable:!1,required:!1,__focus:!0};e.fields=e.fields||[];let a=e.fields.findLastIndex(e=>e.type!=`autodate`);r.type!=`autodate`&&a>=0?e.fields.splice(a+1,0,r):e.fields.push(r)}function i(e=``){let n=e,r=2,i=e.match(/\d+$/)?.[0]||``,o=i?e.substring(0,e.length-i.length):e;for(;a(n);)n=o+((i<<0)+r),r++;return n}function a(n){return!!e.fields?.find(e=>e.name.toLowerCase()===n.toLowerCase())}return t.div({className:`new-collection-field-btn-wrapper`},t.button({type:`button`,className:`btn block outline`,"html-popovertarget":n+`_dropdown`},t.i({className:`ri-add-line`,ariaHidden:!0}),t.span({className:`txt`},`New field`)),t.div({id:n+`_dropdown`,className:`dropdown field-types-dropdown`,popover:`auto`},()=>{let e=[];for(let n in app.fieldTypes){if(n==`password`)continue;let i=app.fieldTypes[n];i.settings&&e.push(t.button({type:`button`,className:`dropdown-item`,onclick:e=>{e.target.closest(`.dropdown`)?.hidePopover(),r(n)}},t.i({className:i.icon||app.utils.fallbackFieldIcon,ariaHidden:!0}),t.span({className:`txt`},i.label||n)))}return e}))},window.app=window.app||{},window.app.utils=window.app.utils||{};var nr={maxKeys:30,requestKeys:!0,collectionJoinKeys:!0};window.app.utils.collectionAutocompleteKeys=function(e,n,r={}){if(!e||!n||!app.store.collections?.length)return[];r=Object.assign({},nr,r);let i=ir(n,app.store.collections,e).sort(rr);if(r.requestKeys){let r=ar(n,app.store.collections,e).sort(rr);for(let e of r)i.push(e)}if(r.collectionJoinKeys){let e=or(n,app.store.collections).sort(rr);for(let n of e)i.push(n)}return i.length>r.maxKeys?i.slice(0,r.maxKeys):i};function rr(e,n){return e.length-n.length}function ir(e,n,r,i=``,a=0){if(!e||a>=4||(typeof r==`string`&&(r=n.find(e=>e.name==r||e.id==r)),!r))return[];e=e.toLowerCase();let o=r.type==`auth`,s=app.utils.getAllCollectionIdentifiers(r,i).filter(n=>n.toLowerCase().includes(e)),c=r.fields||[];for(let r of c){if(r.type==`password`||o&&r.name==`tokenKey`)continue;let c=[];if(i==`@request.body.`&&(c.push(i+r.name+`:changed`),c.push(i+r.name+`:isset`)),typeof app.fieldTypes[r.type]?.filterModifiers==`function`){let e=app.fieldTypes[r.type]?.filterModifiers(r)||[];for(let n of e)c.push(i+r.name+`:`+n)}for(let n of c)n.toLowerCase().includes(e)&&s.push(n);if(r.type==`relation`&&r.collectionId){let o=ir(e,n,r.collectionId,i+r.name+`.`,a+1);for(let e of o)s.push(e)}}for(let o of n){let c=o.fields||[];for(let l of c){if(l.type!=`relation`||l.collectionId!=r.id)continue;let c=i+o.name+`_via_`+l.name,u=ir(e,n,o,c+`.`,a+2);for(let e of u)s.push(e)}}return s}function ar(e,n,r){if(!e)return[];e=e.toLowerCase();let i=[];for(let n of[`@request.context`,`@request.method`,`@request.query.`,`@request.body.`,`@request.headers.`,`@request.auth.collectionId`,`@request.auth.collectionName`])n.toLowerCase().includes(e)&&i.push(n);let a=n.filter(e=>e.type===`auth`);for(let r of a){if(r.system)continue;let a=ir(e,n,r,`@request.auth.`);for(let e of a)app.utils.pushUnique(i,e)}if(typeof r==`string`&&(r=n.find(e=>e.name==r||e.id==r)),!r)return i;let o=ir(e,n,r,`@request.body.`);for(let e of o)i.push(e);return i}function or(e,n){let r=[],i=`@collection.`,a,o;if(12!e.exclude?.includes(r.name)&&(r.name+r.displayName).toLowerCase().replaceAll(` `,``).includes(n))}});function i(){r.searchTerm=``}return n=t.div({pbEvent:`providerPickerModal`,className:`modal provider-picker-modal`,onbeforeopen:n=>e.onbeforeopen?.(n),onafteropen:n=>{e.onafteropen?.(n)},onbeforeclose:n=>e.onbeforeclose?.(n),onafterclose:n=>{e.onafterclose?.(n),n?.remove()}},t.header({className:`modal-header`},t.h6({className:`modal-title`},t.span({className:`txt`},`Select OAuth2 provider`))),t.div({className:`modal-content`},t.div({className:`grid sm`},t.div({className:`col-12`},t.div({className:`fields searchbar`},t.div({className:`field`},t.input({placeholder:`Search...`,className:`p-l-20`,value:()=>r.searchTerm,oninput:e=>r.searchTerm=e.target.value})),()=>{if(r.searchTerm)return t.div({rid:`search-ctrls`,className:`field addon p-r-5`},t.button({type:`button`,className:`btn sm pill secondary transparent`,onclick:()=>i()},`Clear`))})),()=>{if(!(app.store.isLoadingOAuth2Providers||r.filteredProviders.length))return t.div({rid:`notfound`,className:`block txt-center txt-hint`},t.p(null,`No providers found.`),t.button({type:`button`,className:`btn sm secondary`,textContent:`Clear search`,onclick:()=>i()}))},()=>app.store.isLoadingOAuth2Providers?t.div({className:`col-12 txt-center`},t.span({className:`loader active`})):r.filteredProviders.map(r=>t.div({className:`col-sm-6`},t.button({type:`button`,className:`provider-card handle`,onclick:()=>{app.modals.close(n),e.onselect?.(r)}},t.figure({className:`provider-logo`},()=>r.logo?t.img({src:`data:image/svg+xml;base64,`+btoa(r.logo),alt:r.name+` logo`}):t.i({className:app.utils.fallbackProviderIcon,ariaHidden:!0})),t.div({className:`content`},t.span({className:`primadry-txt`},r.displayName||r.name),t.span({className:`secondary-txt`},r.name))))))),t.footer({className:`modal-footer gap-base`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close(n)},t.span({className:`txt`},`Close`)))),n}window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openProviderSettings=function(e={},n={namePrefix:``,onbeforeopen:null,onafteropen:null,onbeforeclose:null,onafterclose:null,onsubmit:null}){let r=cr(e,n);r&&(document.body.appendChild(r),app.modals.open(r))};function cr(e,n){let r,i=`provider_`+app.utils.randomString();e||={};let a=!e.clientId,o=JSON.stringify(e),s=app.store.oauth2Providers?.find(n=>n.name==e.name);if(!s){console.warn(`missing provider for config`,e);return}let c=store({config:JSON.parse(o),get hasChanges(){return o!=JSON.stringify(c.config)},onsubmit:(e,n)=>{}});function l(){c.hasChanges&&(n.onsubmit?.(s,JSON.parse(JSON.stringify(c.config))),app.modals.close(r))}return r=t.div({pbEvent:`providerSettingsModal`,className:`modal provider-settings-modal`,onbeforeopen:e=>n.onbeforeopen?.(e),onafteropen:e=>{n.onafteropen?.(e),setTimeout(()=>{app.store.errors?.oauth2&&(app.store.errors.oauth2=JSON.parse(JSON.stringify(app.store.errors.oauth2)))},0)},onbeforeclose:e=>n.onbeforeclose?.(e),onafterclose:e=>{n.onafterclose?.(e),e?.remove()}},t.header({className:`modal-header`},t.figure({className:`provider-logo`},()=>s.logo?t.img({src:`data:image/svg+xml;base64,`+btoa(s.logo),alt:s.name+` logo`}):t.i({className:app.utils.fallbackProviderIcon,ariaHidden:!0})),t.h6({className:`modal-title`},e.displayName||s.displayName||s.name,t.small({className:`txt-hint`},` (`,e.name,`)`))),t.form({pbEvent:`providerSettingsForm`,id:i+`form`,className:`modal-content`,onsubmit:e=>{e.preventDefault(),l()}},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.clientId`,textContent:`Client ID`}),t.input({type:`text`,required:!0,id:i+`.clientId`,autocomplete:`off`,name:()=>n.namePrefix+`.clientId`,value:()=>c.config.clientId||``,oninput:e=>c.config.clientId=e.target.value.trim()}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.clientSecret`,textContent:`Client secret`}),t.input({type:`password`,id:i+`.clientSecret`,autocomplete:`new-password`,required:()=>a||c.config.clientSecret!==void 0,name:()=>n.namePrefix+`.clientSecret`,value:()=>c.config.clientSecret||``,oninput:e=>c.config.clientSecret=e.target.value.trim(),onkeyup:e=>{e.key==`Backspace`&&c.config.clientSecret===void 0&&(c.config.clientSecret=``)},placeholder:()=>a||c.config.clientSecret!==void 0?``:`* * * * * *`}))),()=>{if(typeof app.oauth2?.[s.name]==`function`)return t.div({className:`col-12`},app.oauth2[s.name](s,n.namePrefix,c))})),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,onclick:()=>app.modals.close(r)},t.span({className:`txt`},`Close`)),t.button({"html-form":i+`form`,type:`submit`,className:`btn`,disabled:()=>!c.hasChanges},t.span({className:`txt`},`Set provider config`)))),r}window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openCollectionChangesConfirmation=async function(e,n,r,i){let a=store({isLoadingConflictingOIDCProviders:!1,conflictingOIDCProviders:[],get isCollectionRenamed(){return e?.name!=n?.name},get isNewCollectionAuth(){return n?.type===`auth`},get isNewCollectionView(){return n?.type===`view`},get renamedFields(){return a.isNewCollectionView?[]:n?.fields?.filter?.(n=>{let r;return n.id&&!n[`@toDelete`]&&(r=e.fields?.find?.(e=>e.id==n.id)),r&&r.name!=n.name})||[]},get deletedFields(){return a.isNewCollectionView?[]:n?.fields?.filter?.(e=>e.id&&e[`@toDelete`])||[]},get multipleToSingleFields(){return a.isNewCollectionView?[]:n?.fields?.filter?.(n=>{let r=e?.fields?.find?.(e=>e.id==n.id);if(!r||r.maxSelect===void 0)return!1;let i=r.maxSelect||1,a=n.maxSelect||1;return i>1&&a==1})||[]},get changedRules(){if(window.location.protocol!=`https:`)return[];let r=[],i=[`listRule`,`viewRule`];a.isNewCollectionView||i.push(`createRule`,`updateRule`,`deleteRule`),a.isNewCollectionAuth&&i.push(`manageRule`,`authRule`);let o,s;for(let a of i)o=e?.[a],s=n?.[a],o!==s&&r.push({prop:a,oldRule:o,newRule:s});return r},get needConfirmation(){return!app.utils.isEmpty(e?.id)&&(a.isCollectionRenamed||a.renamedFields.length||a.deletedFields.length||a.multipleToSingleFields.length||a.changedRules.length||a.conflictingOIDCProviders.length)}}),o=[`oidc`,`oidc2`,`oidc3`];async function s(){if(!(app.utils.isEmpty(e?.id)||!a.isNewCollectionAuth)){a.isLoadingConflictingOIDCProviders=!0;try{a.conflictingOIDCProviders=[];for(let r of o){let i=e?.oauth2?.providers?.find?.(e=>e.name==r),o=n?.oauth2?.providers?.find?.(e=>e.name==r);if(!i||!o)continue;let s=new URL(i.authURL).host,c=new URL(o.authURL).host;s!=c&&await app.pb.collection(`_externalAuths`).getFirstListItem(app.pb.filter(`collectionRef={:collectionId} && provider={:provider}`,{collectionId:n?.id,provider:r}),{requestKey:null})&&a.conflictingOIDCProviders.push({name:r,oldHost:s,newHost:c})}a.isLoadingConflictingOIDCProviders=!1}catch(e){e.isAbort&&(a.isLoadingConflictingOIDCProviders=!1,app.checkApiError(e))}}}if(await s(),!a.needConfirmation)return r();app.modals.confirm(t.div({className:`dangerous-collection-changes-list`},t.h5({className:`block txt-center m-b-base`},`Do you really want to save the collection changes?`),()=>{if(!(!a.isCollectionRenamed&&!a.deletedFields.length&&!a.renamedFields.length))return t.div({className:`alert warning m-b-base`},t.p(null,`If the collection participate in another collection rule, filter or view query, you'll have to update it manually!`),()=>{if(a.deletedFields.length)return t.p(null,`All data associated with the removed fields will be permanently deleted!`)})},()=>{if(a.isCollectionRenamed)return t.ul({className:`collection-changes-list changes-renamed-collection`},t.li({className:`list-item`},`Renamed collection `,t.strong({className:`label warning`},e?.name),t.i({className:`ri-arrow-right-line txt-sm`,ariaHidden:!0}),t.strong({className:`label success`},n?.name||`N/A`)))},()=>{if(a.renamedFields.length)return t.ul({className:`collection-changes-list changes-renamed-fields`},()=>a.renamedFields.map(n=>{let r=e?.fields?.find?.(e=>e.id==n.id);return t.li({className:`list-item`},`Renamed field `,t.strong({className:`label warning`},r?.name),t.i({className:`ri-arrow-right-line txt-sm`,ariaHidden:!0}),t.strong({className:`label success`},n.name||`N/A`))}))},()=>{if(a.deletedFields.length)return t.ul({className:`collection-changes-list changes-deleted-fields`},()=>a.deletedFields.map(e=>t.li({className:`list-item`},`Deleted field `,t.strong({className:`label danger`},e.name||`N/A`))))},()=>{if(a.multipleToSingleFields.length)return t.ul({className:`collection-changes-list changes-multiple-to-single-fields`},()=>a.multipleToSingleFields.map(e=>t.li({className:`list-item`},`Multiple to single value conversion of field `,t.strong({className:`label warning`},e.name||e.id),t.em({className:`txt-sm`},` (will keep only the last array item)`))))},()=>{if(a.changedRules.length)return t.ul({className:`collection-changes-list changes-api-rules`},()=>a.changedRules.map(e=>t.li({className:`list-item`},t.div({className:`content`},t.span({className:`txt`},`Changed API rule for `),t.code(null,e.prop)),t.small({className:`txt-bold`},`Old:`),t.div({className:`rule-content old-rule`},e.oldRule===null?`null (superusers only)`:e.oldRule||`""`),t.small({className:`txt-bold`},`New:`),t.div({className:`rule-content new-rule`},e.newRule===null?`null (superusers only)`:e.newRule||`""`))))},()=>{if(a.conflictingOIDCProviders.length)return t.ul({className:`collection-changes-list changes-api-rules`},()=>a.conflictingOIDCProviders.map(e=>t.li({className:`list-item`},`Changed OIDC `,e.name,` host `,t.strong({className:`label warning`},e.oldHost),t.i({className:`ri-arrow-right-line txt-sm`,ariaHidden:!0}),t.strong({className:`label success`},e.newHost),t.br(),t.span({className:`txt-hint`},`If the old and new OIDC configuration is not for the same provider consider deleting`,` all old _externalAuths records associated to the current collection and provider,`,` otherwise it may result in account linking errors.`),` `,t.a({rel:`noopenener noreferrer`,target:`_blank`,href:()=>`#/collections?collection=_externalAuths&filter=collectionRef%3D%22${n?.id}%22+%26%26+provider%3D%22${e.name}%22`,textContent:`Review existing _externalAuths records`}))))}),r,i,{className:`collection-changes-confirm-modal`})},window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.openCollectionsOverview=function(e={onbeforeopen:null,onafteropen:null,onbeforeclose:null,onafterclose:null}){let n=lr(e);n&&(document.body.appendChild(n),app.modals.open(n))};function lr(e={}){let n=`overview_modal_`+app.utils.randomString(),r={"Fields and relations":ur,Rules:dr},i=store({showSystemCollections:!1,activeTab:Object.keys(r)[0],get collections(){return i.showSystemCollections?app.store.collections:app.store.collections.filter(e=>!e.system)}}),a=t.div({pbEvent:`collectionsOverviewModal`,className:`modal popup collections-overview-modal`,onbeforeopen:n=>e.onbeforeopen?.(n),onafteropen:n=>{e.onafteropen?.(n)},onbeforeclose:n=>e.onbeforeclose?.(n),onafterclose:n=>{e.onafterclose?.(n),n?.remove()}},t.header({className:`modal-header isolated`},t.div({className:`grid sm`},t.div({className:`col-12`},t.div({className:`flex`},t.h6({className:`modal-title`},`Collections overview`),t.div({className:`flex-fill`}),t.div({className:`field`},t.input({id:n+`.showSystemCollections`,type:`checkbox`,className:`sm switch`,checked:()=>i.showSystemCollections,onchange:e=>i.showSystemCollections=e.target.checked}),t.label({htmlFor:n+`.showSystemCollections`},`System collections`)),t.button({type:`button`,className:`btn sm secondary transparent circle modal-close-btn`,title:`Close`,onclick:()=>app.modals.close(a)},t.i({className:`ri-close-line`,ariaHidden:!0})))),t.div({className:`col-12`},t.div({className:`tabs-header equal-width`},()=>{let e=[];for(let n in r)e.push(t.button({type:`button`,className:()=>`tab-item ${i.activeTab==n?`active`:``}`,onclick:()=>i.activeTab=n,textContent:n}));return e})))),()=>r[i.activeTab]?.(i));return a}function ur(e){return t.div({className:`modal-content erd-tab`},app.components.erd({collections:()=>{let n,r;function i(e,i){return n=e.name.startsWith(`_`),r=i.name.startsWith(`_`),e.system&&!i.system||n&&!r?1:!e.system&&i.system||!n&&r?-1:0}return e.collections.slice().sort(i)}}))}function dr(e){let n=[{value:`listRule`,label:`List/Search rule`},{value:`viewRule`,label:`View rule`},{value:`createRule`,label:`Create rule`,filter:e=>e.type!=`view`},{value:`updateRule`,label:`Update rule`,filter:e=>e.type!=`view`},{value:`deleteRule`,label:`Delete rule`,filter:e=>e.type!=`view`},{value:`authRule`,label:`Auth rule`,filter:e=>e.type==`auth`},{value:`manageRule`,label:`Manage rule`,filter:e=>e.type==`auth`},{value:`mfaRule`,label:`MFA rule`,emptyLabel:t.span({className:`label info`},`Enabled for everyone`),rule:e=>e.mfa?.rule,filter:e=>e.mfa?.enabled&&e.type==`auth`}],r=store({activeRuleOption:n[0],get activeCollections(){return r.activeRuleOption.filter?e.collections.filter(e=>r.activeRuleOption.filter(e)):e.collections}});return t.div({className:`modal-content rules-tab`},t.table({className:`rules-table`},t.thead({className:`sticky`},t.tr(null,t.td({colSpan:99,className:`col-rule-btns`},t.div({className:`rule-btns`},()=>n.map(e=>t.button({type:`button`,className:()=>`btn sm ${r.activeRuleOption?.value==e.value?`outline`:`transparent secondary`}`,textContent:()=>e.label,onclick:()=>r.activeRuleOption=e})))))),t.tbody(null,()=>r.activeCollections.length?r.activeCollections.map(e=>t.tr(null,t.th({className:`min-width`},t.div({className:`inline-flex gap-10`},t.i({ariaHidden:!0,className:()=>app.collectionTypes[e.type]?.icon||app.utils.fallbackCollectionIcon}),t.span({className:`txt collection-name`,title:()=>e.name,textContent:()=>e.name}))),()=>{let n;return n=r.activeRuleOption.rule?r.activeRuleOption.rule(e):e[r.activeRuleOption.value],t.td({style:`vertical-align: top`},()=>n===null?r.activeRuleOption.nullLabel?r.activeRuleOption.nullLabel:t.span({className:`label success`},`Superusers only`):n===``?r.activeRuleOption.emptyLabel?r.activeRuleOption.emptyLabel:t.span({className:`label info`},`Public`):app.components.codeBlock({language:`pbrule`,value:n}))})):t.tr(null,t.td({colSpan:99,className:`txt-hint`},t.p(null,`No collections with the selected rule.`))))))}window.app=window.app||{},window.app.oauth2=window.app.oauth2||{},window.app.oauth2.microsoft=function(e,n,r){let i=`microsoft_`+app.utils.randomString();return t.div({pbEvent:`oauth2MicrosoftOptions`,className:`oauth2-microsoft-options`},t.p({className:`txt-bold`},`Azure AD endpoints`),t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.authURL`},`Auth URL`),t.input({id:i+`.authURL`,name:n+`.authURL`,type:`url`,required:!0,value:()=>r.config.authURL||``,oninput:e=>r.config.authURL=e.target.value})),t.div({className:`field-help`},`Ex. https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize`)),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.tokenURL`},`Token URL`),t.input({id:i+`.tokenURL`,name:n+`.tokenURL`,type:`url`,required:!0,value:()=>r.config.tokenURL||``,oninput:e=>r.config.tokenURL=e.target.value})),t.div({className:`field-help`},`Ex. https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/token`))))},window.app=window.app||{},window.app.oauth2=window.app.oauth2||{},window.app.oauth2.lark=function(e,n,r){let i=`lark_`+app.utils.randomString(),a=[{label:`Feishu (China)`,value:`feishu.cn`},{label:`Lark (International)`,value:`larksuite.com`}],o=store({domain:r.config.authURL?.includes(a[1].value)?a[1].value:a[0].value}),s=[watch(()=>o.domain,e=>{e&&(r.config.authURL=`https://accounts.${e}/open-apis/authen/v1/authorize`,r.config.tokenURL=`https://open.${e}/open-apis/authen/v2/oauth/token`,r.config.userInfoURL=`https://open.${e}/open-apis/authen/v1/user_info`)})];return t.div({pbEvent:`oauth2LarkOptions`,className:`oauth2-lark-options`,onunmount:()=>{s.forEach(e=>e?.unwatch())}},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.site`},`Site`),app.components.select({options:a,required:!0,value:()=>o.domain||``,onchange:e=>{o.domain=e?.[0]?.value}}))),t.div({className:`col-12`},t.div({className:`alert info`},`Note that the Lark user's `,t.strong(null,`Union ID`),` will be used for the association with the PocketBase user (see `,t.a({href:`https://open.feishu.cn/document/platform-overveiw/basic-concepts/user-identity-introduction/introduction#3f2d4b63`,target:`_blank`,rel:`noopener noreferrer`,textContent:`Different Types of Lark User IDs`}),`).`))))},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.oauth2EndpointFields=function(e,n,r,i={}){let a=`endpoints_`+app.utils.randomString(),o=store({required:!0,title:`Provider endpoints`}),s=app.utils.extendStore(o,i);return t.div({pbEvent:`oauth2Endpoints`,className:`oauth2-endpoints`,onunmount:()=>{s.forEach(e=>e?.unwatch())}},t.p({className:`txt-bold`},e=>(typeof o.title==`function`&&o.title(e),o.title)),t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:a+`.authURL`},`Auth URL`),t.input({id:a+`.authURL`,name:n+`.authURL`,type:`url`,required:()=>!!o.required,value:()=>r.config.authURL||``,oninput:e=>r.config.authURL=e.target.value}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:a+`.tokenURL`},`Token URL`),t.input({id:a+`.tokenURL`,name:n+`.tokenURL`,type:`url`,required:()=>!!o.required,value:()=>r.config.tokenURL||``,oninput:e=>r.config.tokenURL=e.target.value}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:a+`.userInfoURL`},`User info URL`),t.input({id:a+`.userInfoURL`,name:n+`.userInfoURL`,type:`url`,required:()=>!!o.required,value:()=>r.config.userInfoURL||``,oninput:e=>r.config.userInfoURL=e.target.value})))))},window.app.oauth2=window.app.oauth2||{},window.app.oauth2.gitlab=function(e,n,r){return app.components.oauth2EndpointFields(e,n,r,{required:!1,title:`Self-hosted endpoints (optional)`})},window.app.oauth2.gitea=function(e,n,r){return app.components.oauth2EndpointFields(e,n,r,{required:!1,title:`Self-hosted endpoints (optional)`})},window.app.oauth2.mailcow=function(e,n,r){return app.components.oauth2EndpointFields(e,n,r)},window.app=window.app||{},window.app.oauth2=window.app.oauth2||{},window.app.oauth2.oidc=function(e,n,r){let i=`oidc_`+app.utils.randomString(),a=[{label:`User info URL`,value:!0},{label:`ID Token`,value:!1}],o=store({useUserInfoUrl:!1}),s=[];return t.div({pbEvent:`oauth2OIDCOptions`,className:`oauth2-oidc-options`,onmount:e=>{r.config.displayName===void 0&&(r.config.displayName=`OIDC`),r.config.pkce===void 0&&(r.config.pkce=!0),(r.config.userInfoURL||!r.config.extra)&&(o.useUserInfoUrl=!0),s.push(watch(()=>o.useUserInfoUrl,(e,n)=>{e?r.config.extra=null:(r.config.userInfoURL=``,r.config.extra=r.config.extra||{})}))},onunmount:()=>{s.forEach(e=>e?.unwatch())}},t.div({className:`grid`},t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.displayName`},`Display name`),t.input({id:i+`.displayName`,name:n+`.displayName`,type:`text`,required:!0,value:()=>r.config.displayName||``,oninput:e=>r.config.displayName=e.target.value}))),t.div({className:`col-12`},t.p({className:`txt-bold`},`Endpoints`),t.div({className:`field`},t.label({htmlFor:i+`.authURL`},`Auth URL`),t.input({id:i+`.authURL`,name:n+`.authURL`,type:`url`,required:!0,value:()=>r.config.authURL||``,oninput:e=>r.config.authURL=e.target.value}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.tokenURL`},`Token URL`),t.input({id:i+`.tokenURL`,name:n+`.tokenURL`,type:`url`,required:!0,value:()=>r.config.tokenURL||``,oninput:e=>r.config.tokenURL=e.target.value}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.userInfoSelect`},`Fetch user info from`),app.components.select({id:i+`.userInfoSelect`,required:!0,options:a,value:()=>o.useUserInfoUrl,onchange:e=>o.useUserInfoUrl=e?.[0]?.value})),t.div({className:`oidc-userinfo-options m-t-10`},()=>o.useUserInfoUrl?t.div({className:`field`},t.label({htmlFor:i+`.userInfoURL`},`User info URL`),t.input({id:i+`.userInfoURL`,name:n+`.userInfoURL`,type:`url`,required:!0,value:()=>r.config.userInfoURL||``,oninput:e=>r.config.userInfoURL=e.target.value})):t.div({className:`grid sm`},t.div({className:`col-12 txt-hint txt-sm`},t.em(null,`Both fields are considered optional because the parsed `,t.code(null,`id_token`),` is a direct result of the TLS code->token exchange server response.`)),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.extra.jwksURL`},t.span({className:`txt`},`JWKS verification URL`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`URL to the public token verification keys.`)})),t.input({id:i+`.extra.jwksURL`,name:n+`.extra.jwksURL`,type:`url`,value:()=>r.config.extra?.jwksURL||``,oninput:e=>{r.config.extra=r.config.extra||{},r.config.extra.jwksURL=e.target.value}}))),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:i+`.extra.issuers`},t.span({className:`txt`},`Issuers`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Comma separated list of accepted values for the iss token claim validation.`)})),t.input({id:i+`.extra.issuers`,name:n+`.extra.issuers`,type:`text`,value:()=>app.utils.joinNonEmpty(r.config.extra?.issuers),oninput:e=>{let n=app.utils.splitNonEmpty(e.target.value,`,`),i=app.utils.joinNonEmpty(n);app.utils.joinNonEmpty(r.config.extra?.issuers)!=i&&(r.config.extra=r.config.extra||{},r.config.extra.issuers=n)}})))))),t.div({className:`col-12`},t.div({className:`field`},t.input({id:i+`.pkce`,name:n+`.pkce`,type:`checkbox`,checked:()=>r.config.pkce||!1,onchange:e=>r.config.pkce=e.target.checked}),t.label({htmlFor:i+`.pkce`},t.span({className:`txt`,textContent:`Support PKCE`}),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Usually it should be safe to be always enabled as most providers will just ignore the extra query parameters if they don't support PKCE.`)}))))))},window.app=window.app||{},window.app.oauth2=window.app.oauth2||{},window.app.oauth2.apple=function(e,n,r){return``+app.utils.randomString(),t.div({pbEvent:`oauth2AppleOptions`,className:`oauth2-apple-options`},t.button({type:`button`,className:`btn sm secondary`,onclick:()=>{app.modals.openAppleSecretGenerator({ongenerate:e=>{r.config.clientSecret=e}})}},t.i({className:`ri-key-line`,ariaHidden:!0}),t.span({className:`txt`},`Generate secret`)))},window.app.modals=window.app.modals||{},window.app.modals.openAppleSecretGenerator=function(e={onbeforeopen:null,onafteropen:null,onbeforeclose:null,onafterclose:null,ongenerate:null}){let n=fr(e);n&&(document.body.appendChild(n),app.modals.open(n))};function fr(e={}){let n,r=`secret_generator_`+app.utils.randomString(),i=15777e3,a=store({clientId:``,teamId:``,keyId:``,privateKey:``,duration:i,isSubmitting:!1});async function o(){a.isSubmitting=!0;try{let r=await app.pb.settings.generateAppleClientSecret(a.clientId,a.teamId,a.keyId,a.privateKey.trim(),a.duration);a.isSubmitting=!1,app.toasts.success(`Successfully generated client secret.`),e.ongenerate?.(r.secret),app.modals.close(n)}catch(e){e.isAbort||(app.checkApiError(e),a.isSubmitting=!1)}}return n=t.div({pbEvent:`appleSecretGeneratorModal`,className:`modal popup apple-secret-generator-modal`,onbeforeopen:n=>e.onbeforeopen?.(n),onafteropen:n=>{e.onafteropen?.(n)},onbeforeclose:n=>e.onbeforeclose?.(n),onafterclose:n=>{e.onafterclose?.(n),n?.remove()}},t.header({className:`modal-header`},t.h5({className:`m-auto`},`Generate Apple client secret`)),t.form({id:r+`_form`,className:`modal-content`,onsubmit:e=>{e.preventDefault(),o()}},t.div({className:`grid`},t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:r+`.clientId`},`Client ID`),t.input({id:r+`.clientId`,name:`clientId`,type:`text`,required:!0,value:()=>a.clientId||``,oninput:e=>a.clientId=e.target.value}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:r+`.teamId`},`Team ID`),t.input({id:r+`.teamId`,name:`teamId`,type:`text`,required:!0,value:()=>a.teamId||``,oninput:e=>a.teamId=e.target.value}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:r+`.keyId`},`Key ID`),t.input({id:r+`.keyId`,name:`keyId`,type:`text`,required:!0,value:()=>a.keyId||``,oninput:e=>a.keyId=e.target.value}))),t.div({className:`col-sm-6`},t.div({className:`field`},t.label({htmlFor:r+`.duration`},`Duration (in seconds)`),t.input({id:r+`.duration`,name:`duration`,type:`number`,min:0,step:1,max:i,required:!0,value:()=>a.duration||0,oninput:e=>a.duration=parseInt(e.target.value,10)})),t.div({className:`field-help`},`Max ${i} seconds (~${i/(3600*24*30)<<0} months).`)),t.div({className:`col-sm-12`},t.div({className:`field`},t.label({htmlFor:r+`.privateKey`},`Private key`),t.textarea({id:r+`.privateKey`,name:`privateKey`,type:`text`,required:!0,rows:8,placeholder:`-----BEGIN PRIVATE KEY----- ... diff --git a/ui/dist/index.html b/ui/dist/index.html index 0680497b..327eec01 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.html @@ -13,7 +13,7 @@ - + diff --git a/ui/package-lock.json b/ui/package-lock.json index de3b8b41..12ac05e8 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -224,9 +224,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", - "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, @@ -937,9 +937,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { diff --git a/ui/src/collections/mfaAccordion.js b/ui/src/collections/mfaAccordion.js index 9fa32cf0..0606da62 100644 --- a/ui/src/collections/mfaAccordion.js +++ b/ui/src/collections/mfaAccordion.js @@ -6,7 +6,7 @@ export function mfaAccordion(collection) { if (!collection.mfa) { collection.mfa = { enabled: false, - duration: 900, + duration: 600, rule: "", }; }