mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-30 03:22:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41cac2d0ce | ||
|
|
d71a589733 | ||
|
|
35ddf50cbf | ||
|
|
5cdbca2b04 | ||
|
|
768087993e | ||
|
|
211b9645ee | ||
|
|
2ae117228e | ||
|
|
50fe51cecb | ||
|
|
a12bccaafc | ||
|
|
caee2a55f4 | ||
|
|
a47551804c | ||
|
|
86ce1be8fe | ||
|
|
457e094d41 | ||
|
|
5cec579da9 | ||
|
|
5f496665c7 | ||
|
|
adf4eb822f | ||
|
|
114c01ac12 |
No files matched your search
+1
-1
@@ -59,7 +59,7 @@ For the cases where transactions are really needed, users can utilize the [Batch
|
||||
<details>
|
||||
<summary><strong>List/Search side-channel attacks</strong></summary>
|
||||
|
||||
Over the years we've implemented several extra checks to minimize the risk of List/Search side-channel attacks (see especially [v0.32.0](https://github.com/pocketbase/pocketbase/blob/master/CHANGELOG.md#v0320)) but users need to be aware that all client-side filtered fields are technically subject to timing attacks _(whether they are practical or not is a different topic)_.
|
||||
Over the years we've implemented several extra checks to minimize the risk of List/Search side-channel attacks (see especially [v0.32.0](https://github.com/pocketbase/pocketbase/blob/master/CHANGELOG_23_39.md#v0320)) but users need to be aware that all client-side filtered fields are technically subject to timing attacks _(whether they are practical or not is a different topic)_.
|
||||
|
||||
This is by design and it is accepted tradeoff between performance, security and usability.
|
||||
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
## v0.40.5 (WIP)
|
||||
|
||||
- Minor improvements for the JSVM migration error handling:
|
||||
- Included the recovered panic stack trace of `routine.SafeWrap` in the returned error (max 2KB).
|
||||
- Wrap the individual `up`/`down` JSVM migration arguments in `routine.SafeWrap` so that in case of panic we can still print the failed js migration filename.
|
||||
|
||||
- Updated `modernc.org/sqlite` to 1.60.0 _(minor performance improvement by switching to Go's `memmove` on Linux targets)_.
|
||||
|
||||
- Other minor fixes _(godoc typos, normalized negative jsvm pool size, extra CORS wildcard subdomain checks to prevent misuse, support `Promise` as autocomplete suggestions return, etc.)_.
|
||||
|
||||
|
||||
## v0.40.4
|
||||
|
||||
- Fixed migration deadlock if a logs db write happens to run while the migration is still executing ([#7836](https://github.com/pocketbase/pocketbase/issues/7836)).
|
||||
|
||||
- `app.ResetBootstrapState()` was soft-deprecated in favour of `app.ClearBootstrap()`.
|
||||
_Additionally a new `app.OnBootstrapClear()` hook was added to allow clearing custom allocated `OnBootstrap` resources in case the app uses a non-standard initialization (e.g. doesn't call `Start()` or intentionally skip the `OnTerminate` hook)._
|
||||
|
||||
- Bumped `golang.org/x/*` dependencies.
|
||||
|
||||
|
||||
## v0.40.3
|
||||
|
||||
- Write the status header for JSON responses only if the fields picker succeed or has acceptable fallback.
|
||||
|
||||
+16
-11
@@ -31,9 +31,11 @@ const (
|
||||
// CORSConfig defines the config for CORS middleware.
|
||||
type CORSConfig struct {
|
||||
// AllowOrigins determines the value of the Access-Control-Allow-Origin
|
||||
// response header. This header defines a list of origins that may access the
|
||||
// resource. The wildcard characters '*' and '?' are supported and are
|
||||
// converted to regex fragments '.*' and '.' accordingly.
|
||||
// response header. This header defines a list of origins that may access the
|
||||
// resource.
|
||||
//
|
||||
// The wildcard characters '*' and '?' are supported as subdomain segments
|
||||
// and are converted to regex fragments '.*' and '.' accordingly.
|
||||
//
|
||||
// Security: use extreme caution when handling the origin, and carefully
|
||||
// validate any logic. Remember that attackers may register hostile domain names.
|
||||
@@ -209,7 +211,7 @@ func CORS(config CORSConfig) *hook.Handler[*core.RequestEvent] {
|
||||
allowOrigin = o
|
||||
break
|
||||
}
|
||||
if matchSubdomain(origin, o) {
|
||||
if exactWildcardMatch(origin, o) {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
@@ -281,8 +283,8 @@ func matchScheme(domain, pattern string) bool {
|
||||
return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
|
||||
}
|
||||
|
||||
// matchSubdomain compares authority with wildcard
|
||||
func matchSubdomain(domain, pattern string) bool {
|
||||
// exactWildcardMatch compares domain with a * wildcard pattern
|
||||
func exactWildcardMatch(domain, pattern string) bool {
|
||||
if !matchScheme(domain, pattern) {
|
||||
return false
|
||||
}
|
||||
@@ -310,18 +312,21 @@ func matchSubdomain(domain, pattern string) bool {
|
||||
patComp[i], patComp[opp] = patComp[opp], patComp[i]
|
||||
}
|
||||
|
||||
if len(patComp) != len(domComp) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range domComp {
|
||||
if len(patComp) <= i {
|
||||
return false
|
||||
}
|
||||
p := patComp[i]
|
||||
|
||||
if p == "*" {
|
||||
return true
|
||||
continue
|
||||
}
|
||||
|
||||
if p != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package apis
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCorsExactWildcardMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
domain string
|
||||
pattern string
|
||||
expected bool
|
||||
}{
|
||||
{"", "", false},
|
||||
{"http://example.com", "", false},
|
||||
{"", "http://example.com", false},
|
||||
{"http://example.com", "https://example.com", false},
|
||||
{"abc://example.com", "abc://example.com", true},
|
||||
{"https://example.com", "https://example.com", true},
|
||||
{"https://a.example.com", "https://example.com", false},
|
||||
{"https://example.com", "https://a.example.com", false},
|
||||
{"https://example.com", "https://*.example.com", false},
|
||||
{"https://a.example.com", "https://*.example.com", true},
|
||||
{"https://a.example.com", "https://a.*.example.com", false},
|
||||
{"https://a.b.example.com", "https://a.*.example.com", true},
|
||||
{"https://a.b.example.com", "https://a2.*.example.com", false},
|
||||
{"https://a.b.example.com", "https://a.*", false},
|
||||
{"https://a.b.example", "https://a.*", false},
|
||||
{"https://a.b", "https://a.*", true},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.domain+":"+s.pattern, func(t *testing.T) {
|
||||
result := exactWildcardMatch(s.domain, s.pattern)
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v, got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -54,11 +54,16 @@ type App interface {
|
||||
// Bootstrap initializes the application
|
||||
// (aka. create data dir, open db connections, load settings, etc.).
|
||||
//
|
||||
// It will call ResetBootstrapState() if the application was already bootstrapped.
|
||||
// It calls ClearBootstrap() if the application was already bootstrapped.
|
||||
Bootstrap() error
|
||||
|
||||
// ResetBootstrapState releases the initialized core app resources
|
||||
// ClearBootstrap releases the initialized core app resources
|
||||
// (closing db connections, stopping cron ticker, etc.).
|
||||
//
|
||||
// This method is no-op if the application is not bootstrapped yet.
|
||||
ClearBootstrap() error
|
||||
|
||||
// Deprecated: use ClearBootstrap().
|
||||
ResetBootstrapState() error
|
||||
|
||||
// DataDir returns the app data directory path.
|
||||
@@ -713,6 +718,13 @@ type App interface {
|
||||
// resources (db, app settings, etc).
|
||||
OnBootstrap() *hook.Hook[*BootstrapEvent]
|
||||
|
||||
// OnBootstrapClear hook is triggered when clearing the main application
|
||||
// resources (db connections, cron, logger, etc.)
|
||||
//
|
||||
// It is usually invoked automatically right before app termination
|
||||
// or when manually calling app.ClearBootstrap().
|
||||
OnBootstrapClear() *hook.Hook[*BootstrapEvent]
|
||||
|
||||
// OnServe hook is triggered when the app web server is started
|
||||
// (after starting the TCP listener but before initializing the blocking serve task),
|
||||
// allowing you to adjust its options and attach new routes or middlewares.
|
||||
|
||||
+120
-65
@@ -12,6 +12,7 @@ import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
@@ -89,11 +90,12 @@ type BaseApp struct {
|
||||
auxNonconcurrentDB dbx.Builder
|
||||
|
||||
// app event hooks
|
||||
onBootstrap *hook.Hook[*BootstrapEvent]
|
||||
onServe *hook.Hook[*ServeEvent]
|
||||
onTerminate *hook.Hook[*TerminateEvent]
|
||||
onBackupCreate *hook.Hook[*BackupEvent]
|
||||
onBackupRestore *hook.Hook[*BackupEvent]
|
||||
onBootstrap *hook.Hook[*BootstrapEvent]
|
||||
onBootstrapClear *hook.Hook[*BootstrapEvent]
|
||||
onServe *hook.Hook[*ServeEvent]
|
||||
onTerminate *hook.Hook[*TerminateEvent]
|
||||
onBackupCreate *hook.Hook[*BackupEvent]
|
||||
onBackupRestore *hook.Hook[*BackupEvent]
|
||||
|
||||
// db model hooks
|
||||
onModelValidate *hook.Hook[*ModelEvent]
|
||||
@@ -249,6 +251,7 @@ func NewBaseApp(config BaseAppConfig) *BaseApp {
|
||||
func (app *BaseApp) initHooks() {
|
||||
// app event hooks
|
||||
app.onBootstrap = &hook.Hook[*BootstrapEvent]{}
|
||||
app.onBootstrapClear = &hook.Hook[*BootstrapEvent]{}
|
||||
app.onServe = &hook.Hook[*ServeEvent]{}
|
||||
app.onTerminate = &hook.Hook[*TerminateEvent]{}
|
||||
app.onBackupCreate = &hook.Hook[*BackupEvent]{}
|
||||
@@ -405,14 +408,14 @@ func (app *BaseApp) IsBootstrapped() bool {
|
||||
// Bootstrap initializes the application
|
||||
// (aka. create data dir, open db connections, load settings, etc.).
|
||||
//
|
||||
// It will call ResetBootstrapState() if the application was already bootstrapped.
|
||||
// It calls ClearBootstrap() if the application was already bootstrapped.
|
||||
func (app *BaseApp) Bootstrap() error {
|
||||
event := &BootstrapEvent{}
|
||||
event.App = app
|
||||
|
||||
err := app.OnBootstrap().Trigger(event, func(e *BootstrapEvent) error {
|
||||
// clear resources of previous core state (if any)
|
||||
if err := app.ResetBootstrapState(); err != nil {
|
||||
// clear previous bootstrap state (if any)
|
||||
if err := app.ClearBootstrap(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -460,41 +463,55 @@ func (app *BaseApp) Bootstrap() error {
|
||||
return err
|
||||
}
|
||||
|
||||
type closer interface {
|
||||
Close() error
|
||||
// Deprecated: use [ClearBootstrap].
|
||||
func (app *BaseApp) ResetBootstrapState() error {
|
||||
return app.ClearBootstrap()
|
||||
}
|
||||
|
||||
// ResetBootstrapState releases the initialized core app resources
|
||||
// ClearBootstrap releases the initialized core app resources
|
||||
// (closing db connections, stopping cron ticker, etc.).
|
||||
func (app *BaseApp) ResetBootstrapState() error {
|
||||
app.Cron().Stop()
|
||||
|
||||
var errs []error
|
||||
|
||||
dbs := []*dbx.Builder{
|
||||
&app.concurrentDB,
|
||||
&app.nonconcurrentDB,
|
||||
&app.auxConcurrentDB,
|
||||
&app.auxNonconcurrentDB,
|
||||
//
|
||||
// This method is no-op if the application is not bootstrapped yet.
|
||||
func (app *BaseApp) ClearBootstrap() error {
|
||||
if !app.IsBootstrapped() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, db := range dbs {
|
||||
if db == nil {
|
||||
continue
|
||||
event := &BootstrapEvent{}
|
||||
event.App = app
|
||||
|
||||
return app.OnBootstrapClear().Trigger(event, func(e *BootstrapEvent) error {
|
||||
type closer interface {
|
||||
Close() error
|
||||
}
|
||||
if v, ok := (*db).(closer); ok {
|
||||
if err := v.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
var errs []error
|
||||
|
||||
dbs := []*dbx.Builder{
|
||||
&app.concurrentDB,
|
||||
&app.nonconcurrentDB,
|
||||
&app.auxConcurrentDB,
|
||||
&app.auxNonconcurrentDB,
|
||||
}
|
||||
|
||||
for _, db := range dbs {
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := (*db).(closer); ok {
|
||||
if err := v.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
*db = nil
|
||||
}
|
||||
*db = nil
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DB returns the default app data.db builder instance.
|
||||
@@ -817,7 +834,7 @@ func (app *BaseApp) Restart() error {
|
||||
event.IsRestart = true
|
||||
|
||||
return app.OnTerminate().Trigger(event, func(e *TerminateEvent) error {
|
||||
_ = e.App.ResetBootstrapState()
|
||||
_ = e.App.ClearBootstrap()
|
||||
|
||||
// attempt to restart the bootstrap process in case execve returns an error for some reason
|
||||
defer func() {
|
||||
@@ -860,6 +877,10 @@ func (app *BaseApp) OnBootstrap() *hook.Hook[*BootstrapEvent] {
|
||||
return app.onBootstrap
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnBootstrapClear() *hook.Hook[*BootstrapEvent] {
|
||||
return app.onBootstrapClear
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnServe() *hook.Hook[*ServeEvent] {
|
||||
return app.onServe
|
||||
}
|
||||
@@ -1413,7 +1434,15 @@ func (app *BaseApp) registerBaseHooks() {
|
||||
Id: "__pbCronStart__",
|
||||
Func: func(e *ServeEvent) error {
|
||||
app.Cron().Start()
|
||||
return e.Next()
|
||||
},
|
||||
Priority: 999,
|
||||
})
|
||||
|
||||
app.OnBootstrapClear().Bind(&hook.Handler[*BootstrapEvent]{
|
||||
Id: "__pbCronStop__",
|
||||
Func: func(e *BootstrapEvent) error {
|
||||
app.Cron().Stop()
|
||||
return e.Next()
|
||||
},
|
||||
Priority: 999,
|
||||
@@ -1470,9 +1499,41 @@ func getLoggerMinLevel(app App) slog.Level {
|
||||
}
|
||||
|
||||
func (app *BaseApp) initLogger() error {
|
||||
var stopped atomic.Bool
|
||||
|
||||
duration := 3 * time.Second
|
||||
ticker := time.NewTicker(duration)
|
||||
done := make(chan bool, 1)
|
||||
|
||||
done := make(chan struct{}, 1)
|
||||
|
||||
runLogsWrite := func(logs []*logger.Log) {
|
||||
if !app.IsBootstrapped() || app.Settings().Logs.MaxDays == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// write the accumulated logs
|
||||
//
|
||||
// note: based on several local tests there is no
|
||||
// significant performance difference between small number
|
||||
// of separate write queries vs 1 big INSERT
|
||||
app.AuxRunInTransaction(func(txApp App) error {
|
||||
model := &Log{}
|
||||
for _, l := range logs {
|
||||
model.MarkAsNew()
|
||||
model.Id = GenerateDefaultRandomId()
|
||||
model.Level = int(l.Level)
|
||||
model.Message = l.Message
|
||||
model.Data = l.Data
|
||||
model.Created, _ = types.ParseDateTime(l.Time)
|
||||
|
||||
if err := txApp.AuxSave(model); err != nil {
|
||||
log.Println("Failed to write log", model, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
handler := logger.NewBatchHandler(logger.BatchOptions{
|
||||
Level: getLoggerMinLevel(app),
|
||||
@@ -1487,35 +1548,25 @@ func (app *BaseApp) initLogger() error {
|
||||
}
|
||||
}
|
||||
|
||||
ticker.Reset(duration)
|
||||
if !stopped.Load() {
|
||||
ticker.Reset(duration)
|
||||
}
|
||||
|
||||
return app.Settings().Logs.MaxDays > 0
|
||||
},
|
||||
WriteFunc: func(ctx context.Context, logs []*logger.Log) error {
|
||||
if !app.IsBootstrapped() || app.Settings().Logs.MaxDays == 0 {
|
||||
return nil
|
||||
// don't block and wait for the write transaction to complete
|
||||
// when we can't be sure if the logs write wasn't triggered while
|
||||
// inside another AUX db transaction (ticker or batch threshold reached)
|
||||
// which can block indefinitely and cause deadlock
|
||||
// (https://github.com/pocketbase/pocketbase/issues/7836)
|
||||
shouldBlock, _ := ctx.Value(logger.BlockKey).(bool)
|
||||
if shouldBlock {
|
||||
runLogsWrite(logs)
|
||||
} else {
|
||||
routine.FireAndForget(func() { runLogsWrite(logs) })
|
||||
}
|
||||
|
||||
// write the accumulated logs
|
||||
// (note: based on several local tests there is no significant performance difference between small number of separate write queries vs 1 big INSERT)
|
||||
app.AuxRunInTransaction(func(txApp App) error {
|
||||
model := &Log{}
|
||||
for _, l := range logs {
|
||||
model.MarkAsNew()
|
||||
model.Id = GenerateDefaultRandomId()
|
||||
model.Level = int(l.Level)
|
||||
model.Message = l.Message
|
||||
model.Data = l.Data
|
||||
model.Created, _ = types.ParseDateTime(l.Time)
|
||||
|
||||
if err := txApp.AuxSave(model); err != nil {
|
||||
log.Println("Failed to write log", model, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
@@ -1535,17 +1586,21 @@ func (app *BaseApp) initLogger() error {
|
||||
|
||||
app.logger = slog.New(handler)
|
||||
|
||||
// write all remaining logs before ticker.Stop to avoid races with ResetBootstrap user calls
|
||||
app.OnTerminate().Bind(&hook.Handler[*TerminateEvent]{
|
||||
Id: "__pbAppLoggerOnTerminate__",
|
||||
Func: func(e *TerminateEvent) error {
|
||||
handler.WriteAll(context.Background())
|
||||
// attempt to write all queued logs before clearing the application bootstrap state
|
||||
app.OnBootstrapClear().Bind(&hook.Handler[*BootstrapEvent]{
|
||||
Id: "__pbAppLoggerFlushBeforeStop__",
|
||||
Func: func(e *BootstrapEvent) error {
|
||||
// extra precaution in case the hook was manually triggered while inside aux db transaction
|
||||
_, isTx := e.App.AuxNonconcurrentDB().(*dbx.Tx)
|
||||
ctx := context.WithValue(context.Background(), logger.BlockKey, !isTx)
|
||||
handler.WriteAll(ctx)
|
||||
|
||||
stopped.Store(true)
|
||||
ticker.Stop()
|
||||
|
||||
// don't block in case OnTerminate is triggered more than once
|
||||
// don't block in case the hook is triggered more than once
|
||||
select {
|
||||
case done <- true:
|
||||
case done <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
+144
-43
@@ -65,7 +65,7 @@ func TestBaseAppBootstrap(t *testing.T) {
|
||||
app := core.NewBaseApp(core.BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
if app.IsBootstrapped() {
|
||||
t.Fatal("Didn't expect the application to be bootstrapped.")
|
||||
@@ -115,7 +115,7 @@ func TestBaseAppBootstrap(t *testing.T) {
|
||||
runNilChecks(nilChecksBeforeReset)
|
||||
|
||||
// reset
|
||||
if err := app.ResetBootstrapState(); err != nil {
|
||||
if err := app.ClearBootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func TestNewBaseAppTx(t *testing.T) {
|
||||
app := core.NewBaseApp(core.BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -185,7 +185,7 @@ func TestBaseAppNewMailClient(t *testing.T) {
|
||||
DataDir: testDataDir,
|
||||
EncryptionEnv: "pb_test_env",
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
client1 := app.NewMailClient()
|
||||
m1, ok := client1.(*mailer.Sendmail)
|
||||
@@ -215,7 +215,7 @@ func TestBaseAppNewFilesystem(t *testing.T) {
|
||||
app := core.NewBaseApp(core.BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
// local
|
||||
local, localErr := app.NewFilesystem()
|
||||
@@ -244,7 +244,7 @@ func TestBaseAppNewBackupsFilesystem(t *testing.T) {
|
||||
app := core.NewBaseApp(core.BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
// local
|
||||
local, localErr := app.NewBackupsFilesystem()
|
||||
@@ -266,74 +266,175 @@ func TestBaseAppNewBackupsFilesystem(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const logsThreshold = 200
|
||||
|
||||
func assertLogsCount(t *testing.T, app core.App, expected int) {
|
||||
var total int
|
||||
|
||||
err := app.LogQuery().Select("count(*)").Row(&total)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch total logs: %v", err)
|
||||
}
|
||||
|
||||
if total != expected {
|
||||
t.Fatalf("Expected %d log(s), got %d", expected, total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAppLoggerWrites(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// note: outside of synctest because the bootstrap tickers could deadlock
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
// reset
|
||||
if err := app.DeleteOldLogs(time.Now()); err != nil {
|
||||
// clear old logs
|
||||
err := app.DeleteOldLogs(time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const logsThreshold = 200
|
||||
|
||||
totalLogs := func(app core.App, t *testing.T) int {
|
||||
var total int
|
||||
|
||||
err := app.LogQuery().Select("count(*)").Row(&total)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch total logs: %v", err)
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
t.Run("disabled logs retention", func(t *testing.T) {
|
||||
app.Settings().Logs.MaxDays = 0
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
app.Settings().Logs.MaxDays = 0
|
||||
|
||||
for i := 0; i < logsThreshold+1; i++ {
|
||||
app.Logger().Error("test")
|
||||
}
|
||||
for i := 0; i < logsThreshold+1; i++ {
|
||||
app.Logger().Error("test")
|
||||
}
|
||||
|
||||
if total := totalLogs(app, t); total != 0 {
|
||||
t.Fatalf("Expected no logs, got %d", total)
|
||||
}
|
||||
// short delay for the non-blocking write goroutine
|
||||
synctest.Sleep(time.Nanosecond)
|
||||
|
||||
assertLogsCount(t, app, 0)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("test batch logs writes", func(t *testing.T) {
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
app.Settings().Logs.MaxDays = 1
|
||||
app.Settings().Logs.MaxDays = 2
|
||||
|
||||
for i := 0; i < logsThreshold-1; i++ {
|
||||
app.Logger().Error("test")
|
||||
}
|
||||
|
||||
if total := totalLogs(app, t); total != 0 {
|
||||
t.Fatalf("Expected no logs, got %d", total)
|
||||
}
|
||||
// short delay for the non-blocking write goroutine
|
||||
synctest.Sleep(time.Nanosecond)
|
||||
|
||||
// should trigger batch write
|
||||
// below threshold
|
||||
assertLogsCount(t, app, 0)
|
||||
|
||||
// threshold reached -> should trigger batch write
|
||||
app.Logger().Error("test")
|
||||
|
||||
// should be added for the next batch write
|
||||
// should be skipped from this batch and added for the next
|
||||
app.Logger().Error("test")
|
||||
|
||||
if total := totalLogs(app, t); total != logsThreshold {
|
||||
t.Fatalf("Expected %d logs, got %d", logsThreshold, total)
|
||||
}
|
||||
// short delay for the non-blocking write goroutine
|
||||
synctest.Sleep(time.Nanosecond)
|
||||
|
||||
// wait for 3 secs to check the timer trigger
|
||||
synctest.Sleep(3000 * time.Millisecond)
|
||||
assertLogsCount(t, app, logsThreshold)
|
||||
|
||||
if total := totalLogs(app, t); total != logsThreshold {
|
||||
t.Fatalf("Expected %d logs, got %d", logsThreshold, total)
|
||||
}
|
||||
// note: we can't test the flush timer here because the ticker
|
||||
// was started out of the synctest buble to avoid deadlocks
|
||||
// (see TestBaseAppLoggerWritesAwaited for a flaky but real timer test)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseAppLoggerWritesAwaited(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
// clear old logs
|
||||
err := app.DeleteOldLogs(time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// enable logs persistence
|
||||
app.Settings().Logs.MaxDays = 1
|
||||
err = app.Save(app.Settings())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("flush on timer tick", func(t *testing.T) {
|
||||
timeout := time.After(5 * time.Second)
|
||||
done := make(chan struct{})
|
||||
|
||||
logsHook := app.OnModelAfterCreateSuccess("_logs")
|
||||
hookId := logsHook.BindFunc(func(e *core.ModelEvent) error {
|
||||
done <- struct{}{}
|
||||
return e.Next()
|
||||
})
|
||||
defer logsHook.Unbind(hookId)
|
||||
|
||||
app.Logger().Error("test")
|
||||
|
||||
// short wait to ensure that there is no non-blocking write
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
assertLogsCount(t, app, 0)
|
||||
|
||||
// wait for the ticker to write the db record
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("ticker wait timeout")
|
||||
case <-done:
|
||||
}
|
||||
|
||||
assertLogsCount(t, app, 1)
|
||||
})
|
||||
|
||||
t.Run("before ClearBootstrap flush", func(t *testing.T) {
|
||||
app.Logger().Error("test")
|
||||
|
||||
app.Bootstrap()
|
||||
|
||||
assertLogsCount(t, app, 2)
|
||||
})
|
||||
|
||||
t.Run("batch flush inside aux transaction shouldn't hang", func(t *testing.T) {
|
||||
timeout := time.After(1 * time.Second)
|
||||
done := make(chan struct{})
|
||||
totalCreated := 0
|
||||
|
||||
logsHook := app.OnModelAfterCreateSuccess("_logs")
|
||||
hookId := logsHook.BindFunc(func(e *core.ModelEvent) error {
|
||||
totalCreated++
|
||||
if totalCreated == 200 {
|
||||
done <- struct{}{}
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
defer logsHook.Unbind(hookId)
|
||||
|
||||
app.AuxRunInTransaction(func(txApp core.App) error {
|
||||
for range logsThreshold {
|
||||
txApp.Logger().Error("test")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// wait for the non-blocking write
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("non-blocking write timeout")
|
||||
case <-done:
|
||||
}
|
||||
|
||||
assertLogsCount(t, app, 202)
|
||||
|
||||
// force clear to ensure that there are no other logs
|
||||
app.Bootstrap()
|
||||
|
||||
assertLogsCount(t, app, 202)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
@@ -373,7 +474,7 @@ func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) {
|
||||
DataDir: testDataDir,
|
||||
IsDev: s.isDev,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -3,17 +3,24 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/logger"
|
||||
)
|
||||
|
||||
func TestBaseAppLoggerLevelDevPrint(t *testing.T) {
|
||||
// temp unset to avoid littering the stdout if the test fails when in dev mode
|
||||
colorOutput := color.Output
|
||||
color.Output = io.Discard
|
||||
defer func() { color.Output = colorOutput }()
|
||||
|
||||
testLogLevel := 4
|
||||
|
||||
scenarios := []struct {
|
||||
@@ -48,7 +55,7 @@ func TestBaseAppLoggerLevelDevPrint(t *testing.T) {
|
||||
DataDir: testDataDir,
|
||||
IsDev: s.isDev,
|
||||
})
|
||||
defer app.ResetBootstrapState()
|
||||
defer app.ClearBootstrap()
|
||||
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -68,7 +75,7 @@ func TestBaseAppLoggerLevelDevPrint(t *testing.T) {
|
||||
var printedLevels []int
|
||||
var persistedLevels []int
|
||||
|
||||
ctx := context.Background()
|
||||
ctx := context.WithValue(context.Background(), logger.BlockKey, true)
|
||||
|
||||
// track printed logs
|
||||
originalPrintLog := printLog
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestSendSystemAlert(t *testing.T) {
|
||||
testApp := NewBaseApp(BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer testApp.ResetBootstrapState()
|
||||
defer testApp.ClearBootstrap()
|
||||
|
||||
if err := testApp.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -72,7 +72,7 @@ func TestSendSystemAlertToAllSuperusers(t *testing.T) {
|
||||
testApp := NewBaseApp(BaseAppConfig{
|
||||
DataDir: testDataDir,
|
||||
})
|
||||
defer testApp.ResetBootstrapState()
|
||||
defer testApp.ClearBootstrap()
|
||||
|
||||
if err := testApp.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -17,12 +17,12 @@ require (
|
||||
github.com/pocketbase/tygoja v0.1.0
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
golang.org/x/crypto v0.56.0
|
||||
golang.org/x/image v0.45.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.22.0
|
||||
modernc.org/sqlite v1.57.0
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/image v0.46.0
|
||||
golang.org/x/net v0.59.0
|
||||
golang.org/x/oauth2 v0.37.0
|
||||
golang.org/x/sync v0.23.0
|
||||
modernc.org/sqlite v1.60.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -40,11 +40,11 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
golang.org/x/mod v0.40.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/tools v0.49.0 // indirect
|
||||
modernc.org/libc v1.74.4 // indirect
|
||||
golang.org/x/mod v0.41.0 // indirect
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
golang.org/x/tools v0.50.0 // indirect
|
||||
modernc.org/libc v1.77.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.12.0 // indirect
|
||||
modernc.org/memory v1.12.1 // indirect
|
||||
)
|
||||
@@ -88,30 +88,30 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
|
||||
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
|
||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
|
||||
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
|
||||
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
|
||||
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -119,30 +119,30 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
|
||||
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
|
||||
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
|
||||
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
|
||||
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.12.0 h1:twkmYNkGXCvtYWzoux02jtK6eovjZbdI0uHFUYp6kuU=
|
||||
modernc.org/memory v1.12.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
|
||||
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||
modernc.org/sqlite v1.60.0 h1:7AZh8lREDo8x3j7aSdF7KGpAKUkJExJ1p67tcRnmttM=
|
||||
modernc.org/sqlite v1.60.0/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -64,7 +64,7 @@ const Layout = `
|
||||
text-align: center;
|
||||
padding: 0 20px;
|
||||
margin: 5px 0;
|
||||
font-family: Source Sans Pro, sans-serif, emoji;;
|
||||
font-family: Source Sans Pro, sans-serif, emoji;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
expectedDriverVersion = "v1.57.0"
|
||||
expectedLibcVersion = "v1.74.4"
|
||||
expectedDriverVersion = "v1.60.0"
|
||||
expectedLibcVersion = "v1.77.1"
|
||||
|
||||
// ModerncDepsCheckHookId is the id of the hook that performs the modernc.org/* deps checks.
|
||||
// It could be used for removing/unbinding the hook if you don't want the checks.
|
||||
|
||||
@@ -1613,7 +1613,7 @@ func TestHooksBindsCount(t *testing.T) {
|
||||
vm := goja.New()
|
||||
hooksBinds(app, vm, nil)
|
||||
|
||||
testBindsCount(vm, "this", 82, t)
|
||||
testBindsCount(vm, "this", 83, t)
|
||||
}
|
||||
|
||||
func TestHooksBinds(t *testing.T) {
|
||||
|
||||
+3073
-3043
File diff suppressed because it is too large.
Load diff
+24
-4
@@ -83,10 +83,10 @@ type Config struct {
|
||||
HooksFilesPattern string
|
||||
|
||||
// HooksPoolSize specifies how many goja.Runtime instances to prewarm
|
||||
// and keep for the JS app hooks gorotines execution.
|
||||
// and keep for the JS app hooks goroutine execution to reuse.
|
||||
//
|
||||
// Zero or negative value means that it will create a new goja.Runtime
|
||||
// on every fired goroutine.
|
||||
// Zero or negative value means that no pool will be maintained and
|
||||
// instead it will create a new goja.Runtime on every fired goroutine.
|
||||
HooksPoolSize int
|
||||
|
||||
// MigrationsDir specifies the JS migrations directory.
|
||||
@@ -218,7 +218,27 @@ func (p *plugin) registerMigrations() error {
|
||||
vm.Set("__hooks", absHooksDir)
|
||||
|
||||
vm.Set("migrate", func(up, down func(txApp core.App) error) {
|
||||
core.AppMigrations.Register(up, down, file)
|
||||
// note: safe wrap to capture any eventual panic and to allow
|
||||
// the error message to print the migration filename
|
||||
core.AppMigrations.Register(
|
||||
func(txApp core.App) error {
|
||||
return routine.SafeWrap(func() error {
|
||||
if up == nil {
|
||||
return nil
|
||||
}
|
||||
return up(txApp)
|
||||
})()
|
||||
},
|
||||
func(txApp core.App) error {
|
||||
return routine.SafeWrap(func() error {
|
||||
if down == nil {
|
||||
return nil
|
||||
}
|
||||
return down(txApp)
|
||||
})()
|
||||
},
|
||||
file,
|
||||
)
|
||||
})
|
||||
|
||||
if p.config.OnInit != nil {
|
||||
|
||||
@@ -20,6 +20,10 @@ type vmsPool struct {
|
||||
|
||||
// newPool creates a new pool with pre-warmed vms generated from the specified factory.
|
||||
func newPool(size int, factory func() *goja.Runtime) *vmsPool {
|
||||
if size < 0 {
|
||||
size = 0
|
||||
}
|
||||
|
||||
pool := &vmsPool{
|
||||
factory: factory,
|
||||
items: make([]*poolItem, size),
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ func (pb *PocketBase) Execute() error {
|
||||
event := new(core.TerminateEvent)
|
||||
event.App = pb
|
||||
return pb.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error {
|
||||
return errors.Join(e.App.ResetBootstrapState(), execErr)
|
||||
return errors.Join(e.App.ClearBootstrap(), execErr)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -37,9 +37,9 @@ func (t *TestApp) Cleanup() {
|
||||
event.App = t
|
||||
|
||||
t.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error {
|
||||
t.ClearBootstrap()
|
||||
t.TestMailer.Reset()
|
||||
t.ResetEventCalls()
|
||||
t.ResetBootstrapState()
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
@@ -144,6 +144,14 @@ func NewTestAppWithConfig(config core.BaseAppConfig) (*TestApp, error) {
|
||||
Priority: -99999,
|
||||
})
|
||||
|
||||
t.OnBootstrapClear().Bind(&hook.Handler[*core.BootstrapEvent]{
|
||||
Func: func(e *core.BootstrapEvent) error {
|
||||
t.registerEventCall("OnBootstrapClear")
|
||||
return e.Next()
|
||||
},
|
||||
Priority: -99999,
|
||||
})
|
||||
|
||||
t.OnServe().Bind(&hook.Handler[*core.ServeEvent]{
|
||||
Func: func(e *core.ServeEvent) error {
|
||||
t.registerEventCall("OnServe")
|
||||
|
||||
@@ -11,6 +11,13 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
// contextKey is an alias type to prevent collisions with other log context keys.
|
||||
type contextKey int
|
||||
|
||||
// BlockKey is a context key usually used to indicate that the
|
||||
// batched logs write should block until writes are completed.
|
||||
var BlockKey contextKey
|
||||
|
||||
var _ slog.Handler = (*BatchHandler)(nil)
|
||||
|
||||
// BatchOptions are options for the BatchHandler.
|
||||
|
||||
@@ -22,8 +22,8 @@ func FireAndForget(f func(), wg ...*sync.WaitGroup) {
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("[FireAndForget] RECOVERED FROM PANIC:", err)
|
||||
if rec := recover(); rec != nil {
|
||||
log.Println("[FireAndForget] RECOVERED FROM PANIC:", rec)
|
||||
|
||||
stack := make([]byte, 2<<10) // 2 KB
|
||||
length := runtime.Stack(stack, false)
|
||||
@@ -40,8 +40,10 @@ func FireAndForget(f func(), wg ...*sync.WaitGroup) {
|
||||
func SafeWrap(f func() error) func() error {
|
||||
return func() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("[SafeWrap] recovered from panic: %v", r)
|
||||
if rec := recover(); rec != nil {
|
||||
stack := make([]byte, 2<<10) // 2 KB
|
||||
length := runtime.Stack(stack, false)
|
||||
err = fmt.Errorf("[SafeWrap] recovered from panic: %v\n%s", rec, string(stack[:length]))
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ func (s *SortField) BuildExpr(fieldResolver FieldResolver) (string, error) {
|
||||
result, err := fieldResolver.Resolve(s.Name)
|
||||
|
||||
// invalidate empty fields and non-column identifiers
|
||||
if err != nil || len(result.Params) > 0 || result.Identifier == "" || strings.ToLower(result.Identifier) == "null" {
|
||||
if err != nil || len(result.Params) > 0 || result.Identifier == "" || strings.EqualFold(result.Identifier, "null") {
|
||||
return "", fmt.Errorf("invalid sort field %q", s.Name)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs"
|
||||
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
|
||||
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
|
||||
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
|
||||
PB_VERSION = "v0.40.3"
|
||||
PB_VERSION = "v0.40.5-dev"
|
||||
@@ -1 +1 @@
|
||||
@font-face{font-display:block;font-family:remixicon;src:url(../fonts/remixicon/remixicon.woff2?t=1734404658139)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-regular.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-italic.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600italic.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-regular.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-600.woff2)format("woff2")}.ri-24-hours-fill:before{content:""}.ri-24-hours-line:before{content:""}.ri-4k-fill:before{content:""}.ri-4k-line:before{content:""}.ri-a-b:before{content:""}.ri-account-box-fill:before{content:""}.ri-account-box-line:before{content:""}.ri-account-circle-fill:before{content:""}.ri-account-circle-line:before{content:""}.ri-account-pin-box-fill:before{content:""}.ri-account-pin-box-line:before{content:""}.ri-account-pin-circle-fill:before{content:""}.ri-account-pin-circle-line:before{content:""}.ri-add-box-fill:before{content:""}.ri-add-box-line:before{content:""}.ri-add-circle-fill:before{content:""}.ri-add-circle-line:before{content:""}.ri-add-fill:before{content:""}.ri-add-line:before{content:""}.ri-admin-fill:before{content:""}.ri-admin-line:before{content:""}.ri-advertisement-fill:before{content:""}.ri-advertisement-line:before{content:""}.ri-airplay-fill:before{content:""}.ri-airplay-line:before{content:""}.ri-alarm-fill:before{content:""}.ri-alarm-line:before{content:""}.ri-alarm-warning-fill:before{content:""}.ri-alarm-warning-line:before{content:""}.ri-album-fill:before{content:""}.ri-album-line:before{content:""}.ri-alert-fill:before{content:""}.ri-alert-line:before{content:""}.ri-aliens-fill:before{content:""}.ri-aliens-line:before{content:""}.ri-align-bottom:before{content:""}.ri-align-center:before{content:""}.ri-align-justify:before{content:""}.ri-align-left:before{content:""}.ri-align-right:before{content:""}.ri-align-top:before{content:""}.ri-align-vertically:before{content:""}.ri-alipay-fill:before{content:""}.ri-alipay-line:before{content:""}.ri-amazon-fill:before{content:""}.ri-amazon-line:before{content:""}.ri-anchor-fill:before{content:""}.ri-anchor-line:before{content:""}.ri-ancient-gate-fill:before{content:""}.ri-ancient-gate-line:before{content:""}.ri-ancient-pavilion-fill:before{content:""}.ri-ancient-pavilion-line:before{content:""}.ri-android-fill:before{content:""}.ri-android-line:before{content:""}.ri-angularjs-fill:before{content:""}.ri-angularjs-line:before{content:""}.ri-anticlockwise-2-fill:before{content:""}.ri-anticlockwise-2-line:before{content:""}.ri-anticlockwise-fill:before{content:""}.ri-anticlockwise-line:before{content:""}.ri-app-store-fill:before{content:""}.ri-app-store-line:before{content:""}.ri-apple-fill:before{content:""}.ri-apple-line:before{content:""}.ri-apps-2-fill:before{content:""}.ri-apps-2-line:before{content:""}.ri-apps-fill:before{content:""}.ri-apps-line:before{content:""}.ri-archive-drawer-fill:before{content:""}.ri-archive-drawer-line:before{content:""}.ri-archive-fill:before{content:""}.ri-archive-line:before{content:""}.ri-arrow-down-circle-fill:before{content:""}.ri-arrow-down-circle-line:before{content:""}.ri-arrow-down-fill:before{content:""}.ri-arrow-down-line:before{content:""}.ri-arrow-down-s-fill:before{content:""}.ri-arrow-down-s-line:before{content:""}.ri-arrow-drop-down-fill:before{content:""}.ri-arrow-drop-down-line:before{content:""}.ri-arrow-drop-left-fill:before{content:""}.ri-arrow-drop-left-line:before{content:""}.ri-arrow-drop-right-fill:before{content:""}.ri-arrow-drop-right-line:before{content:""}.ri-arrow-drop-up-fill:before{content:""}.ri-arrow-drop-up-line:before{content:""}.ri-arrow-go-back-fill:before{content:""}.ri-arrow-go-back-line:before{content:""}.ri-arrow-go-forward-fill:before{content:""}.ri-arrow-go-forward-line:before{content:""}.ri-arrow-left-circle-fill:before{content:""}.ri-arrow-left-circle-line:before{content:""}.ri-aLine truncated
|
||||
@font-face{font-display:block;font-family:remixicon;src:url(../fonts/remixicon/remixicon.woff2?t=1734404658139)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-regular.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-italic.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600italic.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-regular.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-600.woff2)format("woff2")}.ri-24-hours-fill:before{content:""}.ri-24-hours-line:before{content:""}.ri-4k-fill:before{content:""}.ri-4k-line:before{content:""}.ri-a-b:before{content:""}.ri-account-box-fill:before{content:""}.ri-account-box-line:before{content:""}.ri-account-circle-fill:before{content:""}.ri-account-circle-line:before{content:""}.ri-account-pin-box-fill:before{content:""}.ri-account-pin-box-line:before{content:""}.ri-account-pin-circle-fill:before{content:""}.ri-account-pin-circle-line:before{content:""}.ri-add-box-fill:before{content:""}.ri-add-box-line:before{content:""}.ri-add-circle-fill:before{content:""}.ri-add-circle-line:before{content:""}.ri-add-fill:before{content:""}.ri-add-line:before{content:""}.ri-admin-fill:before{content:""}.ri-admin-line:before{content:""}.ri-advertisement-fill:before{content:""}.ri-advertisement-line:before{content:""}.ri-airplay-fill:before{content:""}.ri-airplay-line:before{content:""}.ri-alarm-fill:before{content:""}.ri-alarm-line:before{content:""}.ri-alarm-warning-fill:before{content:""}.ri-alarm-warning-line:before{content:""}.ri-album-fill:before{content:""}.ri-album-line:before{content:""}.ri-alert-fill:before{content:""}.ri-alert-line:before{content:""}.ri-aliens-fill:before{content:""}.ri-aliens-line:before{content:""}.ri-align-bottom:before{content:""}.ri-align-center:before{content:""}.ri-align-justify:before{content:""}.ri-align-left:before{content:""}.ri-align-right:before{content:""}.ri-align-top:before{content:""}.ri-align-vertically:before{content:""}.ri-alipay-fill:before{content:""}.ri-alipay-line:before{content:""}.ri-amazon-fill:before{content:""}.ri-amazon-line:before{content:""}.ri-anchor-fill:before{content:""}.ri-anchor-line:before{content:""}.ri-ancient-gate-fill:before{content:""}.ri-ancient-gate-line:before{content:""}.ri-ancient-pavilion-fill:before{content:""}.ri-ancient-pavilion-line:before{content:""}.ri-android-fill:before{content:""}.ri-android-line:before{content:""}.ri-angularjs-fill:before{content:""}.ri-angularjs-line:before{content:""}.ri-anticlockwise-2-fill:before{content:""}.ri-anticlockwise-2-line:before{content:""}.ri-anticlockwise-fill:before{content:""}.ri-anticlockwise-line:before{content:""}.ri-app-store-fill:before{content:""}.ri-app-store-line:before{content:""}.ri-apple-fill:before{content:""}.ri-apple-line:before{content:""}.ri-apps-2-fill:before{content:""}.ri-apps-2-line:before{content:""}.ri-apps-fill:before{content:""}.ri-apps-line:before{content:""}.ri-archive-drawer-fill:before{content:""}.ri-archive-drawer-line:before{content:""}.ri-archive-fill:before{content:""}.ri-archive-line:before{content:""}.ri-arrow-down-circle-fill:before{content:""}.ri-arrow-down-circle-line:before{content:""}.ri-arrow-down-fill:before{content:""}.ri-arrow-down-line:before{content:""}.ri-arrow-down-s-fill:before{content:""}.ri-arrow-down-s-line:before{content:""}.ri-arrow-drop-down-fill:before{content:""}.ri-arrow-drop-down-line:before{content:""}.ri-arrow-drop-left-fill:before{content:""}.ri-arrow-drop-left-line:before{content:""}.ri-arrow-drop-right-fill:before{content:""}.ri-arrow-drop-right-line:before{content:""}.ri-arrow-drop-up-fill:before{content:""}.ri-arrow-drop-up-line:before{content:""}.ri-arrow-go-back-fill:before{content:""}.ri-arrow-go-back-line:before{content:""}.ri-arrow-go-forward-fill:before{content:""}.ri-arrow-go-forward-line:before{content:""}.ri-arrow-left-circle-fill:before{content:""}.ri-arrow-left-circle-line:before{content:""}.ri-aLine truncated
|
||||
@@ -3,8 +3,8 @@ var e=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=
|
||||
`),i=new Blob([r],{type:`text/csv;charset=utf-8;`}),a=window.URL.createObjectURL(i);f.download(a,n)},getApiExampleURL(){let e;if(app.pb.baseURL.startsWith(`http://`)||app.pb.baseURL.startsWith(`https://`))e=app.pb.baseURL;else{e=window.location.href;let n=e.indexOf(`/_/`);e=n>=0?e.substring(0,n):window.location.origin}return e.replace(`//localhost`,`//127.0.0.1`)},isActivePath(e,n=!0,r=``){r||=d.hash;let i;return i=RegExp(n?`^`+RegExp.escape(e)+`\\/?.*$`:`^`+RegExp.escape(e)+`\\/?(?:\\?.+)?$`),i.test(r)},getHashQueryParams(e=``){e||=d.hash;let n=``,r=e.indexOf(`?`);return r>-1&&(n=window.location.hash.substring(r+1)),Object.fromEntries(new URLSearchParams(n))},replaceHashQueryParams(e,n=null){e||={};let r=``,i=window.location.hash,a=i.indexOf(`?`);a>-1&&(r=i.substring(a+1),i=i.substring(0,a));let o=new URLSearchParams(r);for(let n in e){let r=e[n];f.isEmpty(r)?o.delete(n):o.set(n,r)}r=o.toString(),r!=``&&(i+=`?`+r);let s=window.location.href,c=s.indexOf(`#`);c>-1&&(s=s.substring(0,c));let l=s+i;return n===!1||(n===!0?window.history.pushState(null,``,l):window.history.replaceState(null,``,l)),l},getLocalHistory(e,n=null){try{let r=window.localStorage.getItem(e);if(r)return JSON.parse(r)||n}catch(n){console.log(`failed to load local history:`,e,n)}return n},saveLocalHistory(e,n){try{app.utils.isEmpty(n)?window.localStorage.removeItem(e):typeof n==`string`?window.localStorage.setItem(e,n):window.localStorage.setItem(e,JSON.stringify(n))}catch(n){console.log(`failed to save local history:`,e,n)}},generateThumb(e,n=100,r=100){return new Promise(i=>{let a=new FileReader;a.onload=function(a){let o=new Image;o.onload=function(){let a=document.createElement(`canvas`),s=a.getContext(`2d`),c=o.width,l=o.height;return a.width=n,a.height=r,s.drawImage(o,c>l?(c-l)/2:0,0,c>l?l:c,c>l?l:c,0,0,n,r),i(a.toDataURL(e.type))},o.src=a.target.result},a.readAsDataURL(e)})},normalizeSearchFilter(e,n=[]){if(e=(e||``).trim(),!e||!n.length)return e;for(let n of[`=`,`~`,`>`,`<`])if(e.includes(n))return e;return e=isNaN(e)&&e!=`true`&&e!=`false`?e.replace(/^[\"\'\`]|[\"\'\`]$/gm,``):e,n.map(n=>app.pb.filter(`${n}?~{:searchTerm}`,{searchTerm:e})).join(`||`)},logLevels:{[-4]:{label:`DEBUG`,class:``},0:{label:`INFO`,class:`success`},4:{label:`WARN`,class:`warning`},8:{label:`ERROR`,class:`danger`}},logDataFormatters:{execTime:function(e){return e?.data?.execTime===void 0?`N/A`:e.data.execTime+`ms`}},extendStore(e,n={},...r){let i=[];for(let a in n){let o=n[a];typeof e.__raw?.[a]==`function`||typeof o!=`function`||a.length>2&&a.startsWith(`on`)||r.includes(a)?e[a]=o:i.push(watch(o,n=>{e[a]=n}))}return i},cssTimeToMs(e){return e?(e=e.toLowerCase(),e.endsWith(`ms`)?Number(e.substring(0,e.length-2)):e.endsWith(`s`)?Number(e.substring(0,e.length-1)):Number(e)||0):0},isDarkEnoughForWhiteText(e){if(e=e?.startsWith(`#`)?e.substring(1):e,e?.length!=6)return!1;let n=parseInt(e.substring(0,2),16),r=parseInt(e.substring(2,4),16),i=parseInt(e.substring(4,6),16);return(n*299+r*587+i*114)/1e3<128},bulkSelectRange(e,n,r,i){let a=e.findIndex(e=>e.id==r.id);if(a==-1)return;let o=Object.keys(n),s=o[o.length-1],c=e.findIndex(e=>e.id==s);if(c==-1)return;let l=c,u=a;l>u&&([l,u]=[u,l]);for(let r=l;r<=u;r++)i?n[e[r].id]=e[r]:delete n[e[r].id]},imageExtensions:[`.jpg`,`.jpeg`,`.png`,`.svg`,`.gif`,`.jfif`,`.webp`,`.avif`],videoExtensions:[`.mp4`,`.avi`,`.mov`,`.3gp`,`.wmv`],audioExtensions:[`.aa`,`.aac`,`.m4v`,`.mp3`,`.ogg`,`.oga`,`.mogg`,`.amr`],documentExtensions:[`.pdf`,`.doc`,`.docx`,`.xls`,`.xlsx`,`.ppt`,`.pptx`,`.odp`,`.odt`,`.ods`,`.txt`],archiveExtensions:[`.zip`,`.gz`,`.tar`,`.rar`,`.7z`],hasExtension(e,n){return typeof n==`string`&&(n=n.toLowerCase(),!!e?.find(e=>n.endsWith(e)))},hasImageExtension(e){return app.utils.hasExtension(app.utils.imageExtensions,e)},hasVideoExtension(e){return app.utils.hasExtension(app.utils.videoExtensions,e)},hasAudioExtension(e){return app.utils.hasExtension(app.utils.audioExtensions,e)},hasDocumentExtension(e){return app.utils.hasExtension(app.utils.documentExtensions,e)},hasArchiveExtension(e){return app.utils.hasExtension(app.utils.archiveExtensions,e)},getFileType(e){return app.utils.hasImageExtension(e)?`image`:app.utils.hasVideoExtension(e)?`video`:app.utils.hasAudioExtension(e)?`audio`:app.utils.hasDocumentExtension(e)?`document`:app.utils.hasArchiveExtension(e)?`archive`:`file`},fileTypeIcons:{image:`ri-image-line`,video:`ri-movie-line`,audio:`ri-music-2-line`,document:`ri-file-line`,archive:`ri-file-zip-line`,file:`ri-file-line`},fallbackFieldIcon:`ri-puzzle-line`,fallbackCollectionIcon:`ri-puzzle-line`,fallbackProviderIcon:`ri-puzzle-line`,fallbackPresentableProps:[`title`,`name`,`slug`,`email`,`username`,`nickname`,`displayName`,`label`,`subject`,`topic`,`message`,`heading`,`headline`,`header`,`caption`,`key`,`identifier`,`id`],sortedCollections(e=[]){let n,r;function i(e,i){return n=e.name.startsWith(`_`),r=i.name.startsWith(`_`),n&&!r?1:!n&&r?-1:e.name>i.name?1:e.name<i.name?-1:0}return e.slice().sort(i)},sorted
Line truncated
|
||||
`),n+=r.map(e=>{let n=``;return e.name.includes(`(`)||e.name.includes(` `)?n+=e.name:n+="`"+e.name+"`",e.collate&&(n+=` COLLATE `+e.collate),e.sort&&(n+=` `+e.sort.toUpperCase()),n}).join(`,
|
||||
`),r.length>1&&(n+=`
|
||||
`),n+=`)`,e.where&&(n+=` WHERE ${e.where}`),n},replaceIndexFields(e,n){let r=app.utils.parseIndex(e);return typeof n==`function`?Object.assign(r,n(r)||{}):Object.assign(r,n||{}),app.utils.buildIndex(r)},replaceIndexColumn(e,n,r){if(n===r)return e;let i=app.utils.parseIndex(e),a=!1;for(let e of i.columns)e.name===n&&(e.name=r,a=!0);return a?app.utils.buildIndex(i):e}};window.app=window.app||{},window.app.utils=f,window.app=window.app||{},window.app.utils=window.app.utils||{},window.app.utils.mimeTypes=[{ext:``,mimeType:`application/octet-stream`},{ext:`.xpm`,mimeType:`image/x-xpixmap`},{ext:`.7z`,mimeType:`application/x-7z-compressed`},{ext:`.zip`,mimeType:`application/zip`},{ext:`.xlsx`,mimeType:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`},{ext:`.docx`,mimeType:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`},{ext:`.pptx`,mimeType:`application/vnd.openxmlformats-officedocument.presentationml.presentation`},{ext:`.epub`,mimeType:`application/epub+zip`},{ext:`.apk`,mimeType:`application/vnd.android.package-archive`},{ext:`.jar`,mimeType:`application/jar`},{ext:`.odt`,mimeType:`application/vnd.oasis.opendocument.text`},{ext:`.ott`,mimeType:`application/vnd.oasis.opendocument.text-template`},{ext:`.ods`,mimeType:`application/vnd.oasis.opendocument.spreadsheet`},{ext:`.ots`,mimeType:`application/vnd.oasis.opendocument.spreadsheet-template`},{ext:`.odp`,mimeType:`application/vnd.oasis.opendocument.presentation`},{ext:`.otp`,mimeType:`application/vnd.oasis.opendocument.presentation-template`},{ext:`.odg`,mimeType:`application/vnd.oasis.opendocument.graphics`},{ext:`.otg`,mimeType:`application/vnd.oasis.opendocument.graphics-template`},{ext:`.odf`,mimeType:`application/vnd.oasis.opendocument.formula`},{ext:`.odc`,mimeType:`application/vnd.oasis.opendocument.chart`},{ext:`.sxc`,mimeType:`application/vnd.sun.xml.calc`},{ext:`.pdf`,mimeType:`application/pdf`},{ext:`.fdf`,mimeType:`application/vnd.fdf`},{ext:``,mimeType:`application/x-ole-storage`},{ext:`.msi`,mimeType:`application/x-ms-installer`},{ext:`.aaf`,mimeType:`application/octet-stream`},{ext:`.msg`,mimeType:`application/vnd.ms-outlook`},{ext:`.xls`,mimeType:`application/vnd.ms-excel`},{ext:`.pub`,mimeType:`application/vnd.ms-publisher`},{ext:`.ppt`,mimeType:`application/vnd.ms-powerpoint`},{ext:`.doc`,mimeType:`application/msword`},{ext:`.ps`,mimeType:`application/postscript`},{ext:`.psd`,mimeType:`image/vnd.adobe.photoshop`},{ext:`.p7s`,mimeType:`application/pkcs7-signature`},{ext:`.ogg`,mimeType:`application/ogg`},{ext:`.oga`,mimeType:`audio/ogg`},{ext:`.ogv`,mimeType:`video/ogg`},{ext:`.png`,mimeType:`image/png`},{ext:`.png`,mimeType:`image/vnd.mozilla.apng`},{ext:`.jpg`,mimeType:`image/jpeg`},{ext:`.jxl`,mimeType:`image/jxl`},{ext:`.jp2`,mimeType:`image/jp2`},{ext:`.jpf`,mimeType:`image/jpx`},{ext:`.jpm`,mimeType:`image/jpm`},{ext:`.jxs`,mimeType:`image/jxs`},{ext:`.gif`,mimeType:`image/gif`},{ext:`.webp`,mimeType:`image/webp`},{ext:`.exe`,mimeType:`application/vnd.microsoft.portable-executable`},{ext:``,mimeType:`application/x-elf`},{ext:``,mimeType:`application/x-object`},{ext:``,mimeType:`application/x-executable`},{ext:`.so`,mimeType:`application/x-sharedlib`},{ext:``,mimeType:`application/x-coredump`},{ext:`.a`,mimeType:`application/x-archive`},{ext:`.deb`,mimeType:`application/vnd.debian.binary-package`},{ext:`.tar`,mimeType:`application/x-tar`},{ext:`.xar`,mimeType:`application/x-xar`},{ext:`.bz2`,mimeType:`application/x-bzip2`},{ext:`.fits`,mimeType:`application/fits`},{ext:`.tiff`,mimeType:`image/tiff`},{ext:`.bmp`,mimeType:`image/bmp`},{ext:`.ico`,mimeType:`image/x-icon`},{ext:`.mp3`,mimeType:`audio/mpeg`},{ext:`.flac`,mimeType:`audio/flac`},{ext:`.midi`,mimeType:`audio/midi`},{ext:`.ape`,mimeType:`audio/ape`},{ext:`.mpc`,mimeType:`audio/musepack`},{ext:`.amr`,mimeType:`audio/amr`},{ext:`.wav`,mimeType:`audio/wav`},{ext:`.aiff`,mimeType:`audio/aiff`},{ext:`.au`,mimeType:`audio/basic`},{ext:`.mpeg`,mimeType:`video/mpeg`},{ext:`.mov`,mimeType:`video/quicktime`},{ext:`.mp4`,mimeType:`video/mp4`},{ext:`.avif`,mimeType:`image/avif`},{ext:`.3gp`,mimeType:`video/3gpp`},{ext:`.3g2`,mimeType:`video/3gpp2`},{ext:`.mp4`,mimeType:`audio/mp4`},{ext:`.mqv`,mimeType:`video/quicktime`},{ext:`.m4a`,mimeType:`audio/x-m4a`},{ext:`.m4v`,mimeType:`video/x-m4v`},{ext:`.heic`,mimeType:`image/heic`},{ext:`.heic`,mimeType:`image/heic-sequence`},{ext:`.heif`,mimeType:`image/heif`},{ext:`.heif`,mimeType:`image/heif-sequence`},{ext:`.mj2`,mimeType:`video/mj2`},{ext:`.dvb`,mimeType:`video/vnd.dvb.file`},{ext:`.webm`,mimeType:`video/webm`},{ext:`.avi`,mimeType:`video/x-msvideo`},{ext:`.flv`,mimeType:`video/x-flv`},{ext:`.mkv`,mimeType:`video/x-matroska`},{ext:`.asf`,mimeType:`video/x-ms-asf`},{ext:`.asf`,mimeType:`video/x-ms-wmv`},{ext:`.asf`,mimeType:`video/asf`},{ext:`.aac`,mimeType:`audio/aac`},{ext:`.voc`,mimeType:`audio/x-unknown`},{ext:`.m3u`,mimeType:`application/vnd.apple.mpegurl`},{ext:`.rmvb`,mimeType:`application/Line truncated
|
||||
`)}o&&(n.classList.add(`error`),r.__errListener=function(){te(r,n),app.utils.deleteByPath(app.store.errors,i)},r.addEventListener(`input`,r.__errListener),r.addEventListener(`change`,r.__errListener),r.setAttribute(`data-error`,!0),r.setCustomValidity&&r.reportValidity&&r.classList.contains(`inline-error`)?(r.setCustomValidity(o),r.reportValidity(),r._oldTitle=r.title,r.title=o):n.after(t.div({"html-data-input-name":i,className:`field-help error generated-error`,textContent:o})))}}),p.onmessage=e=>{e.data?.collections&&JSON.stringify(app.store.collections)!=JSON.stringify(e.data.collections)&&(app.store.collections=e.data.collections),e.data?.settings&&JSON.stringify(app.store.settings)!=JSON.stringify(e.data.settings)&&(app.store.settings=e.data.settings),e.data?.colorScheme&&(app.store.userColorScheme=e.data.colorScheme)},watch(()=>JSON.stringify(app.store.collections),(e,n)=>{e&&e!=`[]`&&n&&n!=`[]`&&e!=n&&p?.postMessage({collections:JSON.parse(e)})}),watch(()=>JSON.stringify(app.store.settings),(e,n)=>{e&&e!=`{}`&&n&&n!=`{}`&&e!=n&&p?.postMessage({settings:JSON.parse(e)}),window.localStorage.setItem(m,e)});var y=class e extends Error{constructor(n){super(`ClientResponseError`),this.url=``,this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,e.prototype),typeof n==`object`&&n&&(this.originalError=n.originalError,this.url=typeof n.url==`string`?n.url:``,this.status=typeof n.status==`number`?n.status:0,this.isAbort=!!n.isAbort||n.name===`AbortError`||n.message===`Aborted`,this.response=n.response!==null&&typeof n.response==`object`?n.response:n.data!==null&&typeof n.data==`object`?n.data:{}),this.originalError||n instanceof e||(this.originalError=n),this.name=`ClientResponseError `+this.status,this.message=this.response?.message,this.message||=this.isAbort?`The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).`:this.originalError?.cause?.message?.includes(`ECONNREFUSED ::1`)?`Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).`:`Something went wrong.`,this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}},ne=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function re(e,n){let r={};if(typeof e!=`string`)return r;let i=Object.assign({},n||{}).decode||ae,a=0;for(;a<e.length;){let n=e.indexOf(`=`,a);if(n===-1)break;let o=e.indexOf(`;`,a);if(o===-1)o=e.length;else if(o<n){a=e.lastIndexOf(`;`,n-1)+1;continue}let s=e.slice(a,n).trim();if(r[s]===void 0){let a=e.slice(n+1,o).trim();a.charCodeAt(0)===34&&(a=a.slice(1,-1));try{r[s]=i(a)}catch{r[s]=a}}a=o+1}return r}function ie(e,n,r){let i=Object.assign({},r||{}),a=i.encode||b;if(!ne.test(e))throw TypeError(`argument name is invalid`);let o=a(n);if(o&&!ne.test(o))throw TypeError(`argument val is invalid`);let s=e+`=`+o;if(i.maxAge!=null){let e=i.maxAge-0;if(isNaN(e)||!isFinite(e))throw TypeError(`option maxAge is invalid`);s+=`; Max-Age=`+Math.floor(e)}if(i.domain){if(!ne.test(i.domain))throw TypeError(`option domain is invalid`);s+=`; Domain=`+i.domain}if(i.path){if(!ne.test(i.path))throw TypeError(`option path is invalid`);s+=`; Path=`+i.path}if(i.expires){if(!function(e){return Object.prototype.toString.call(e)===`[object Date]`||e instanceof Date}(i.expires)||isNaN(i.expires.valueOf()))throw TypeError(`option expires is invalid`);s+=`; Expires=`+i.expires.toUTCString()}if(i.httpOnly&&(s+=`; HttpOnly`),i.secure&&(s+=`; Secure`),i.priority)switch(typeof i.priority==`string`?i.priority.toLowerCase():i.priority){case`low`:s+=`; Priority=Low`;break;case`medium`:s+=`; Priority=Medium`;break;case`high`:s+=`; Priority=High`;break;default:throw TypeError(`option priority is invalid`)}if(i.sameSite)switch(typeof i.sameSite==`string`?i.sameSite.toLowerCase():i.sameSite){case!0:s+=`; SameSite=Strict`;break;case`lax`:s+=`; SameSite=Lax`;break;case`strict`:s+=`; SameSite=Strict`;break;case`none`:s+=`; SameSite=None`;break;default:throw TypeError(`option sameSite is invalid`)}return s}function ae(e){return e.indexOf(`%`)===-1?e:decodeURIComponent(e)}function b(e){return encodeURIComponent(e)}var x=typeof navigator<`u`&&navigator.product===`ReactNative`||typeof global<`u`&&global.HermesInternal,oe;function S(e){if(e)try{let n=decodeURIComponent(oe(e.split(`.`)[1]).split(``).map((function(e){return`%`+(`00`+e.charCodeAt(0).toString(16)).slice(-2)})).join(``));return JSON.parse(n)||{}}catch{}return{}}function se(e,n=0){let r=S(e);return!(Object.keys(r).length>0&&(!r.exp||r.exp-n>Date.now()/1e3))}oe=typeof atob!=`function`||x?e=>{let n=String(e).replace(/=+$/,``);if(n.length%4==1)throw Error(`'atob' failed: The string to be decoded is not correctly encoded.`);for(var r,i,a=0,o=0,s=``;i=n.charAt(o++);~i&&(r=a%4?64*r+i:i,a++%4)&&(s+=String.fromCharCode(255&r>>(-2*a&6))))i=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=`.in
Line truncated
|
||||
`),n+=`)`,e.where&&(n+=` WHERE ${e.where}`),n},replaceIndexFields(e,n){let r=app.utils.parseIndex(e);return typeof n==`function`?Object.assign(r,n(r)||{}):Object.assign(r,n||{}),app.utils.buildIndex(r)},replaceIndexColumn(e,n,r){if(n===r)return e;let i=app.utils.parseIndex(e),a=!1;for(let e of i.columns)e.name===n&&(e.name=r,a=!0);return a?app.utils.buildIndex(i):e}};window.app=window.app||{},window.app.utils=f,window.app=window.app||{},window.app.utils=window.app.utils||{},window.app.utils.mimeTypes=[{ext:``,mimeType:`application/octet-stream`},{ext:`.xpm`,mimeType:`image/x-xpixmap`},{ext:`.7z`,mimeType:`application/x-7z-compressed`},{ext:`.zip`,mimeType:`application/zip`},{ext:`.xlsx`,mimeType:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`},{ext:`.docx`,mimeType:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`},{ext:`.pptx`,mimeType:`application/vnd.openxmlformats-officedocument.presentationml.presentation`},{ext:`.epub`,mimeType:`application/epub+zip`},{ext:`.apk`,mimeType:`application/vnd.android.package-archive`},{ext:`.jar`,mimeType:`application/jar`},{ext:`.odt`,mimeType:`application/vnd.oasis.opendocument.text`},{ext:`.ott`,mimeType:`application/vnd.oasis.opendocument.text-template`},{ext:`.ods`,mimeType:`application/vnd.oasis.opendocument.spreadsheet`},{ext:`.ots`,mimeType:`application/vnd.oasis.opendocument.spreadsheet-template`},{ext:`.odp`,mimeType:`application/vnd.oasis.opendocument.presentation`},{ext:`.otp`,mimeType:`application/vnd.oasis.opendocument.presentation-template`},{ext:`.odg`,mimeType:`application/vnd.oasis.opendocument.graphics`},{ext:`.otg`,mimeType:`application/vnd.oasis.opendocument.graphics-template`},{ext:`.odf`,mimeType:`application/vnd.oasis.opendocument.formula`},{ext:`.odc`,mimeType:`application/vnd.oasis.opendocument.chart`},{ext:`.sxc`,mimeType:`application/vnd.sun.xml.calc`},{ext:`.pdf`,mimeType:`application/pdf`},{ext:`.fdf`,mimeType:`application/vnd.fdf`},{ext:``,mimeType:`application/x-ole-storage`},{ext:`.msi`,mimeType:`application/x-ms-installer`},{ext:`.aaf`,mimeType:`application/octet-stream`},{ext:`.msg`,mimeType:`application/vnd.ms-outlook`},{ext:`.xls`,mimeType:`application/vnd.ms-excel`},{ext:`.pub`,mimeType:`application/vnd.ms-publisher`},{ext:`.ppt`,mimeType:`application/vnd.ms-powerpoint`},{ext:`.doc`,mimeType:`application/msword`},{ext:`.ps`,mimeType:`application/postscript`},{ext:`.psd`,mimeType:`image/vnd.adobe.photoshop`},{ext:`.p7s`,mimeType:`application/pkcs7-signature`},{ext:`.ogg`,mimeType:`application/ogg`},{ext:`.oga`,mimeType:`audio/ogg`},{ext:`.ogv`,mimeType:`video/ogg`},{ext:`.png`,mimeType:`image/png`},{ext:`.png`,mimeType:`image/vnd.mozilla.apng`},{ext:`.jpg`,mimeType:`image/jpeg`},{ext:`.jxl`,mimeType:`image/jxl`},{ext:`.jp2`,mimeType:`image/jp2`},{ext:`.jpf`,mimeType:`image/jpx`},{ext:`.jpm`,mimeType:`image/jpm`},{ext:`.jxs`,mimeType:`image/jxs`},{ext:`.gif`,mimeType:`image/gif`},{ext:`.webp`,mimeType:`image/webp`},{ext:`.exe`,mimeType:`application/vnd.microsoft.portable-executable`},{ext:``,mimeType:`application/x-elf`},{ext:``,mimeType:`application/x-object`},{ext:``,mimeType:`application/x-executable`},{ext:`.so`,mimeType:`application/x-sharedlib`},{ext:``,mimeType:`application/x-coredump`},{ext:`.a`,mimeType:`application/x-archive`},{ext:`.deb`,mimeType:`application/vnd.debian.binary-package`},{ext:`.tar`,mimeType:`application/x-tar`},{ext:`.xar`,mimeType:`application/x-xar`},{ext:`.bz2`,mimeType:`application/x-bzip2`},{ext:`.fits`,mimeType:`application/fits`},{ext:`.tiff`,mimeType:`image/tiff`},{ext:`.bmp`,mimeType:`image/bmp`},{ext:`.ico`,mimeType:`image/x-icon`},{ext:`.mp3`,mimeType:`audio/mpeg`},{ext:`.flac`,mimeType:`audio/flac`},{ext:`.midi`,mimeType:`audio/midi`},{ext:`.ape`,mimeType:`audio/ape`},{ext:`.mpc`,mimeType:`audio/musepack`},{ext:`.amr`,mimeType:`audio/amr`},{ext:`.wav`,mimeType:`audio/wav`},{ext:`.aiff`,mimeType:`audio/aiff`},{ext:`.au`,mimeType:`audio/basic`},{ext:`.mpeg`,mimeType:`video/mpeg`},{ext:`.mov`,mimeType:`video/quicktime`},{ext:`.mp4`,mimeType:`video/mp4`},{ext:`.avif`,mimeType:`image/avif`},{ext:`.3gp`,mimeType:`video/3gpp`},{ext:`.3g2`,mimeType:`video/3gpp2`},{ext:`.mp4`,mimeType:`audio/mp4`},{ext:`.mqv`,mimeType:`video/quicktime`},{ext:`.m4a`,mimeType:`audio/x-m4a`},{ext:`.m4v`,mimeType:`video/x-m4v`},{ext:`.heic`,mimeType:`image/heic`},{ext:`.heic`,mimeType:`image/heic-sequence`},{ext:`.heif`,mimeType:`image/heif`},{ext:`.heif`,mimeType:`image/heif-sequence`},{ext:`.mj2`,mimeType:`video/mj2`},{ext:`.dvb`,mimeType:`video/vnd.dvb.file`},{ext:`.webm`,mimeType:`video/webm`},{ext:`.avi`,mimeType:`video/x-msvideo`},{ext:`.flv`,mimeType:`video/x-flv`},{ext:`.mkv`,mimeType:`video/x-matroska`},{ext:`.asf`,mimeType:`video/x-ms-asf`},{ext:`.asf`,mimeType:`video/x-ms-wmv`},{ext:`.asf`,mimeType:`video/asf`},{ext:`.aac`,mimeType:`audio/aac`},{ext:`.voc`,mimeType:`audio/x-unknown`},{ext:`.m3u`,mimeType:`application/vnd.apple.mpegurl`},{ext:`.rmvb`,mimeType:`application/Line truncated
|
||||
`)}o&&(n.classList.add(`error`),r.__errListener=function(){te(r,n),app.utils.deleteByPath(app.store.errors,i)},r.addEventListener(`input`,r.__errListener),r.addEventListener(`change`,r.__errListener),r.setAttribute(`data-error`,!0),r.setCustomValidity&&r.reportValidity&&r.classList.contains(`inline-error`)?(r.setCustomValidity(o),r.reportValidity(),r._oldTitle=r.title,r.title=o):n.after(t.div({"html-data-input-name":i,className:`field-help error generated-error`,textContent:o})))}}),p.onmessage=e=>{e.data?.collections&&JSON.stringify(app.store.collections)!=JSON.stringify(e.data.collections)&&(app.store.collections=e.data.collections),e.data?.settings&&JSON.stringify(app.store.settings)!=JSON.stringify(e.data.settings)&&(app.store.settings=e.data.settings),e.data?.colorScheme&&(app.store.userColorScheme=e.data.colorScheme)},watch(()=>JSON.stringify(app.store.collections),(e,n)=>{e&&e!=`[]`&&n&&n!=`[]`&&e!=n&&p?.postMessage({collections:JSON.parse(e)})}),watch(()=>JSON.stringify(app.store.settings),(e,n)=>{e&&e!=`{}`&&n&&n!=`{}`&&e!=n&&p?.postMessage({settings:JSON.parse(e)}),window.localStorage.setItem(m,e)});var y=class e extends Error{constructor(n){super(`ClientResponseError`),this.url=``,this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,e.prototype),typeof n==`object`&&n&&(this.originalError=n.originalError,this.url=typeof n.url==`string`?n.url:``,this.status=typeof n.status==`number`?n.status:0,this.isAbort=!!n.isAbort||n.name===`AbortError`||n.message===`Aborted`,this.response=n.response!==null&&typeof n.response==`object`?n.response:n.data!==null&&typeof n.data==`object`?n.data:{}),this.originalError||n instanceof e||(this.originalError=n),this.name=`ClientResponseError `+this.status,this.message=this.response?.message,this.message||=this.isAbort?`The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).`:this.originalError?.cause?.message?.includes(`ECONNREFUSED ::1`)?`Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).`:`Something went wrong.`,this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}},ne=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function re(e,n){let r={};if(typeof e!=`string`)return r;let i=Object.assign({},n||{}).decode||ae,a=0;for(;a<e.length;){let n=e.indexOf(`=`,a);if(n===-1)break;let o=e.indexOf(`;`,a);if(o===-1)o=e.length;else if(o<n){a=e.lastIndexOf(`;`,n-1)+1;continue}let s=e.slice(a,n).trim();if(r[s]===void 0){let a=e.slice(n+1,o).trim();a.charCodeAt(0)===34&&(a=a.slice(1,-1));try{r[s]=i(a)}catch{r[s]=a}}a=o+1}return r}function ie(e,n,r){let i=Object.assign({},r||{}),a=i.encode||b;if(!ne.test(e))throw TypeError(`argument name is invalid`);let o=a(n);if(o&&!ne.test(o))throw TypeError(`argument val is invalid`);let s=e+`=`+o;if(i.maxAge!=null){let e=i.maxAge-0;if(isNaN(e)||!isFinite(e))throw TypeError(`option maxAge is invalid`);s+=`; Max-Age=`+Math.floor(e)}if(i.domain){if(!ne.test(i.domain))throw TypeError(`option domain is invalid`);s+=`; Domain=`+i.domain}if(i.path){if(!ne.test(i.path))throw TypeError(`option path is invalid`);s+=`; Path=`+i.path}if(i.expires){if(!function(e){return Object.prototype.toString.call(e)===`[object Date]`||e instanceof Date}(i.expires)||isNaN(i.expires.valueOf()))throw TypeError(`option expires is invalid`);s+=`; Expires=`+i.expires.toUTCString()}if(i.httpOnly&&(s+=`; HttpOnly`),i.secure&&(s+=`; Secure`),i.priority)switch(typeof i.priority==`string`?i.priority.toLowerCase():i.priority){case`low`:s+=`; Priority=Low`;break;case`medium`:s+=`; Priority=Medium`;break;case`high`:s+=`; Priority=High`;break;default:throw TypeError(`option priority is invalid`)}if(i.sameSite)switch(typeof i.sameSite==`string`?i.sameSite.toLowerCase():i.sameSite){case!0:s+=`; SameSite=Strict`;break;case`lax`:s+=`; SameSite=Lax`;break;case`strict`:s+=`; SameSite=Strict`;break;case`none`:s+=`; SameSite=None`;break;default:throw TypeError(`option sameSite is invalid`)}return s}function ae(e){return e.indexOf(`%`)===-1?e:decodeURIComponent(e)}function b(e){return encodeURIComponent(e)}var x=typeof navigator<`u`&&navigator.product===`ReactNative`||typeof global<`u`&&global.HermesInternal,oe;function S(e){if(e)try{let n=decodeURIComponent(oe(e.split(`.`)[1]).split(``).map((function(e){return`%`+(`00`+e.charCodeAt(0).toString(16)).slice(-2)})).join(``));return JSON.parse(n)||{}}catch{}return{}}function se(e,n=0){let r=S(e);return!(Object.keys(r).length>0&&(!r.exp||r.exp-n>Date.now()/1e3))}oe=typeof atob!=`function`||x?e=>{let n=String(e).replace(/=+$/,``);if(n.length%4==1)throw Error(`'atob' failed: The string to be decoded is not correctly encoded.`);for(var r,i,a=0,o=0,s=``;i=n.charAt(o++);~i&&(r=a%4?64*r+i:i,a++%4)&&(s+=String.fromCharCode(255&r>>(-2*a&6))))i=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=`.in
Line truncated
|
||||
`+ft:null),"html-class":`formatted-date`,className:()=>`formatted-date ${n.short?`short`:`full`}`,onunmount:()=>{r.forEach(e=>e?.unwatch())}},()=>{if(!n.value)return t.span({className:`missing-value`});if(n.short){let e=n.value.split(` `);return[t.span({className:`primary-date`},e[0]),t.span({className:`secondary-date`},e[1])]}return[t.span({className:`primary-date`},app.utils.toLocalDatetime(n.value)),t.span({className:`secondary-date`},n.value)]})},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.refreshButton=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,tooltip:`Refresh`,className:`btn transparent secondary circle rotate-btn`,disabled:!1,onclick:function(e){}}),r=app.utils.extendStore(n,e),i,a=t.button({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,type:`button`,ariaLabel:app.attrs.tooltip(()=>n.tooltip),disabled:()=>n.disabled,className:()=>n.className,onunmount:()=>{clearTimeout(i),r.forEach(e=>e?.unwatch())},onclick:e=>{e.preventDefault(),n.onclick&&n.onclick(e),a.dataset.rotate=!0,a.addEventListener(`animationend`,()=>{a.dataset.rotate=!1}),clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),a.dataset.rotate=!1},500)}},t.i({className:`ri-refresh-line`,ariaHidden:!0}));return a},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.searchHistoryButton=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,value:void 0,historyKey:`default`,max:15,openInNewTabParam:`filter`,btnClassName:`btn sm pill secondary transparent p-r-5`,onselect:function(e){}}),r=app.utils.extendStore(n,e),i=store({items:app.utils.getLocalHistory(n.historyKey,[])});function a(e){o(e),i.items.unshift(e)}function o(e){app.utils.removeByValue(i.items,e)}let s=`history_dropdown_`+app.utils.randomString();r.push(watch(()=>n.value,e=>{e&&a(e)})),r.push(watch(()=>{i.items.length>n.max&&(i.items=i.items.slice(0,n.max)),app.utils.saveLocalHistory(n.historyKey,i.items)}));let c=t.div({id:s,className:`dropdown sm left nowrap history-searchbar-dropdown`,popover:`hint`,onclick:e=>(e.stopPropagation(),!1)},t.div({className:`block p-5`},t.small({className:`txt-hint`},`Search history`)),()=>i.items?.length?i.items.slice(0,n.max).map(e=>t.button({type:`button`,className:`dropdown-item txt-code`,onclick:()=>{c.hidePopover(),n.onselect?.(e),a(e)},onauxclick:()=>{if(n.openInNewTabParam){a(e),c.hidePopover();let r=app.utils.replaceHashQueryParams({[n.openInNewTabParam]:e},!1);window.open(r,`_blank`)}}},t.span({className:`txt-ellipsis`,title:e,textContent:e}),t.small({role:`button`,className:`remove-btn link-hint m-l-auto p-l-5 p-r-5`,title:`Clear`,onauxclick:e=>(e.stopPropagation(),!1),onclick:n=>(n.stopPropagation(),o(e),!1)},t.i({className:`ri-close-line`,ariaHidden:!0})))):t.div({rid:`no-history`,className:`block p-5`},t.span(null,`Your recent searches will show up here.`)));return t.button({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,type:`button`,title:`Search history`,className:()=>n.btnClassName,"html-popovertarget":s,onunmount:()=>{r?.forEach(e=>e?.unwatch())}},t.i({className:`ri-search-line`,ariaHidden:!0}),t.i({className:`ri-arrow-drop-down-line`,ariaHidden:!0}),c)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.s3Test=function(e={}){let n=`s3_test_request`,r=store({rid:void 0,config:null,label:`Use S3 storage`,testFilesystem:`storage`}),i=app.utils.extendStore(r,e),a=store({isTesting:!1,testError:null,get hasError(){return!app.utils.isEmpty(a.testError)}}),o,s;function c(e=150){if(!r.config.enabled){clearTimeout(o);return}a.isTesting=!0,clearTimeout(o),o=setTimeout(()=>{l()},e)}async function l(){if(a.isTesting=!0,!r.config.enabled||!r.testFilesystem){a.testError=null,a.isTesting=!1;return}app.pb.cancelRequest(n),clearTimeout(s),s=setTimeout(()=>{app.pb.cancelRequest(n),a.testError=Error(`S3 test connection timeout.`),a.isTesting=!1},3e4);try{await app.pb.props.testS3(r.testFilesystem,{requestKey:n}),a.testError=null,a.isTesting=!1}catch(e){e?.isAbort||(a.testError=e,a.isTesting=!1,clearTimeout(s))}}return i.push(watch(()=>r.testFilesystem&&r.config,()=>c())),t.div({pbEvent:`s3Test`,rid:r.rid,hidden:()=>!r.testFilesystem,className:()=>`label s3-test-label txt-nowrap ${a.hasError?`warning`:`success`}`,ariaDescription:app.attrs.tooltip(()=>a.testError?.data?.message),onunmount:()=>{clearTimeout(s),clearTimeout(o),i.forEach(e=>e?.unwatch())}},()=>a.isTesting?t.span({className:`loader sm`}):a.hasError?[t.i({className:`ri-error-warning-line txt-warning`,ariaHidden:!0}),t.span({className:`txt`},`Failed to establish S3 connection`)]:[t.i({className:`ri-checkbox-circle-line txt-success`,ariaHidden:!0}),t.span({className:`txt`},`S3 connected successfully`)])},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.s3ConfigFields=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,in
Line truncated
|
||||
<video controls width="300">
|
||||
<source src="${a}" />
|
||||
@@ -83,4 +83,4 @@ Records update will also require an extra unlock step before save.`)})))),t.div(
|
||||
@weekly
|
||||
@daily
|
||||
@midnight
|
||||
@hourly`)},`macros`),`.`,t.br(),`By default the timezone is in UTC.`)),t.div({className:`col-lg-6`},t.div({className:`field`},t.label({htmlFor:`backups.cronMaxKeep`},`Max @auto backups to keep`),t.input({id:`backups.cronMaxKeep`,name:`backups.cronMaxKeep`,type:`number`,required:()=>a.enableAutoBackups,min:1,value:()=>a.formSettings.backups.cronMaxKeep,oninput:e=>{a.formSettings.backups.cronMaxKeep=parseInt(e.target.value,10)}})))))),t.div({className:`col-lg-12`},app.components.s3ConfigFields({toggleLabel:`Store backups in S3 storage`,testFilesystem:`backups`,config:()=>a.formSettings.backups.s3})),t.div({className:`col-lg-12`},t.hr()),t.div({className:`col-lg-12`},t.div({className:`flex`},t.div({className:`m-r-auto`}),t.button({hidden:()=>!a.hasChanges,type:`button`,className:`btn transparent secondary`,onclick:l},t.span({className:`txt`},`Cancel`)),t.button({className:()=>`btn expanded-lg ${a.isSaving?`loading`:``}`,disabled:()=>!a.hasChanges||a.isSaving},t.span({className:`txt`},`Save changes`))))])))}function zi(e={oncreated:null}){let n=Bi(e);n&&(document.body.appendChild(n),app.modals.open(n))}function Bi(e){let n,r=`backup_create_`+app.utils.randomString(),i=store({name:``,isSubmitting:!1}),a;async function o(){if(!i.isSubmitting){i.isSubmitting=!0,clearTimeout(a),a=setTimeout(()=>{app.modals.close(n)},1500);try{await app.pb.backups.create(i.name,{requestKey:r}),i.isSubmitting=!1,e.oncreated&&e.oncreated(i.name),app.toasts.success(`Successfully generated new backup.`),app.modals.close(n)}catch(e){e.isAbort||(clearTimeout(a),i.isSubmitting=!1,app.checkApiError(e))}}}return n=t.div({pbEvent:`backupCreateModal`,className:`modal popup backup-create-modal`,onbeforeclose:()=>{i.isSubmitting&&app.toasts.info(`The backup was started but may take a while to complete. You can come back later.`)},onafterclose:e=>{clearTimeout(a),e?.remove()}},t.header({className:`modal-header`},t.h5({className:`m-auto txt-center`},`Initialize new backup`)),t.form({id:r,className:`modal-content backup-restore-form`,autocomplete:`off`,onsubmit:e=>{e.preventDefault(),o()}},t.div({className:`grid`},t.div({className:`col-lg-12`},t.div({className:`alert warning`},t.div({className:`content`},t.p(null,`Please note that during the backup the performance could be slightly degraded and some queries may take longer to complete than usual.`),t.p({className:`txt-bold`},`If you are using S3 storage for the collections file upload, you'll have to backup them separately since they are not locally stored and they will not be included in the generated backup!`)))),t.div({className:`col-lg-12`},t.div({className:`field`},t.label({htmlFor:r+`_name`},`Backup name`),t.input({id:r+`_name`,name:`name`,type:`text`,pattern:`^[a-z0-9_-]+.zip$`,placeholder:`Leave empty to autogenerate`,value:()=>i.name,oninput:e=>i.name=e.target.value})),t.div({className:`field-help`},`Must be in the format [a-z0-9_-].zip`)))),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,disabled:()=>i.isSubmitting,onclick:()=>app.modals.close(n)},t.span({className:`txt`},`Cancel`)),t.button({"html-form":r,type:`submit`,className:()=>`btn ${i.isSubmitting?`loading`:``}`,disabled:()=>i.isSubmitting},t.span({className:`txt`},`Start backup`)))),n}function Vi(e){let n=Hi(e);document.body.appendChild(n),app.modals.open(n)}function Hi(e){let n=`backup_restore_`+app.utils.randomString(),r=store({key:e,keyConfirm:``,isSubmitting:!1,get canSubmit(){return r.key&&r.key==r.keyConfirm}}),i;async function a(){if(!(r.isSubmitting||!r.canSubmit)){clearTimeout(i),r.isSubmitting=!0;try{await app.pb.backups.restore(r.keyConfirm),i=setTimeout(()=>{window.location.reload(),r.isSubmitting=!1},2e3)}catch(e){clearTimeout(i),e?.isAbort||(r.isSubmitting=!1,app.checkApiError(e))}}}return t.div({pbEvent:`backupRestoreModal`,className:`modal popup backup-restore-modal`,onbeforeclose:()=>!r.isSubmitting,onafterclose:e=>{e?.remove()},onunmount:()=>{clearTimeout(i)}},t.header({className:`modal-header`},t.h5({className:`m-auto txt-center`},`Restore `,t.strong(null,()=>r.key))),t.form({id:n,className:`modal-content backup-restore-form`,autocomplete:`off`,onsubmit:e=>{e.preventDefault(),a()}},t.div({className:`grid`},t.div({className:`col-lg-12`},t.div({className:`alert danger`},t.div({className:`content`},t.p({className:`txt-bold`},`Please proceed with extreme caution and use it only with trusted backups!`),t.p(null,`Backup restore currently works only on UNIX based systems.`),t.p(null,`The restore operation will attempt to replace your existing `,t.code(null,`pb_data`),` with the one from the backup and will restart the application process.`),t.p(null,`This means that on success all of your data (including app settings, users, superusers, etc.) will be replaced with the ones from the backup.`),t.p(null,`The operation will be reverted if the backup is invalid (ex. missing `,t.code(null,`data.db`),` file).`),t.p(null,`Below is an oversimplified version of the restore flow:Line truncated
|
||||
@hourly`)},`macros`),`.`,t.br(),`By default the timezone is in UTC.`)),t.div({className:`col-lg-6`},t.div({className:`field`},t.label({htmlFor:`backups.cronMaxKeep`},`Max @auto backups to keep`),t.input({id:`backups.cronMaxKeep`,name:`backups.cronMaxKeep`,type:`number`,required:()=>a.enableAutoBackups,min:1,value:()=>a.formSettings.backups.cronMaxKeep,oninput:e=>{a.formSettings.backups.cronMaxKeep=parseInt(e.target.value,10)}})))))),t.div({className:`col-lg-12`},app.components.s3ConfigFields({toggleLabel:`Store backups in S3 storage`,testFilesystem:`backups`,config:()=>a.formSettings.backups.s3})),t.div({className:`col-lg-12`},t.hr()),t.div({className:`col-lg-12`},t.div({className:`flex`},t.div({className:`m-r-auto`}),t.button({hidden:()=>!a.hasChanges,type:`button`,className:`btn transparent secondary`,onclick:l},t.span({className:`txt`},`Cancel`)),t.button({className:()=>`btn expanded-lg ${a.isSaving?`loading`:``}`,disabled:()=>!a.hasChanges||a.isSaving},t.span({className:`txt`},`Save changes`))))])))}function zi(e={oncreated:null}){let n=Bi(e);n&&(document.body.appendChild(n),app.modals.open(n))}function Bi(e){let n,r=`backup_create_`+app.utils.randomString(),i=store({name:``,isSubmitting:!1}),a;async function o(){if(!i.isSubmitting){i.isSubmitting=!0,clearTimeout(a),a=setTimeout(()=>{app.modals.close(n)},1500);try{await app.pb.backups.create(i.name,{requestKey:r}),i.isSubmitting=!1,e.oncreated&&e.oncreated(i.name),app.toasts.success(`Successfully generated new backup.`),app.modals.close(n)}catch(e){e.isAbort||(clearTimeout(a),i.isSubmitting=!1,app.checkApiError(e))}}}return n=t.div({pbEvent:`backupCreateModal`,className:`modal popup backup-create-modal`,onbeforeclose:()=>{i.isSubmitting&&app.toasts.info(`The backup was started but may take a while to complete. You can come back later.`)},onafterclose:e=>{clearTimeout(a),e?.remove()}},t.header({className:`modal-header`},t.h5({className:`m-auto txt-center`},`Initialize new backup`)),t.form({id:r,className:`modal-content backup-restore-form`,autocomplete:`off`,onsubmit:e=>{e.preventDefault(),o()}},t.div({className:`grid`},t.div({className:`col-lg-12`},t.div({className:`alert warning`},t.div({className:`content`},t.p(null,`Please note that during the backup the performance could be slightly degraded and some queries may take longer to complete than usual.`),t.p({className:`txt-bold`},`If you are using S3 storage for the collections file upload, you'll have to backup them separately since they are not locally stored and they will not be included in the generated backup!`)))),t.div({className:`col-lg-12`},t.div({className:`field`},t.label({htmlFor:r+`_name`},`Backup name`),t.input({id:r+`_name`,name:`name`,type:`text`,pattern:`^[a-z0-9_-]+.zip$`,placeholder:`Leave empty to autogenerate`,value:()=>i.name,oninput:e=>i.name=e.target.value})),t.div({className:`field-help`},`Must be in the format [a-z0-9_-].zip`)))),t.footer({className:`modal-footer`},t.button({type:`button`,className:`btn transparent m-r-auto`,disabled:()=>i.isSubmitting,onclick:()=>app.modals.close(n)},t.span({className:`txt`},`Cancel`)),t.button({"html-form":r,type:`submit`,className:()=>`btn ${i.isSubmitting?`loading`:``}`,disabled:()=>i.isSubmitting},t.span({className:`txt`},`Start backup`)))),n}function Vi(e){let n=Hi(e);document.body.appendChild(n),app.modals.open(n)}function Hi(e){let n=`backup_restore_`+app.utils.randomString(),r=store({key:e,keyConfirm:``,isSubmitting:!1,get canSubmit(){return r.key&&r.key==r.keyConfirm}}),i;async function a(){if(!(r.isSubmitting||!r.canSubmit)){clearTimeout(i),r.isSubmitting=!0;try{await app.pb.backups.restore(r.keyConfirm),i=setTimeout(()=>{window.location.reload(),r.isSubmitting=!1},2e3)}catch(e){clearTimeout(i),e?.isAbort||(r.isSubmitting=!1,app.checkApiError(e))}}}return t.div({pbEvent:`backupRestoreModal`,className:`modal popup backup-restore-modal`,onbeforeclose:()=>!r.isSubmitting,onafterclose:e=>{e?.remove()},onunmount:()=>{clearTimeout(i)}},t.header({className:`modal-header`},t.h5({className:`m-auto txt-center`},`Restore `,t.strong(null,()=>r.key))),t.form({id:n,className:`modal-content backup-restore-form`,autocomplete:`off`,onsubmit:e=>{e.preventDefault(),a()}},t.div({className:`grid`},t.div({className:`col-lg-12`},t.div({className:`alert danger`},t.div({className:`content`},t.p({className:`txt-bold`},`Please proceed with extreme caution and use it only with trusted backups!`),t.p(null,`Backup restore currently works only on UNIX based systems.`),t.p(null,`The restore operation will attempt to replace your existing `,t.code(null,`pb_data`),` with the one from the backup and will restart the application process.`),t.p(null,`This means that on success all of your data (including app settings, users, superusers, etc.) will be replaced with the ones from the backup.`),t.p(null,`The operation will be reverted if the backup is invalid (ex. missing `,t.code(null,`data.db`),` file).`),t.p(null,`Below is an oversimplified version of the restore flow:Line truncated
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-VdywtJWb.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.newEmail||!a.collectionId){app.toasts.error(`Invalid or expired email change token.`),window.location.hash=`#/`;return}app.store.title=`Confirm email change`;let o=store({password:``,isSubmitting:!1,isSuccess:!1,showPassword:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmEmailChange(i,o.password),o.isSuccess=!0}catch(e){app.checkApiError(e),o.isSuccess=!1}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmEmailChange`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmEmailChangeAlert`,className:`alert success txt-center`},t.p(null,`The email was successfully changed.`),t.p(null,`You can go back and sign in with your new email address.`)):t.form({pbEvent:`confirmEmailChangeForm`,className:`grid confirm-email-change-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your password to confirm changing your email address to `,t.strong(null,a.newEmail),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`password_confirm`},`Password`),t.input({id:`password_confirm`,name:`password`,required:!0,autofocus:!0,type:()=>o.showPassword?`text`:`password`,value:()=>o.password,oninput:e=>o.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showPassword?`Hide password`:`Show password`),onclick:()=>o.showPassword=!o.showPassword},t.i({className:()=>o.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Confirm new email`)))))}export{r as pageConfirmEmailChange};
|
||||
import{n as e,t as n}from"./index-DP_1cuxz.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.newEmail||!a.collectionId){app.toasts.error(`Invalid or expired email change token.`),window.location.hash=`#/`;return}app.store.title=`Confirm email change`;let o=store({password:``,isSubmitting:!1,isSuccess:!1,showPassword:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmEmailChange(i,o.password),o.isSuccess=!0}catch(e){app.checkApiError(e),o.isSuccess=!1}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmEmailChange`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmEmailChangeAlert`,className:`alert success txt-center`},t.p(null,`The email was successfully changed.`),t.p(null,`You can go back and sign in with your new email address.`)):t.form({pbEvent:`confirmEmailChangeForm`,className:`grid confirm-email-change-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your password to confirm changing your email address to `,t.strong(null,a.newEmail),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`password_confirm`},`Password`),t.input({id:`password_confirm`,name:`password`,required:!0,autofocus:!0,type:()=>o.showPassword?`text`:`password`,value:()=>o.password,oninput:e=>o.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showPassword?`Hide password`:`Show password`),onclick:()=>o.showPassword=!o.showPassword},t.i({className:()=>o.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Confirm new email`)))))}export{r as pageConfirmEmailChange};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-VdywtJWb.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired password reset token.`),window.location.hash=`#/`;return}app.store.title=`Confirm password reset`;let o=store({newPassword:``,newPasswordConfirm:``,showNewPassword:!1,showNewPasswordConfirm:!1,isSubmitting:!1,isSuccess:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmPasswordReset(i,o.newPassword,o.newPasswordConfirm),o.isSuccess=!0}catch(e){app.checkApiError(e)}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmPasswordReset`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmPasswordResetAlert`,className:`alert success txt-center`},t.p(null,`The password was successfully changed.`),t.p(null,`You can go back to sign in with your new password.`)):t.form({pbEvent:`confirmPasswordResetForm`,className:`grid confirm-password-reset-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your new password for `,t.strong(null,a.email),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPassword`},`New password`),t.input({id:`newPassword`,name:`password`,required:!0,autofocus:!0,autocomplete:`new-password`,type:()=>o.showNewPassword?`text`:`password`,value:()=>o.newPassword,oninput:e=>o.newPassword=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPassword?`Hide password`:`Show password`),onclick:()=>o.showNewPassword=!o.showNewPassword},t.i({className:()=>o.showNewPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPasswordConfirm`},`New password confirm`),t.input({id:`newPasswordConfirm`,name:`passwordConfirm`,required:!0,autocomplete:`new-password`,type:()=>o.showNewPasswordConfirm?`text`:`password`,value:()=>o.newPasswordConfirm,oninput:e=>o.newPasswordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPasswordConfirm?`Hide password`:`Show password`),onclick:()=>o.showNewPasswordConfirm=!o.showNewPasswordConfirm},t.i({className:()=>o.showNewPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Set new password`)))))}export{r as pageConfirmPasswordReset};
|
||||
import{n as e,t as n}from"./index-DP_1cuxz.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired password reset token.`),window.location.hash=`#/`;return}app.store.title=`Confirm password reset`;let o=store({newPassword:``,newPasswordConfirm:``,showNewPassword:!1,showNewPasswordConfirm:!1,isSubmitting:!1,isSuccess:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmPasswordReset(i,o.newPassword,o.newPasswordConfirm),o.isSuccess=!0}catch(e){app.checkApiError(e)}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmPasswordReset`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmPasswordResetAlert`,className:`alert success txt-center`},t.p(null,`The password was successfully changed.`),t.p(null,`You can go back to sign in with your new password.`)):t.form({pbEvent:`confirmPasswordResetForm`,className:`grid confirm-password-reset-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your new password for `,t.strong(null,a.email),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPassword`},`New password`),t.input({id:`newPassword`,name:`password`,required:!0,autofocus:!0,autocomplete:`new-password`,type:()=>o.showNewPassword?`text`:`password`,value:()=>o.newPassword,oninput:e=>o.newPassword=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPassword?`Hide password`:`Show password`),onclick:()=>o.showNewPassword=!o.showNewPassword},t.i({className:()=>o.showNewPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPasswordConfirm`},`New password confirm`),t.input({id:`newPasswordConfirm`,name:`passwordConfirm`,required:!0,autocomplete:`new-password`,type:()=>o.showNewPasswordConfirm?`text`:`password`,value:()=>o.newPasswordConfirm,oninput:e=>o.newPasswordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPasswordConfirm?`Hide password`:`Show password`),onclick:()=>o.showNewPasswordConfirm=!o.showNewPasswordConfirm},t.i({className:()=>o.showNewPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Set new password`)))))}export{r as pageConfirmPasswordReset};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-VdywtJWb.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired verification token.`),window.location.hash=`#/`;return}app.store.title=`Confirm verification`;let o=store({isConfirming:!1,isConfirmSuccess:!1,isResending:!1,isResendSuccess:!1});s();async function s(){if(o.isConfirming)return;o.isConfirming=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmVerification(i),o.isConfirmSuccess=!0}catch{o.isConfirmSuccess=!1}o.isConfirming=!1}async function c(){if(o.isResending)return;o.isResending=!0;let e=new n(`../`);try{await e.collection(a.collectionId).requestVerification(a.email),o.isResendSuccess=!0}catch(e){app.checkApiError(e),o.isResendSuccess=!1}o.isResending=!1}return t.div({pbEvent:`pageConfirmVerification`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isConfirming?t.div({className:`block txt-center`},t.span({className:`loader`},`Please wait...`)):o.isConfirmSuccess?t.div({pbEvent:`confirmVerificationSuccessAlert`,className:`alert success txt-center`},t.p(null,`Successfully verified `,t.strong(null,a.email),`.`)):o.isResendSuccess?t.div({pbEvent:`confirmVerificationResendAlert`,className:`alert success txt-center`},t.p(null,`Please check your email for the new verification link.`)):[t.div({pbEvent:`confirmVerificationErrorAlert`,className:`alert danger txt-center m-b-base`},t.p(null,`Invalid or expired verification token.`)),t.button({type:`button`,className:()=>`btn transparent lg block ${o.isResending?`loading`:``}`,disabled:()=>o.isResending,onclick:()=>c()},t.span({className:`txt`},`Resend`))])}export{r as pageConfirmVerification};
|
||||
import{n as e,t as n}from"./index-DP_1cuxz.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired verification token.`),window.location.hash=`#/`;return}app.store.title=`Confirm verification`;let o=store({isConfirming:!1,isConfirmSuccess:!1,isResending:!1,isResendSuccess:!1});s();async function s(){if(o.isConfirming)return;o.isConfirming=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmVerification(i),o.isConfirmSuccess=!0}catch{o.isConfirmSuccess=!1}o.isConfirming=!1}async function c(){if(o.isResending)return;o.isResending=!0;let e=new n(`../`);try{await e.collection(a.collectionId).requestVerification(a.email),o.isResendSuccess=!0}catch(e){app.checkApiError(e),o.isResendSuccess=!1}o.isResending=!1}return t.div({pbEvent:`pageConfirmVerification`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isConfirming?t.div({className:`block txt-center`},t.span({className:`loader`},`Please wait...`)):o.isConfirmSuccess?t.div({pbEvent:`confirmVerificationSuccessAlert`,className:`alert success txt-center`},t.p(null,`Successfully verified `,t.strong(null,a.email),`.`)):o.isResendSuccess?t.div({pbEvent:`confirmVerificationResendAlert`,className:`alert success txt-center`},t.p(null,`Please check your email for the new verification link.`)):[t.div({pbEvent:`confirmVerificationErrorAlert`,className:`alert danger txt-center m-b-base`},t.p(null,`Invalid or expired verification token.`)),t.button({type:`button`,className:()=>`btn transparent lg block ${o.isResending?`loading`:``}`,disabled:()=>o.isResending,onclick:()=>c()},t.span({className:`txt`},`Resend`))])}export{r as pageConfirmVerification};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,r as n}from"./index-VdywtJWb.js";function r(r){let i=r.params?.token||``;if(e(i).type!=`auth`||n(i)){app.toasts.error(`The installer token is invalid or has expired.`),window.location.hash=`#/`;return}app.store.title=`Setup your PocketBase instance`;let a=store({email:``,password:``,passwordConfirm:``,showPassword:!1,showPasswordConfirm:!1,isSubmitting:!1,isUploading:!1,get isBusy(){return a.isSubmitting||a.isUploading}});async function o(){if(!a.isBusy){a.isSubmitting=!0;try{await app.pb.collection(`_superusers`).create({email:a.email,password:a.password,passwordConfirm:a.passwordConfirm},{headers:{Authorization:i}}),await app.pb.collection(`_superusers`).authWithPassword(a.email,a.password),window.location.hash=`#/`}catch(e){app.checkApiError(e)}a.isSubmitting=!1}}let s=`backupFileInput`;function c(){let e=document.getElementById(s);e&&(e.value=``)}function l(e){e&&app.modals.confirm(t.h6(null,`Note that we don't perform validations for the uploaded backup files. Proceed with caution and only if you trust the file source.\n\nDo you really want to upload and initialize "${e.name}"?`),()=>{u(e)},()=>{c()})}async function u(e){if(!(!e||a.isBusy)){a.isUploading=!0;try{await app.pb.backups.upload({file:e},{headers:{Authorization:i}}),await app.pb.backups.restore(e.name,{headers:{Authorization:i}}),app.toasts.info(`Please wait while extracting the uploaded archive!`),await new Promise(e=>setTimeout(e,3e3)),window.location.href=`#/`}catch(e){app.checkApiError(e)}c(),a.isUploading=!1}}return t.div({pbEvent:`pageInstaller`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),t.form({pbEvent:`installerForm`,className:`grid installer-form`,onsubmit:e=>{e.preventDefault(),o(a)}},t.div({className:`col-12 txt-center`},`Create your first superuser account in order to continue:`),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:`superuser_email`},`Email`),t.input({id:`superuser_email`,name:`email`,type:`email`,required:!0,autofocus:!0,autocomplete:`off`,disabled:()=>a.isBusy,value:()=>a.email,oninput:e=>a.email=e.target.value}))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password`},`Password`),t.input({id:`superuser_password`,name:`password`,min:10,required:!0,disabled:()=>a.isBusy,type:()=>a.showPassword?`text`:`password`,value:()=>a.password,oninput:e=>a.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPassword?`Hide password`:`Show password`),onclick:()=>a.showPassword=!a.showPassword},t.i({className:()=>a.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0})))),t.div({className:`field-help`},`Recommended at least 10 characters.`)),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password_confirm`},`Password confirm`),t.input({id:`superuser_password_confirm`,name:`passwordConfirm`,required:!0,disabled:()=>a.isBusy,type:()=>a.showPasswordConfirm?`text`:`password`,value:()=>a.passwordConfirm,oninput:e=>a.passwordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPasswordConfirm?`Hide password`:`Show password`),onclick:()=>a.showPasswordConfirm=!a.showPasswordConfirm},t.i({className:()=>a.showPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg next block ${a.isSubmitting?`loading`:``}`,disabled:()=>a.isBusy},t.span({className:`txt`},`Create superuser and login`),t.i({className:`ri-arrow-right-line`,ariaHidden:!0})))),t.hr(),t.label({htmlFor:s,className:()=>`btn secondary transparent lg block ${a.isBusy?`disabled`:``} ${a.isUploading?`loading`:``}`},t.i({className:`ri-upload-cloud-line`,ariaHidden:!0}),t.span({className:`txt`},`Or initialize from backup`)),t.input({id:s,type:`file`,className:`hidden`,accept:`.zip`,onchange:e=>{l(e.target?.files?.[0])}}))}export{r as pageInstaller};
|
||||
import{n as e,r as n}from"./index-DP_1cuxz.js";function r(r){let i=r.params?.token||``;if(e(i).type!=`auth`||n(i)){app.toasts.error(`The installer token is invalid or has expired.`),window.location.hash=`#/`;return}app.store.title=`Setup your PocketBase instance`;let a=store({email:``,password:``,passwordConfirm:``,showPassword:!1,showPasswordConfirm:!1,isSubmitting:!1,isUploading:!1,get isBusy(){return a.isSubmitting||a.isUploading}});async function o(){if(!a.isBusy){a.isSubmitting=!0;try{await app.pb.collection(`_superusers`).create({email:a.email,password:a.password,passwordConfirm:a.passwordConfirm},{headers:{Authorization:i}}),await app.pb.collection(`_superusers`).authWithPassword(a.email,a.password),window.location.hash=`#/`}catch(e){app.checkApiError(e)}a.isSubmitting=!1}}let s=`backupFileInput`;function c(){let e=document.getElementById(s);e&&(e.value=``)}function l(e){e&&app.modals.confirm(t.h6(null,`Note that we don't perform validations for the uploaded backup files. Proceed with caution and only if you trust the file source.\n\nDo you really want to upload and initialize "${e.name}"?`),()=>{u(e)},()=>{c()})}async function u(e){if(!(!e||a.isBusy)){a.isUploading=!0;try{await app.pb.backups.upload({file:e},{headers:{Authorization:i}}),await app.pb.backups.restore(e.name,{headers:{Authorization:i}}),app.toasts.info(`Please wait while extracting the uploaded archive!`),await new Promise(e=>setTimeout(e,3e3)),window.location.href=`#/`}catch(e){app.checkApiError(e)}c(),a.isUploading=!1}}return t.div({pbEvent:`pageInstaller`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),t.form({pbEvent:`installerForm`,className:`grid installer-form`,onsubmit:e=>{e.preventDefault(),o(a)}},t.div({className:`col-12 txt-center`},`Create your first superuser account in order to continue:`),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:`superuser_email`},`Email`),t.input({id:`superuser_email`,name:`email`,type:`email`,required:!0,autofocus:!0,autocomplete:`off`,disabled:()=>a.isBusy,value:()=>a.email,oninput:e=>a.email=e.target.value}))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password`},`Password`),t.input({id:`superuser_password`,name:`password`,min:10,required:!0,disabled:()=>a.isBusy,type:()=>a.showPassword?`text`:`password`,value:()=>a.password,oninput:e=>a.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPassword?`Hide password`:`Show password`),onclick:()=>a.showPassword=!a.showPassword},t.i({className:()=>a.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0})))),t.div({className:`field-help`},`Recommended at least 10 characters.`)),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password_confirm`},`Password confirm`),t.input({id:`superuser_password_confirm`,name:`passwordConfirm`,required:!0,disabled:()=>a.isBusy,type:()=>a.showPasswordConfirm?`text`:`password`,value:()=>a.passwordConfirm,oninput:e=>a.passwordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPasswordConfirm?`Hide password`:`Show password`),onclick:()=>a.showPasswordConfirm=!a.showPasswordConfirm},t.i({className:()=>a.showPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg next block ${a.isSubmitting?`loading`:``}`,disabled:()=>a.isBusy},t.span({className:`txt`},`Create superuser and login`),t.i({className:`ri-arrow-right-line`,ariaHidden:!0})))),t.hr(),t.label({htmlFor:s,className:()=>`btn secondary transparent lg block ${a.isBusy?`disabled`:``} ${a.isUploading?`loading`:``}`},t.i({className:`ri-upload-cloud-line`,ariaHidden:!0}),t.span({className:`txt`},`Or initialize from backup`)),t.input({id:s,type:`file`,className:`hidden`,accept:`.zip`,onchange:e=>{l(e.target?.files?.[0])}}))}export{r as pageInstaller};
|
||||
Vendored
+2
-2
@@ -13,8 +13,8 @@
|
||||
|
||||
<!-- prism -->
|
||||
<script src="./libs/prism/prism.js" data-manual></script>
|
||||
<script type="module" crossorigin src="./assets/index-VdywtJWb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-kt0ysE3i.css">
|
||||
<script type="module" crossorigin src="./assets/index-DP_1cuxz.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-BxIaELVO.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
Generated
+4
-4
@@ -7,7 +7,7 @@
|
||||
"name": "PocketBase Superuser UI",
|
||||
"dependencies": {
|
||||
"leaflet": "^1.9.4",
|
||||
"pocketbase": "^0.28.0"
|
||||
"pocketbase": "^0.28.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dprint": "^0.52.0",
|
||||
@@ -865,9 +865,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pocketbase": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.28.0.tgz",
|
||||
"integrity": "sha512-aCrab5XPPQ40iKIKicQLdUvrAvJQCHZ3cwZD5i22A8PVM/EcemUta7WFw0x4G6WQEOBMa/8kioqCR62ozk4KvQ==",
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.28.1.tgz",
|
||||
"integrity": "sha512-/3ihkq+rvfcs0MQgrK4sEElOg6gfenHW9S/O+3BSO+V1yT+4B10+9aT22uTQUPqze9ItwNtwWyY5ZmdgVCTdVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"leaflet": "^1.9.4",
|
||||
"pocketbase": "^0.28.0"
|
||||
"pocketbase": "^0.28.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dprint": "^0.52.0",
|
||||
|
||||
+34
-26
@@ -145,7 +145,7 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
autocomplete: "off",
|
||||
autocapitalize: "off",
|
||||
role: "textbox",
|
||||
className: "editor-content",
|
||||
className: "code-editor-content",
|
||||
"html-data-placeholder": () => props.placeholder,
|
||||
onmount: (el) => {
|
||||
// auto change change textContent only if it props.value was
|
||||
@@ -187,7 +187,7 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
autocompleteTimeoutId = setTimeout(() => {
|
||||
autocompleteTimeoutId = setTimeout(async () => {
|
||||
closeAutocompleteDropdown();
|
||||
|
||||
if (!editorContent?.isConnected) {
|
||||
@@ -209,7 +209,7 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
|
||||
let suggestions = [];
|
||||
if (typeof props.autocomplete == "function") {
|
||||
suggestions = props.autocomplete(match.prefix || match.word) || [];
|
||||
suggestions = (await props.autocomplete(match.prefix || match.word)) || [];
|
||||
} else if (!app.utils.isEmpty(props.autocomplete)) {
|
||||
const wordLowercased = (match.prefix || match.word).toLowerCase();
|
||||
suggestions = props.autocomplete.filter((item) => {
|
||||
@@ -236,36 +236,44 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
|
||||
openAutocompleteDropdown(() => {
|
||||
return suggestions.map((suggestion, i) => {
|
||||
return t.button({
|
||||
type: "button",
|
||||
className: `dropdown-item ${i == 0 ? "active" : ""}`,
|
||||
textContent: suggestion.label || suggestion.value || suggestion,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
return t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: `dropdown-item ${i == 0 ? "active" : ""}`,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
editorContent.focus();
|
||||
editorContent.focus();
|
||||
|
||||
const word = suggestion.value || suggestion;
|
||||
const word = suggestion.value || suggestion;
|
||||
|
||||
// note: replacing the text doesn't preserve the native "undo" history
|
||||
// (document.execCommand is being deprecated)
|
||||
editorContent.textContent = editorContent.textContent.substring(0, match.start)
|
||||
+ word
|
||||
+ editorContent.textContent.substring(match.end + 1);
|
||||
// note: replacing the text doesn't preserve the native "undo" history
|
||||
// (document.execCommand is being deprecated)
|
||||
editorContent.textContent = editorContent.textContent.substring(0, match.start)
|
||||
+ word
|
||||
+ editorContent.textContent.substring(match.end + 1);
|
||||
|
||||
updateValue(editorContent.textContent);
|
||||
updateValue(editorContent.textContent);
|
||||
|
||||
try {
|
||||
window
|
||||
.getSelection()
|
||||
.setPosition(editorContent.childNodes[0], match.start + word.length);
|
||||
} catch (err) {
|
||||
console.warn("failed to set caret position", err);
|
||||
try {
|
||||
window
|
||||
.getSelection()
|
||||
.setPosition(editorContent.childNodes[0], match.start + word.length);
|
||||
} catch (err) {
|
||||
console.warn("failed to set caret position", err);
|
||||
}
|
||||
|
||||
closeAutocompleteDropdown();
|
||||
},
|
||||
},
|
||||
(el) => {
|
||||
if (typeof suggestion?.label == "function") {
|
||||
return suggestion.label(el);
|
||||
}
|
||||
|
||||
closeAutocompleteDropdown();
|
||||
return suggestion?.label || suggestion?.value || suggestion;
|
||||
},
|
||||
});
|
||||
);
|
||||
});
|
||||
});
|
||||
}, 50);
|
||||
@@ -420,7 +428,7 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
);
|
||||
};
|
||||
|
||||
const highlightThreshold = 800;
|
||||
const highlightThreshold = 850;
|
||||
|
||||
function highlight(content, language) {
|
||||
content = typeof content == "string" ? content : "";
|
||||
|
||||
+3
-3
@@ -1091,7 +1091,7 @@ button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.editor-content,
|
||||
.code-editor-content,
|
||||
.highlight-overlay {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -1119,7 +1119,7 @@ button {
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.editor-content {
|
||||
.code-editor-content {
|
||||
color: transparent;
|
||||
resize: none;
|
||||
caret-color: var(--surfaceTxtHintColor);
|
||||
@@ -1137,7 +1137,7 @@ button {
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
&.pre-wrap {
|
||||
.editor-content,
|
||||
.code-editor-content,
|
||||
.highlight-overlay {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
.code-editor {
|
||||
z-index: 0;
|
||||
min-height: 41px;
|
||||
.editor-content:empty::before {
|
||||
.code-editor-content:empty::before {
|
||||
font-family: var(--baseFontFamily);
|
||||
}
|
||||
.highlight-overlay {
|
||||
color: var(--surfaceTxtColor);
|
||||
}
|
||||
.editor-content, .highlight-overlay {
|
||||
.code-editor-content, .highlight-overlay {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user