logs refactoring

This commit is contained in:
Gani Georgiev 2023-11-26 13:33:17 +02:00
parent ff5535f4de
commit 821aae4a62
109 changed files with 7320 additions and 3728 deletions

View File

@ -1,3 +1,35 @@
## v0.20.0-rc4
- Bumped the minimum required Go version to 1.21.0 in order to integrate with the builtin `slog` package.
- removed _requests table in favor of _logs
- Renamed:
```
Dao.RequestQuery(...) -> Dao.LogQuery(...)
Dao.FindRequestById(...) -> Dao.FindLogById(...)
Dao.RequestsStats(...) -> Dao.LogsStats(...)
Dao.RequestsStats(...) -> Dao.LogsStats(...)
Dao.DeleteOldRequests(...) -> Dao.DeleteOldLogs(...)
Dao.SaveRequest(...) -> Dao.SaveLog(...)
```
- removed app.IsDebug() and the `--debug` flag
- (@todo docs) Implemented `slog.Logger` via `app.Logger()`.
Logs db writes are debounced and batched. DB write happens on
- 3sec after the last debounced log write
- when the batch threshold, currently 200, is reached (this is primarily to prevent the memory to grow unrestricted)
- right before app termination to attempt saving the current logs queue
Several minor log improvements:
- Log the requests execution times.
- Added option to toggle IP request logging.
- Added option to specify a minimum log level.
- Added option to export individual or bulk selected logs as json.
- Soft-deprecated and renamed `app.Cache()` with `app.Store()`.
## v0.20.0-rc3 ## v0.20.0-rc3
- Synced with the recent fixes in v0.19.4. - Synced with the recent fixes in v0.19.4.

View File

@ -1,7 +1,6 @@
package apis package apis
import ( import (
"log"
"net/http" "net/http"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
@ -129,9 +128,8 @@ func (api *adminApi) requestPasswordReset(c echo.Context) error {
return api.app.OnAdminBeforeRequestPasswordResetRequest().Trigger(event, func(e *core.AdminRequestPasswordResetEvent) error { return api.app.OnAdminBeforeRequestPasswordResetRequest().Trigger(event, func(e *core.AdminRequestPasswordResetEvent) error {
// run in background because we don't need to show the result to the client // run in background because we don't need to show the result to the client
routine.FireAndForget(func() { routine.FireAndForget(func() {
if err := next(e.Admin); err != nil && api.app.IsDebug() { if err := next(e.Admin); err != nil {
// @todo replace after logs generalization api.app.Logger().Error("Failed to send admin password reset request.", "error", err)
log.Println(err)
} }
}) })

View File

@ -2,7 +2,6 @@ package apis
import ( import (
"context" "context"
"log"
"net/http" "net/http"
"path/filepath" "path/filepath"
"time" "time"
@ -69,7 +68,7 @@ func (api *backupApi) list(c echo.Context) error {
} }
func (api *backupApi) create(c echo.Context) error { func (api *backupApi) create(c echo.Context) error {
if api.app.Cache().Has(core.CacheKeyActiveBackup) { if api.app.Store().Has(core.StoreKeyActiveBackup) {
return NewBadRequestError("Try again later - another backup/restore process has already been started", nil) return NewBadRequestError("Try again later - another backup/restore process has already been started", nil)
} }
@ -152,7 +151,7 @@ func (api *backupApi) download(c echo.Context) error {
} }
func (api *backupApi) restore(c echo.Context) error { func (api *backupApi) restore(c echo.Context) error {
if api.app.Cache().Has(core.CacheKeyActiveBackup) { if api.app.Store().Has(core.StoreKeyActiveBackup) {
return NewBadRequestError("Try again later - another backup/restore process has already been started.", nil) return NewBadRequestError("Try again later - another backup/restore process has already been started.", nil)
} }
@ -181,8 +180,8 @@ func (api *backupApi) restore(c echo.Context) error {
// give some optimistic time to write the response // give some optimistic time to write the response
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
if err := api.app.RestoreBackup(ctx, key); err != nil && api.app.IsDebug() { if err := api.app.RestoreBackup(ctx, key); err != nil {
log.Println(err) api.app.Logger().Error("Failed to restore backup", "key", key, "error", err.Error())
} }
}() }()
@ -203,7 +202,7 @@ func (api *backupApi) delete(c echo.Context) error {
key := c.PathParam("key") key := c.PathParam("key")
if key != "" && cast.ToString(api.app.Cache().Get(core.CacheKeyActiveBackup)) == key { if key != "" && cast.ToString(api.app.Store().Get(core.StoreKeyActiveBackup)) == key {
return NewBadRequestError("The backup is currently being used and cannot be deleted.", nil) return NewBadRequestError("The backup is currently being used and cannot be deleted.", nil)
} }

View File

@ -116,7 +116,7 @@ func TestBackupsCreate(t *testing.T) {
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
app.Cache().Set(core.CacheKeyActiveBackup, "") app.Store().Set(core.StoreKeyActiveBackup, "")
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
ensureNoBackups(t, app) ensureNoBackups(t, app)
@ -562,7 +562,7 @@ func TestBackupsDelete(t *testing.T) {
} }
// mock active backup with the same name to delete // mock active backup with the same name to delete
app.Cache().Set(core.CacheKeyActiveBackup, "test1.zip") app.Store().Set(core.StoreKeyActiveBackup, "test1.zip")
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
noTestBackupFilesChanges(t, app) noTestBackupFilesChanges(t, app)
@ -583,7 +583,7 @@ func TestBackupsDelete(t *testing.T) {
} }
// mock active backup with different name // mock active backup with different name
app.Cache().Set(core.CacheKeyActiveBackup, "new.zip") app.Store().Set(core.StoreKeyActiveBackup, "new.zip")
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
files, err := getBackupFiles(app) files, err := getBackupFiles(app)
@ -700,7 +700,7 @@ func TestBackupsRestore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
app.Cache().Set(core.CacheKeyActiveBackup, "") app.Store().Set(core.StoreKeyActiveBackup, "")
}, },
ExpectedStatus: 400, ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},

View File

@ -6,11 +6,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log" "log/slog"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware" "github.com/labstack/echo/v5/middleware"
@ -26,7 +27,7 @@ const trailedAdminPath = "/_/"
// system and app specific routes and middlewares. // system and app specific routes and middlewares.
func InitApi(app core.App) (*echo.Echo, error) { func InitApi(app core.App) (*echo.Echo, error) {
e := echo.New() e := echo.New()
e.Debug = app.IsDebug() e.Debug = false
e.JSONSerializer = &rest.Serializer{ e.JSONSerializer = &rest.Serializer{
FieldsParam: fieldsQueryParam, FieldsParam: fieldsQueryParam,
} }
@ -49,6 +50,13 @@ func InitApi(app core.App) (*echo.Echo, error) {
e.Pre(LoadAuthContext(app)) e.Pre(LoadAuthContext(app))
e.Use(middleware.Recover()) e.Use(middleware.Recover())
e.Use(middleware.Secure()) e.Use(middleware.Secure())
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set(ContextExecStartKey, time.Now())
return next(c)
}
})
// custom error handler // custom error handler
e.HTTPErrorHandler = func(c echo.Context, err error) { e.HTTPErrorHandler = func(c echo.Context, err error) {
@ -56,30 +64,14 @@ func InitApi(app core.App) (*echo.Echo, error) {
return // no error return // no error
} }
if c.Response().Committed {
if app.IsDebug() {
log.Println("HTTPErrorHandler response was already committed:", err)
}
return
}
var apiErr *ApiError var apiErr *ApiError
if errors.As(err, &apiErr) { if errors.As(err, &apiErr) {
if app.IsDebug() && apiErr.RawData() != nil { // already an api error...
log.Println(apiErr.RawData())
}
} else if v := new(echo.HTTPError); errors.As(err, &v) { } else if v := new(echo.HTTPError); errors.As(err, &v) {
if v.Internal != nil && app.IsDebug() {
log.Println(v.Internal)
}
msg := fmt.Sprintf("%v", v.Message) msg := fmt.Sprintf("%v", v.Message)
apiErr = NewApiError(v.Code, msg, v) apiErr = NewApiError(v.Code, msg, v)
} else { } else {
if app.IsDebug() {
log.Println(err)
}
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
apiErr = NewNotFoundError("", err) apiErr = NewNotFoundError("", err)
} else { } else {
@ -87,13 +79,19 @@ func InitApi(app core.App) (*echo.Echo, error) {
} }
} }
logRequest(app, c, apiErr)
if c.Response().Committed {
return // already commited
}
event := new(core.ApiErrorEvent) event := new(core.ApiErrorEvent)
event.HttpContext = c event.HttpContext = c
event.Error = apiErr event.Error = apiErr
// send error response // send error response
hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error { hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error {
if c.Response().Committed { if e.HttpContext.Response().Committed {
return nil return nil
} }
@ -106,12 +104,11 @@ func InitApi(app core.App) (*echo.Echo, error) {
}) })
if hookErr == nil { if hookErr == nil {
if err := app.OnAfterApiError().Trigger(event); err != nil && app.IsDebug() { if err := app.OnAfterApiError().Trigger(event); err != nil {
log.Println(hookErr) app.Logger().Debug("OnAfterApiError failure", slog.String("error", hookErr.Error()))
} }
} else if app.IsDebug() { } else {
// truly rare case; eg. client already disconnected app.Logger().Debug("OnBeforeApiError error (truly rare case, eg. client already disconnected)", slog.String("error", hookErr.Error()))
log.Println(hookErr)
} }
} }
@ -215,7 +212,7 @@ func updateHasAdminsCache(app core.App) error {
return err return err
} }
app.Cache().Set(hasAdminsCacheKey, total > 0) app.Store().Set(hasAdminsCacheKey, total > 0)
return nil return nil
} }
@ -240,14 +237,14 @@ func installerRedirect(app core.App) echo.MiddlewareFunc {
return next(c) return next(c)
} }
hasAdmins := cast.ToBool(app.Cache().Get(hasAdminsCacheKey)) hasAdmins := cast.ToBool(app.Store().Get(hasAdminsCacheKey))
if !hasAdmins { if !hasAdmins {
// update the cache to make sure that the admin wasn't created by another process // update the cache to make sure that the admin wasn't created by another process
if err := updateHasAdminsCache(app); err != nil { if err := updateHasAdminsCache(app); err != nil {
return err return err
} }
hasAdmins = cast.ToBool(app.Cache().Get(hasAdminsCacheKey)) hasAdmins = cast.ToBool(app.Store().Get(hasAdminsCacheKey))
} }
_, hasInstallerParam := c.Request().URL.Query()["installer"] _, hasInstallerParam := c.Request().URL.Query()["installer"]

View File

@ -1141,7 +1141,7 @@ func TestCollectionsImport(t *testing.T) {
}, },
ExpectedEvents: map[string]int{ ExpectedEvents: map[string]int{
"OnCollectionsBeforeImportRequest": 1, "OnCollectionsBeforeImportRequest": 1,
"OnModelBeforeDelete": 4, "OnModelBeforeDelete": 3,
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
collections := []*models.Collection{} collections := []*models.Collection{}

View File

@ -32,7 +32,7 @@ func (api *healthApi) healthCheck(c echo.Context) error {
resp := new(healthCheckResponse) resp := new(healthCheckResponse)
resp.Code = http.StatusOK resp.Code = http.StatusOK
resp.Message = "API is healthy." resp.Message = "API is healthy."
resp.Data.CanBackup = !api.app.Cache().Has(core.CacheKeyActiveBackup) resp.Data.CanBackup = !api.app.Store().Has(core.StoreKeyActiveBackup)
return c.JSON(http.StatusOK, resp) return c.JSON(http.StatusOK, resp)
} }

View File

@ -15,27 +15,27 @@ func bindLogsApi(app core.App, rg *echo.Group) {
api := logsApi{app: app} api := logsApi{app: app}
subGroup := rg.Group("/logs", RequireAdminAuth()) subGroup := rg.Group("/logs", RequireAdminAuth())
subGroup.GET("/requests", api.requestsList) subGroup.GET("", api.list)
subGroup.GET("/requests/stats", api.requestsStats) subGroup.GET("/stats", api.stats)
subGroup.GET("/requests/:id", api.requestView) subGroup.GET("/:id", api.view)
} }
type logsApi struct { type logsApi struct {
app core.App app core.App
} }
var requestFilterFields = []string{ var logFilterFields = []string{
"rowid", "id", "created", "updated", "rowid", "id", "created", "updated",
"url", "method", "status", "auth", "level", "message", "data",
"remoteIp", "userIp", "referer", "userAgent", `^data\.[\w\.\:]*\w+$`,
} }
func (api *logsApi) requestsList(c echo.Context) error { func (api *logsApi) list(c echo.Context) error {
fieldResolver := search.NewSimpleFieldResolver(requestFilterFields...) fieldResolver := search.NewSimpleFieldResolver(logFilterFields...)
result, err := search.NewProvider(fieldResolver). result, err := search.NewProvider(fieldResolver).
Query(api.app.LogsDao().RequestQuery()). Query(api.app.LogsDao().LogQuery()).
ParseAndExec(c.QueryParams().Encode(), &[]*models.Request{}) ParseAndExec(c.QueryParams().Encode(), &[]*models.Log{})
if err != nil { if err != nil {
return NewBadRequestError("", err) return NewBadRequestError("", err)
@ -44,8 +44,8 @@ func (api *logsApi) requestsList(c echo.Context) error {
return c.JSON(http.StatusOK, result) return c.JSON(http.StatusOK, result)
} }
func (api *logsApi) requestsStats(c echo.Context) error { func (api *logsApi) stats(c echo.Context) error {
fieldResolver := search.NewSimpleFieldResolver(requestFilterFields...) fieldResolver := search.NewSimpleFieldResolver(logFilterFields...)
filter := c.QueryParam(search.FilterQueryParam) filter := c.QueryParam(search.FilterQueryParam)
@ -58,24 +58,24 @@ func (api *logsApi) requestsStats(c echo.Context) error {
} }
} }
stats, err := api.app.LogsDao().RequestsStats(expr) stats, err := api.app.LogsDao().LogsStats(expr)
if err != nil { if err != nil {
return NewBadRequestError("Failed to generate requests stats.", err) return NewBadRequestError("Failed to generate logs stats.", err)
} }
return c.JSON(http.StatusOK, stats) return c.JSON(http.StatusOK, stats)
} }
func (api *logsApi) requestView(c echo.Context) error { func (api *logsApi) view(c echo.Context) error {
id := c.PathParam("id") id := c.PathParam("id")
if id == "" { if id == "" {
return NewNotFoundError("", nil) return NewNotFoundError("", nil)
} }
request, err := api.app.LogsDao().FindRequestById(id) log, err := api.app.LogsDao().FindLogById(id)
if err != nil || request == nil { if err != nil || log == nil {
return NewNotFoundError("", err) return NewNotFoundError("", err)
} }
return c.JSON(http.StatusOK, request) return c.JSON(http.StatusOK, log)
} }

View File

@ -8,19 +8,19 @@ import (
"github.com/pocketbase/pocketbase/tests" "github.com/pocketbase/pocketbase/tests"
) )
func TestRequestsList(t *testing.T) { func TestLogsList(t *testing.T) {
scenarios := []tests.ApiScenario{ scenarios := []tests.ApiScenario{
{ {
Name: "unauthorized", Name: "unauthorized",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests", Url: "/api/logs",
ExpectedStatus: 401, ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{ {
Name: "authorized as auth record", Name: "authorized as auth record",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests", Url: "/api/logs",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc", "Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
}, },
@ -30,12 +30,12 @@ func TestRequestsList(t *testing.T) {
{ {
Name: "authorized as admin", Name: "authorized as admin",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests", Url: "/api/logs",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },
@ -52,12 +52,12 @@ func TestRequestsList(t *testing.T) {
{ {
Name: "authorized as admin + filter", Name: "authorized as admin + filter",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests?filter=status>200", Url: "/api/logs?filter=data.status>200",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },
@ -77,19 +77,19 @@ func TestRequestsList(t *testing.T) {
} }
} }
func TestRequestView(t *testing.T) { func TestLogView(t *testing.T) {
scenarios := []tests.ApiScenario{ scenarios := []tests.ApiScenario{
{ {
Name: "unauthorized", Name: "unauthorized",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/873f2133-9f38-44fb-bf82-c8f53b310d91", Url: "/api/logs/873f2133-9f38-44fb-bf82-c8f53b310d91",
ExpectedStatus: 401, ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{ {
Name: "authorized as auth record", Name: "authorized as auth record",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/873f2133-9f38-44fb-bf82-c8f53b310d91", Url: "/api/logs/873f2133-9f38-44fb-bf82-c8f53b310d91",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc", "Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
}, },
@ -99,12 +99,12 @@ func TestRequestView(t *testing.T) {
{ {
Name: "authorized as admin (nonexisting request log)", Name: "authorized as admin (nonexisting request log)",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/missing1-9f38-44fb-bf82-c8f53b310d91", Url: "/api/logs/missing1-9f38-44fb-bf82-c8f53b310d91",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },
@ -114,12 +114,12 @@ func TestRequestView(t *testing.T) {
{ {
Name: "authorized as admin (existing request log)", Name: "authorized as admin (existing request log)",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/873f2133-9f38-44fb-bf82-c8f53b310d91", Url: "/api/logs/873f2133-9f38-44fb-bf82-c8f53b310d91",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },
@ -135,19 +135,19 @@ func TestRequestView(t *testing.T) {
} }
} }
func TestRequestsStats(t *testing.T) { func TestLogsStats(t *testing.T) {
scenarios := []tests.ApiScenario{ scenarios := []tests.ApiScenario{
{ {
Name: "unauthorized", Name: "unauthorized",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/stats", Url: "/api/logs/stats",
ExpectedStatus: 401, ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{ {
Name: "authorized as auth record", Name: "authorized as auth record",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/stats", Url: "/api/logs/stats",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc", "Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
}, },
@ -157,12 +157,12 @@ func TestRequestsStats(t *testing.T) {
{ {
Name: "authorized as admin", Name: "authorized as admin",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/stats", Url: "/api/logs/stats",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },
@ -174,12 +174,12 @@ func TestRequestsStats(t *testing.T) {
{ {
Name: "authorized as admin + filter", Name: "authorized as admin + filter",
Method: http.MethodGet, Method: http.MethodGet,
Url: "/api/logs/requests/stats?filter=status>200", Url: "/api/logs/stats?filter=data.status>200",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
if err := tests.MockRequestLogsData(app); err != nil { if err := tests.MockLogsData(app); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}, },

View File

@ -2,7 +2,7 @@ package apis
import ( import (
"fmt" "fmt"
"log" "log/slog"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -15,7 +15,6 @@ import (
"github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/routine" "github.com/pocketbase/pocketbase/tools/routine"
"github.com/pocketbase/pocketbase/tools/security" "github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/types"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
@ -24,6 +23,7 @@ const (
ContextAdminKey string = "admin" ContextAdminKey string = "admin"
ContextAuthRecordKey string = "authRecord" ContextAuthRecordKey string = "authRecord"
ContextCollectionKey string = "collection" ContextCollectionKey string = "collection"
ContextExecStartKey string = "execStart"
) )
// RequireGuestOnly middleware requires a request to NOT have a valid // RequireGuestOnly middleware requires a request to NOT have a valid
@ -285,34 +285,46 @@ func LoadCollectionContext(app core.App, optCollectionTypes ...string) echo.Midd
func ActivityLogger(app core.App) echo.MiddlewareFunc { func ActivityLogger(app core.App) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
err := next(c) if err := next(c); err != nil {
logsMaxDays := app.Settings().Logs.MaxDays
// no logs retention
if logsMaxDays == 0 {
return err return err
} }
logRequest(app, c, nil)
return nil
}
}
}
func logRequest(app core.App, c echo.Context, err *ApiError) {
// no logs retention
if app.Settings().Logs.MaxDays == 0 {
return
}
attrs := make([]any, 0, 15)
attrs = append(attrs, slog.String("type", "request"))
started := cast.ToTime(c.Get(ContextExecStartKey))
if !started.IsZero() {
attrs = append(attrs, slog.Float64("execTime", float64(time.Since(started))/float64(time.Millisecond)))
}
httpRequest := c.Request() httpRequest := c.Request()
httpResponse := c.Response() httpResponse := c.Response()
method := strings.ToUpper(httpRequest.Method)
status := httpResponse.Status status := httpResponse.Status
meta := types.JsonMap{} url := httpRequest.URL.RequestURI()
// parse the request error
if err != nil { if err != nil {
switch v := err.(type) { status = err.Code
case *echo.HTTPError: attrs = append(
status = v.Code attrs,
meta["errorMessage"] = v.Message slog.String("error", err.Message),
meta["errorDetails"] = fmt.Sprint(v.Internal) slog.Any("details", err.RawData()),
case *ApiError: )
status = v.Code
meta["errorMessage"] = v.Message
meta["errorDetails"] = fmt.Sprint(v.RawData())
default:
status = http.StatusBadRequest
meta["errorMessage"] = v.Error()
}
} }
requestAuth := models.RequestAuthGuest requestAuth := models.RequestAuthGuest
@ -322,47 +334,34 @@ func ActivityLogger(app core.App) echo.MiddlewareFunc {
requestAuth = models.RequestAuthAdmin requestAuth = models.RequestAuthAdmin
} }
attrs = append(
attrs,
slog.String("url", url),
slog.String("method", method),
slog.Int("status", status),
slog.String("auth", requestAuth),
slog.String("referer", httpRequest.Referer()),
slog.String("userAgent", httpRequest.UserAgent()),
)
if app.Settings().Logs.LogIp {
ip, _, _ := net.SplitHostPort(httpRequest.RemoteAddr) ip, _, _ := net.SplitHostPort(httpRequest.RemoteAddr)
attrs = append(
model := &models.Request{ attrs,
Url: httpRequest.URL.RequestURI(), slog.String("userIp", realUserIp(httpRequest, ip)),
Method: strings.ToUpper(httpRequest.Method), slog.String("remoteIp", ip),
Status: status, )
Auth: requestAuth,
UserIp: realUserIp(httpRequest, ip),
RemoteIp: ip,
Referer: httpRequest.Referer(),
UserAgent: httpRequest.UserAgent(),
Meta: meta,
} }
// set timestamp fields before firing a new go routine
model.RefreshCreated()
model.RefreshUpdated()
// don't block on logs write
routine.FireAndForget(func() { routine.FireAndForget(func() {
if err := app.LogsDao().SaveRequest(model); err != nil && app.IsDebug() { message := method + " " + url
log.Println("Log save failed:", err) if err != nil {
} app.Logger().Error("(Failed) "+message, attrs...)
} else {
// Delete old request logs app.Logger().Info(message, attrs...)
// ---
now := time.Now()
lastLogsDeletedAt := cast.ToTime(app.Cache().Get("lastLogsDeletedAt"))
daysDiff := now.Sub(lastLogsDeletedAt).Hours() * 24
if daysDiff > float64(logsMaxDays) {
deleteErr := app.LogsDao().DeleteOldRequests(now.AddDate(0, 0, -1*logsMaxDays))
if deleteErr == nil {
app.Cache().Set("lastLogsDeletedAt", now)
} else if app.IsDebug() {
log.Println("Logs delete failed:", deleteErr)
}
} }
}) })
return err
}
}
} }
// Returns the "real" user IP from common proxy headers (or fallbackIp if none is found). // Returns the "real" user IP from common proxy headers (or fallbackIp if none is found).

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log/slog"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -51,8 +51,12 @@ func (api *realtimeApi) connect(c echo.Context) error {
Client: client, Client: client,
} }
if err := api.app.OnRealtimeDisconnectRequest().Trigger(disconnectEvent); err != nil && api.app.IsDebug() { if err := api.app.OnRealtimeDisconnectRequest().Trigger(disconnectEvent); err != nil {
log.Println(err) api.app.Logger().Debug(
"OnRealtimeDisconnectRequest error",
slog.String("clientId", client.Id()),
slog.String("error", err.Error()),
)
} }
api.app.SubscriptionsBroker().Unregister(client.Id()) api.app.SubscriptionsBroker().Unregister(client.Id())
@ -74,9 +78,7 @@ func (api *realtimeApi) connect(c echo.Context) error {
return err return err
} }
if api.app.IsDebug() { api.app.Logger().Debug("Realtime connection established.", slog.String("clientId", client.Id()))
log.Printf("Realtime connection established: %s\n", client.Id())
}
// signalize established connection (aka. fire "connect" message) // signalize established connection (aka. fire "connect" message)
connectMsgEvent := &core.RealtimeMessageEvent{ connectMsgEvent := &core.RealtimeMessageEvent{
@ -98,9 +100,11 @@ func (api *realtimeApi) connect(c echo.Context) error {
return api.app.OnRealtimeAfterMessageSend().Trigger(e) return api.app.OnRealtimeAfterMessageSend().Trigger(e)
}) })
if connectMsgErr != nil { if connectMsgErr != nil {
if api.app.IsDebug() { api.app.Logger().Debug(
log.Println("Realtime connection closed (failed to deliver PB_CONNECT):", client.Id(), connectMsgErr) "Realtime connection closed (failed to deliver PB_CONNECT)",
} slog.String("clientId", client.Id()),
slog.String("error", connectMsgErr.Error()),
)
return nil return nil
} }
@ -116,9 +120,10 @@ func (api *realtimeApi) connect(c echo.Context) error {
case msg, ok := <-client.Channel(): case msg, ok := <-client.Channel():
if !ok { if !ok {
// channel is closed // channel is closed
if api.app.IsDebug() { api.app.Logger().Debug(
log.Println("Realtime connection closed (closed channel):", client.Id()) "Realtime connection closed (closed channel)",
} slog.String("clientId", client.Id()),
)
return nil return nil
} }
@ -138,9 +143,11 @@ func (api *realtimeApi) connect(c echo.Context) error {
return api.app.OnRealtimeAfterMessageSend().Trigger(msgEvent) return api.app.OnRealtimeAfterMessageSend().Trigger(msgEvent)
}) })
if msgErr != nil { if msgErr != nil {
if api.app.IsDebug() { api.app.Logger().Debug(
log.Println("Realtime connection closed (failed to deliver message):", client.Id(), msgErr) "Realtime connection closed (failed to deliver message)",
} slog.String("clientId", client.Id()),
slog.String("error", msgErr.Error()),
)
return nil return nil
} }
@ -148,9 +155,10 @@ func (api *realtimeApi) connect(c echo.Context) error {
idleTimer.Reset(idleTimeout) idleTimer.Reset(idleTimeout)
case <-c.Request().Context().Done(): case <-c.Request().Context().Done():
// connection is closed // connection is closed
if api.app.IsDebug() { api.app.Logger().Debug(
log.Println("Realtime connection closed (cancelled request):", client.Id()) "Realtime connection closed (cancelled request)",
} slog.String("clientId", client.Id()),
)
return nil return nil
} }
} }
@ -267,8 +275,13 @@ func (api *realtimeApi) bindEvents() {
api.app.OnModelAfterCreate().PreAdd(func(e *core.ModelEvent) error { api.app.OnModelAfterCreate().PreAdd(func(e *core.ModelEvent) error {
if record := api.resolveRecord(e.Model); record != nil { if record := api.resolveRecord(e.Model); record != nil {
if err := api.broadcastRecord("create", record, false); err != nil && api.app.IsDebug() { if err := api.broadcastRecord("create", record, false); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to broadcast record create",
slog.String("id", record.Id),
slog.String("collectionName", record.Collection().Name),
slog.String("error", err.Error()),
)
} }
} }
return nil return nil
@ -276,8 +289,13 @@ func (api *realtimeApi) bindEvents() {
api.app.OnModelAfterUpdate().PreAdd(func(e *core.ModelEvent) error { api.app.OnModelAfterUpdate().PreAdd(func(e *core.ModelEvent) error {
if record := api.resolveRecord(e.Model); record != nil { if record := api.resolveRecord(e.Model); record != nil {
if err := api.broadcastRecord("update", record, false); err != nil && api.app.IsDebug() { if err := api.broadcastRecord("update", record, false); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to broadcast record update",
slog.String("id", record.Id),
slog.String("collectionName", record.Collection().Name),
slog.String("error", err.Error()),
)
} }
} }
return nil return nil
@ -285,8 +303,13 @@ func (api *realtimeApi) bindEvents() {
api.app.OnModelBeforeDelete().Add(func(e *core.ModelEvent) error { api.app.OnModelBeforeDelete().Add(func(e *core.ModelEvent) error {
if record := api.resolveRecord(e.Model); record != nil { if record := api.resolveRecord(e.Model); record != nil {
if err := api.broadcastRecord("delete", record, true); err != nil && api.app.IsDebug() { if err := api.broadcastRecord("delete", record, true); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to dry cache record delete",
slog.String("id", record.Id),
slog.String("collectionName", record.Collection().Name),
slog.String("error", err.Error()),
)
} }
} }
return nil return nil
@ -294,8 +317,13 @@ func (api *realtimeApi) bindEvents() {
api.app.OnModelAfterDelete().Add(func(e *core.ModelEvent) error { api.app.OnModelAfterDelete().Add(func(e *core.ModelEvent) error {
if record := api.resolveRecord(e.Model); record != nil { if record := api.resolveRecord(e.Model); record != nil {
if err := api.broadcastDryCachedRecord("delete", record); err != nil && api.app.IsDebug() { if err := api.broadcastDryCachedRecord("delete", record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to broadcast record delete",
slog.String("id", record.Id),
slog.String("collectionName", record.Collection().Name),
slog.String("error", err.Error()),
)
} }
} }
return nil return nil
@ -389,8 +417,15 @@ func (api *realtimeApi) broadcastRecord(action string, record *models.Record, dr
rawExpand := cast.ToString(options.Query[expandQueryParam]) rawExpand := cast.ToString(options.Query[expandQueryParam])
if rawExpand != "" { if rawExpand != "" {
expandErrs := api.app.Dao().ExpandRecord(cleanRecord, strings.Split(rawExpand, ","), expandFetch(api.app.Dao(), requestInfo)) expandErrs := api.app.Dao().ExpandRecord(cleanRecord, strings.Split(rawExpand, ","), expandFetch(api.app.Dao(), requestInfo))
if api.app.IsDebug() && len(expandErrs) > 0 { if len(expandErrs) > 0 {
log.Println("[broadcastRecord] expand errors", expandErrs) api.app.Logger().Debug(
"[broadcastRecord] expand errors",
slog.String("id", cleanRecord.Id),
slog.String("collectionName", cleanRecord.Collection().Name),
slog.String("sub", sub),
slog.String("expand", rawExpand),
slog.Any("errors", expandErrs),
)
} }
} }
@ -416,14 +451,26 @@ func (api *realtimeApi) broadcastRecord(action string, record *models.Record, dr
decoded, err := rest.PickFields(cleanRecord, rawFields) decoded, err := rest.PickFields(cleanRecord, rawFields)
if err == nil { if err == nil {
data.Record = decoded data.Record = decoded
} else if api.app.IsDebug() { } else {
log.Println(err) api.app.Logger().Debug(
"[broadcastRecord] pick fields error",
slog.String("id", cleanRecord.Id),
slog.String("collectionName", cleanRecord.Collection().Name),
slog.String("sub", sub),
slog.String("fields", rawFields),
slog.String("error", err.Error()),
)
} }
} }
dataBytes, err := json.Marshal(data) dataBytes, err := json.Marshal(data)
if err != nil && api.app.IsDebug() { if err != nil {
log.Println("[broadcastRecord] data marshal error", err) api.app.Logger().Debug(
"[broadcastRecord] data marshal error",
slog.String("id", cleanRecord.Id),
slog.String("collectionName", cleanRecord.Collection().Name),
slog.String("error", err.Error()),
)
continue continue
} }

View File

@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log" "log/slog"
"net/http" "net/http"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
@ -111,16 +111,16 @@ func (api *recordAuthApi) authMethods(c echo.Context) error {
provider, err := auth.NewProviderByName(name) provider, err := auth.NewProviderByName(name)
if err != nil { if err != nil {
if api.app.IsDebug() { api.app.Logger().Debug("Missing or invalid provier name", slog.String("name", name))
log.Println(err)
}
continue // skip provider continue // skip provider
} }
if err := config.SetupProvider(provider); err != nil { if err := config.SetupProvider(provider); err != nil {
if api.app.IsDebug() { api.app.Logger().Debug(
log.Println(err) "Failed to setup provider",
} slog.String("name", name),
slog.String("error", err.Error()),
)
continue // skip provider continue // skip provider
} }
@ -327,8 +327,11 @@ func (api *recordAuthApi) requestPasswordReset(c echo.Context) error {
return api.app.OnRecordBeforeRequestPasswordResetRequest().Trigger(event, func(e *core.RecordRequestPasswordResetEvent) error { return api.app.OnRecordBeforeRequestPasswordResetRequest().Trigger(event, func(e *core.RecordRequestPasswordResetEvent) error {
// run in background because we don't need to show the result to the client // run in background because we don't need to show the result to the client
routine.FireAndForget(func() { routine.FireAndForget(func() {
if err := next(e.Record); err != nil && api.app.IsDebug() { if err := next(e.Record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to send password reset email",
slog.String("error", err.Error()),
)
} }
}) })
@ -416,8 +419,11 @@ func (api *recordAuthApi) requestVerification(c echo.Context) error {
return api.app.OnRecordBeforeRequestVerificationRequest().Trigger(event, func(e *core.RecordRequestVerificationEvent) error { return api.app.OnRecordBeforeRequestVerificationRequest().Trigger(event, func(e *core.RecordRequestVerificationEvent) error {
// run in background because we don't need to show the result to the client // run in background because we don't need to show the result to the client
routine.FireAndForget(func() { routine.FireAndForget(func() {
if err := next(e.Record); err != nil && api.app.IsDebug() { if err := next(e.Record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to send verification email",
slog.String("error", err.Error()),
)
} }
}) })

View File

@ -898,7 +898,7 @@ func TestRecordAuthRequestEmailChange(t *testing.T) {
ExpectedStatus: 400, ExpectedStatus: 400,
ExpectedContent: []string{ ExpectedContent: []string{
`"data":`, `"data":`,
`"newEmail":{"code":"validation_record_email_exists"`, `"newEmail":{"code":"validation_record_email_invalid"`,
}, },
}, },
{ {

View File

@ -2,7 +2,7 @@ package apis
import ( import (
"fmt" "fmt"
"log" "log/slog"
"net/http" "net/http"
"strings" "strings"
@ -88,8 +88,8 @@ func (api *recordApi) list(c echo.Context) error {
return nil return nil
} }
if err := EnrichRecords(e.HttpContext, api.app.Dao(), e.Records); err != nil && api.app.IsDebug() { if err := EnrichRecords(e.HttpContext, api.app.Dao(), e.Records); err != nil {
log.Println(err) api.app.Logger().Debug("Failed to enrich list records", slog.String("error", err.Error()))
} }
return e.HttpContext.JSON(http.StatusOK, e.Result) return e.HttpContext.JSON(http.StatusOK, e.Result)
@ -142,8 +142,13 @@ func (api *recordApi) view(c echo.Context) error {
return nil return nil
} }
if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil && api.app.IsDebug() { if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to enrich view record",
slog.String("id", e.Record.Id),
slog.String("collectionName", e.Record.Collection().Name),
slog.String("error", err.Error()),
)
} }
return e.HttpContext.JSON(http.StatusOK, e.Record) return e.HttpContext.JSON(http.StatusOK, e.Record)
@ -235,8 +240,13 @@ func (api *recordApi) create(c echo.Context) error {
return NewBadRequestError("Failed to create record.", err) return NewBadRequestError("Failed to create record.", err)
} }
if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil && api.app.IsDebug() { if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to enrich create record",
slog.String("id", e.Record.Id),
slog.String("collectionName", e.Record.Collection().Name),
slog.String("error", err.Error()),
)
} }
return api.app.OnRecordAfterCreateRequest().Trigger(event, func(e *core.RecordCreateEvent) error { return api.app.OnRecordAfterCreateRequest().Trigger(event, func(e *core.RecordCreateEvent) error {
@ -322,8 +332,13 @@ func (api *recordApi) update(c echo.Context) error {
return NewBadRequestError("Failed to update record.", err) return NewBadRequestError("Failed to update record.", err)
} }
if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil && api.app.IsDebug() { if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil {
log.Println(err) api.app.Logger().Debug(
"Failed to enrich update record",
slog.String("id", e.Record.Id),
slog.String("collectionName", e.Record.Collection().Name),
slog.String("error", err.Error()),
)
} }
return api.app.OnRecordAfterUpdateRequest().Trigger(event, func(e *core.RecordUpdateEvent) error { return api.app.OnRecordAfterUpdateRequest().Trigger(event, func(e *core.RecordUpdateEvent) error {

View File

@ -3,6 +3,7 @@ package apis
import ( import (
"fmt" "fmt"
"log" "log"
"log/slog"
"net/http" "net/http"
"strings" "strings"
@ -108,8 +109,8 @@ func RecordAuthResponse(
expands, expands,
expandFetch(app.Dao(), &requestInfo), expandFetch(app.Dao(), &requestInfo),
) )
if len(failed) > 0 && app.IsDebug() { if len(failed) > 0 {
log.Println("Failed to expand relations: ", failed) app.Logger().Debug("[RecordAuthResponse] Failed to expand relations", slog.Any("errors", failed))
} }
} }

View File

@ -191,7 +191,7 @@ func Serve(app core.App, config ServeConfig) (*http.Server, error) {
// try to gracefully shutdown the server on app termination // try to gracefully shutdown the server on app termination
app.OnTerminate().Add(func(e *core.TerminateEvent) error { app.OnTerminate().Add(func(e *core.TerminateEvent) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
server.Shutdown(ctx) server.Shutdown(ctx)
return nil return nil

View File

@ -5,6 +5,7 @@ package core
import ( import (
"context" "context"
"log/slog"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos" "github.com/pocketbase/pocketbase/daos"
@ -48,6 +49,9 @@ type App interface {
// the users table from LogsDao will result in error. // the users table from LogsDao will result in error.
LogsDao() *daos.Dao LogsDao() *daos.Dao
// Logger returns the active app logger.
Logger() *slog.Logger
// DataDir returns the app data directory path. // DataDir returns the app data directory path.
DataDir() string DataDir() string
@ -55,16 +59,15 @@ type App interface {
// (used for settings encryption). // (used for settings encryption).
EncryptionEnv() string EncryptionEnv() string
// IsDebug returns whether the app is in debug mode
// (showing more detailed error logs, executed sql statements, etc.).
IsDebug() bool
// Settings returns the loaded app settings. // Settings returns the loaded app settings.
Settings() *settings.Settings Settings() *settings.Settings
// Cache returns the app internal cache store. // Deprecated: Use app.Store() instead.
Cache() *store.Store[any] Cache() *store.Store[any]
// Store returns the app runtime store.
Store() *store.Store[any]
// SubscriptionsBroker returns the app realtime subscriptions broker instance. // SubscriptionsBroker returns the app realtime subscriptions broker instance.
SubscriptionsBroker() *subscriptions.Broker SubscriptionsBroker() *subscriptions.Broker

View File

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"log" "log"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -18,10 +19,14 @@ import (
"github.com/pocketbase/pocketbase/models/settings" "github.com/pocketbase/pocketbase/models/settings"
"github.com/pocketbase/pocketbase/tools/filesystem" "github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/hook"
"github.com/pocketbase/pocketbase/tools/logger"
"github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/routine" "github.com/pocketbase/pocketbase/tools/routine"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/store" "github.com/pocketbase/pocketbase/tools/store"
"github.com/pocketbase/pocketbase/tools/subscriptions" "github.com/pocketbase/pocketbase/tools/subscriptions"
"github.com/pocketbase/pocketbase/tools/types"
"github.com/spf13/cast"
) )
const ( const (
@ -39,8 +44,9 @@ var _ App = (*BaseApp)(nil)
// BaseApp implements core.App and defines the base PocketBase app structure. // BaseApp implements core.App and defines the base PocketBase app structure.
type BaseApp struct { type BaseApp struct {
// @todo consider introducing a mutex to allow safe concurrent config changes during runtime
// configurable parameters // configurable parameters
isDebug bool
dataDir string dataDir string
encryptionEnv string encryptionEnv string
dataMaxOpenConns int dataMaxOpenConns int
@ -49,11 +55,12 @@ type BaseApp struct {
logsMaxIdleConns int logsMaxIdleConns int
// internals // internals
cache *store.Store[any] store *store.Store[any]
settings *settings.Settings settings *settings.Settings
dao *daos.Dao dao *daos.Dao
logsDao *daos.Dao logsDao *daos.Dao
subscriptionsBroker *subscriptions.Broker subscriptionsBroker *subscriptions.Broker
logger *slog.Logger
// app event hooks // app event hooks
onBeforeBootstrap *hook.Hook[*BootstrapEvent] onBeforeBootstrap *hook.Hook[*BootstrapEvent]
@ -169,7 +176,6 @@ type BaseApp struct {
type BaseAppConfig struct { type BaseAppConfig struct {
DataDir string DataDir string
EncryptionEnv string EncryptionEnv string
IsDebug bool
DataMaxOpenConns int // default to 500 DataMaxOpenConns int // default to 500
DataMaxIdleConns int // default 20 DataMaxIdleConns int // default 20
LogsMaxOpenConns int // default to 100 LogsMaxOpenConns int // default to 100
@ -183,13 +189,12 @@ type BaseAppConfig struct {
func NewBaseApp(config BaseAppConfig) *BaseApp { func NewBaseApp(config BaseAppConfig) *BaseApp {
app := &BaseApp{ app := &BaseApp{
dataDir: config.DataDir, dataDir: config.DataDir,
isDebug: config.IsDebug,
encryptionEnv: config.EncryptionEnv, encryptionEnv: config.EncryptionEnv,
dataMaxOpenConns: config.DataMaxOpenConns, dataMaxOpenConns: config.DataMaxOpenConns,
dataMaxIdleConns: config.DataMaxIdleConns, dataMaxIdleConns: config.DataMaxIdleConns,
logsMaxOpenConns: config.LogsMaxOpenConns, logsMaxOpenConns: config.LogsMaxOpenConns,
logsMaxIdleConns: config.LogsMaxIdleConns, logsMaxIdleConns: config.LogsMaxIdleConns,
cache: store.New[any](nil), store: store.New[any](nil),
settings: settings.New(), settings: settings.New(),
subscriptionsBroker: subscriptions.NewBroker(), subscriptionsBroker: subscriptions.NewBroker(),
@ -314,6 +319,17 @@ func (app *BaseApp) IsBootstrapped() bool {
return app.dao != nil && app.logsDao != nil && app.settings != nil return app.dao != nil && app.logsDao != nil && app.settings != nil
} }
// Logger returns the default app logger.
//
// If the application is not bootstrapped yet, fallbacks to slog.Default().
func (app *BaseApp) Logger() *slog.Logger {
if app.logger == nil {
return slog.Default()
}
return app.logger
}
// Bootstrap initializes the application // Bootstrap initializes the application
// (aka. create data dir, open db connections, load settings, etc.). // (aka. create data dir, open db connections, load settings, etc.).
// //
@ -343,6 +359,10 @@ func (app *BaseApp) Bootstrap() error {
return err return err
} }
if err := app.initLogger(); err != nil {
return err
}
// we don't check for an error because the db migrations may have not been executed yet // we don't check for an error because the db migrations may have not been executed yet
app.RefreshSettings() app.RefreshSettings()
@ -438,20 +458,20 @@ func (app *BaseApp) EncryptionEnv() string {
return app.encryptionEnv return app.encryptionEnv
} }
// IsDebug returns whether the app is in debug mode
// (showing more detailed error logs, executed sql statements, etc.).
func (app *BaseApp) IsDebug() bool {
return app.isDebug
}
// Settings returns the loaded app settings. // Settings returns the loaded app settings.
func (app *BaseApp) Settings() *settings.Settings { func (app *BaseApp) Settings() *settings.Settings {
return app.settings return app.settings
} }
// Cache returns the app internal cache store. // Deprecated: Use app.Store() instead.
func (app *BaseApp) Cache() *store.Store[any] { func (app *BaseApp) Cache() *store.Store[any] {
return app.cache color.Yellow("app.Store() is soft-deprecated. Please replace it with app.Store().")
return app.Store()
}
// Store returns the app internal runtime store.
func (app *BaseApp) Store() *store.Store[any] {
return app.store
} }
// SubscriptionsBroker returns the app realtime subscriptions broker instance. // SubscriptionsBroker returns the app realtime subscriptions broker instance.
@ -569,6 +589,11 @@ func (app *BaseApp) RefreshSettings() error {
return err return err
} }
// reload handler level (if initialized)
if h, ok := app.Logger().Handler().(*logger.BatchHandler); ok {
h.SetLevel(slog.Level(app.settings.Logs.MinLevel))
}
return nil return nil
} }
@ -988,7 +1013,7 @@ func (app *BaseApp) initLogsDB() error {
} }
concurrentDB.DB().SetMaxOpenConns(maxOpenConns) concurrentDB.DB().SetMaxOpenConns(maxOpenConns)
concurrentDB.DB().SetMaxIdleConns(maxIdleConns) concurrentDB.DB().SetMaxIdleConns(maxIdleConns)
concurrentDB.DB().SetConnMaxIdleTime(5 * time.Minute) concurrentDB.DB().SetConnMaxIdleTime(3 * time.Minute)
nonconcurrentDB, err := connectDB(filepath.Join(app.DataDir(), "logs.db")) nonconcurrentDB, err := connectDB(filepath.Join(app.DataDir(), "logs.db"))
if err != nil { if err != nil {
@ -996,7 +1021,7 @@ func (app *BaseApp) initLogsDB() error {
} }
nonconcurrentDB.DB().SetMaxOpenConns(1) nonconcurrentDB.DB().SetMaxOpenConns(1)
nonconcurrentDB.DB().SetMaxIdleConns(1) nonconcurrentDB.DB().SetMaxIdleConns(1)
nonconcurrentDB.DB().SetConnMaxIdleTime(5 * time.Minute) nonconcurrentDB.DB().SetConnMaxIdleTime(3 * time.Minute)
app.logsDao = daos.NewMultiDB(concurrentDB, nonconcurrentDB) app.logsDao = daos.NewMultiDB(concurrentDB, nonconcurrentDB)
@ -1019,7 +1044,7 @@ func (app *BaseApp) initDataDB() error {
} }
concurrentDB.DB().SetMaxOpenConns(maxOpenConns) concurrentDB.DB().SetMaxOpenConns(maxOpenConns)
concurrentDB.DB().SetMaxIdleConns(maxIdleConns) concurrentDB.DB().SetMaxIdleConns(maxIdleConns)
concurrentDB.DB().SetConnMaxIdleTime(5 * time.Minute) concurrentDB.DB().SetConnMaxIdleTime(3 * time.Minute)
nonconcurrentDB, err := connectDB(filepath.Join(app.DataDir(), "data.db")) nonconcurrentDB, err := connectDB(filepath.Join(app.DataDir(), "data.db"))
if err != nil { if err != nil {
@ -1027,19 +1052,17 @@ func (app *BaseApp) initDataDB() error {
} }
nonconcurrentDB.DB().SetMaxOpenConns(1) nonconcurrentDB.DB().SetMaxOpenConns(1)
nonconcurrentDB.DB().SetMaxIdleConns(1) nonconcurrentDB.DB().SetMaxIdleConns(1)
nonconcurrentDB.DB().SetConnMaxIdleTime(5 * time.Minute) nonconcurrentDB.DB().SetConnMaxIdleTime(3 * time.Minute)
if app.IsDebug() { // @todo benchmark whether it will have an impact if always enabled as TRACE log
nonconcurrentDB.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) { // nonconcurrentDB.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql) // color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
} // }
concurrentDB.QueryLogFunc = nonconcurrentDB.QueryLogFunc // concurrentDB.QueryLogFunc = nonconcurrentDB.QueryLogFunc
// nonconcurrentDB.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
nonconcurrentDB.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) { // color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql) // }
} // concurrentDB.ExecLogFunc = nonconcurrentDB.ExecLogFunc
concurrentDB.ExecLogFunc = nonconcurrentDB.ExecLogFunc
}
app.dao = app.createDaoWithHooks(concurrentDB, nonconcurrentDB) app.dao = app.createDaoWithHooks(concurrentDB, nonconcurrentDB)
@ -1129,14 +1152,13 @@ func (app *BaseApp) registerDefaultHooks() {
// run in the background for "optimistic" delete to avoid // run in the background for "optimistic" delete to avoid
// blocking the delete transaction // blocking the delete transaction
//
// @todo consider creating a bg process queue so that the
// call could be "retried" in case of a failure.
routine.FireAndForget(func() { routine.FireAndForget(func() {
if err := deletePrefix(prefix); err != nil && app.IsDebug() { if err := deletePrefix(prefix); err != nil {
// non critical error - only log for debug app.Logger().Error(
// (usually could happen because of S3 api limits) "Failed to delete storage prefix (non critical error; usually could happen because of S3 api limits)",
log.Println(err) slog.String("prefix", prefix),
slog.String("error", err.Error()),
)
} }
}) })
} }
@ -1144,12 +1166,94 @@ func (app *BaseApp) registerDefaultHooks() {
return nil return nil
}) })
app.OnTerminate().Add(func(e *TerminateEvent) error { if err := app.initAutobackupHooks(); err != nil {
app.ResetBootstrapState() app.Logger().Error("Failed to init auto backup hooks", slog.String("error", err.Error()))
}
}
func (app *BaseApp) initLogger() error {
duration := 3 * time.Second
ticker := time.NewTicker(duration)
done := make(chan bool)
level := slog.LevelInfo
if app.Settings() != nil {
level = slog.Level(app.Settings().Logs.MinLevel)
}
handler := logger.NewBatchHandler(logger.BatchOptions{
Level: level,
BatchSize: 200,
BeforeAddFunc: func(ctx context.Context, log *logger.Log) bool {
ticker.Reset(duration)
return true
},
WriteFunc: func(ctx context.Context, logs []*logger.Log) error {
if !app.IsBootstrapped() {
return nil
}
// 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.LogsDao().RunInTransaction(func(txDao *daos.Dao) error {
model := &models.Log{}
for _, l := range logs {
model.MarkAsNew()
// note: using pseudorandom for a slightly better performance
model.Id = security.PseudorandomStringWithAlphabet(models.DefaultIdLength, models.DefaultIdAlphabet)
model.Level = int(l.Level)
model.Message = l.Message
model.Data = l.Data
model.Created, _ = types.ParseDateTime(l.Time)
model.Updated = model.Created
if err := txDao.SaveLog(model); err != nil {
log.Println("Failed to write log", model, err)
}
}
return nil return nil
}) })
if err := app.initAutobackupHooks(); err != nil && app.IsDebug() { // delete old logs
log.Println(err) // ---
logsMaxDays := app.Settings().Logs.MaxDays
now := time.Now()
lastLogsDeletedAt := cast.ToTime(app.Store().Get("lastLogsDeletedAt"))
daysDiff := now.Sub(lastLogsDeletedAt).Hours() * 24
if daysDiff > float64(logsMaxDays) {
deleteErr := app.LogsDao().DeleteOldLogs(now.AddDate(0, 0, -1*logsMaxDays))
if deleteErr == nil {
app.Store().Set("lastLogsDeletedAt", now)
} else {
log.Println("Logs delete failed", deleteErr)
} }
} }
return nil
},
})
ctx := context.Background()
go func() {
for {
select {
case <-done:
handler.WriteAll(ctx)
case <-ticker.C:
handler.WriteAll(ctx)
}
}
}()
app.logger = slog.New(handler)
app.OnTerminate().PreAdd(func(e *TerminateEvent) error {
ticker.Stop()
done <- true
return nil
})
return nil
}

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -22,8 +22,11 @@ import (
"github.com/pocketbase/pocketbase/tools/security" "github.com/pocketbase/pocketbase/tools/security"
) )
// Deprecated: Replaced with StoreKeyActiveBackup.
const CacheKeyActiveBackup string = "@activeBackup" const CacheKeyActiveBackup string = "@activeBackup"
const StoreKeyActiveBackup string = "@activeBackup"
// CreateBackup creates a new backup of the current app pb_data directory. // CreateBackup creates a new backup of the current app pb_data directory.
// //
// If name is empty, it will be autogenerated. // If name is empty, it will be autogenerated.
@ -43,7 +46,7 @@ const CacheKeyActiveBackup string = "@activeBackup"
// //
// Backups can be stored on S3 if it is configured in app.Settings().Backups. // Backups can be stored on S3 if it is configured in app.Settings().Backups.
func (app *BaseApp) CreateBackup(ctx context.Context, name string) error { func (app *BaseApp) CreateBackup(ctx context.Context, name string) error {
if app.Cache().Has(CacheKeyActiveBackup) { if app.Store().Has(StoreKeyActiveBackup) {
return errors.New("try again later - another backup/restore operation has already been started") return errors.New("try again later - another backup/restore operation has already been started")
} }
@ -51,8 +54,8 @@ func (app *BaseApp) CreateBackup(ctx context.Context, name string) error {
name = app.generateBackupName("pb_backup_") name = app.generateBackupName("pb_backup_")
} }
app.Cache().Set(CacheKeyActiveBackup, name) app.Store().Set(StoreKeyActiveBackup, name)
defer app.Cache().Remove(CacheKeyActiveBackup) defer app.Store().Remove(StoreKeyActiveBackup)
// root dir entries to exclude from the backup generation // root dir entries to exclude from the backup generation
exclude := []string{LocalBackupsDirName, LocalTempDirName} exclude := []string{LocalBackupsDirName, LocalTempDirName}
@ -135,12 +138,12 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
return errors.New("restore is not supported on windows") return errors.New("restore is not supported on windows")
} }
if app.Cache().Has(CacheKeyActiveBackup) { if app.Store().Has(StoreKeyActiveBackup) {
return errors.New("try again later - another backup/restore operation has already been started") return errors.New("try again later - another backup/restore operation has already been started")
} }
app.Cache().Set(CacheKeyActiveBackup, name) app.Store().Set(StoreKeyActiveBackup, name)
defer app.Cache().Remove(CacheKeyActiveBackup) defer app.Store().Remove(StoreKeyActiveBackup)
fsys, err := app.NewBackupsFilesystem() fsys, err := app.NewBackupsFilesystem()
if err != nil { if err != nil {
@ -189,8 +192,12 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
// remove the extracted zip file since we no longer need it // remove the extracted zip file since we no longer need it
// (this is in case the app restarts and the defer calls are not called) // (this is in case the app restarts and the defer calls are not called)
if err := os.Remove(tempZip.Name()); err != nil && app.IsDebug() { if err := os.Remove(tempZip.Name()); err != nil {
log.Println(err) app.Logger().Debug(
"[RestoreBackup] Failed to remove the temp zip backup file",
slog.String("file", tempZip.Name()),
slog.String("error", err.Error()),
)
} }
// root dir entries to exclude from the backup restore // root dir entries to exclude from the backup restore
@ -223,8 +230,8 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
// restart the app // restart the app
if err := app.Restart(); err != nil { if err := app.Restart(); err != nil {
if err := revertDataDirChanges(); err != nil { if revertErr := revertDataDirChanges(); revertErr != nil {
panic(err) panic(revertErr)
} }
return fmt.Errorf("failed to restart the app process: %w", err) return fmt.Errorf("failed to restart the app process: %w", err)
@ -251,9 +258,12 @@ func (app *BaseApp) initAutobackupHooks() error {
name := app.generateBackupName(autoPrefix) name := app.generateBackupName(autoPrefix)
if err := app.CreateBackup(context.Background(), name); err != nil && app.IsDebug() { if err := app.CreateBackup(context.Background(), name); err != nil {
// @todo replace after logs generalization app.Logger().Debug(
log.Println(err) "[Backup cron] Failed to create backup",
slog.String("name", name),
slog.String("error", err.Error()),
)
} }
maxKeep := app.Settings().Backups.CronMaxKeep maxKeep := app.Settings().Backups.CronMaxKeep
@ -263,17 +273,21 @@ func (app *BaseApp) initAutobackupHooks() error {
} }
fsys, err := app.NewBackupsFilesystem() fsys, err := app.NewBackupsFilesystem()
if err != nil && app.IsDebug() { if err != nil {
// @todo replace after logs generalization app.Logger().Debug(
log.Println(err) "[Backup cron] Failed to initialize the backup filesystem",
slog.String("error", err.Error()),
)
return return
} }
defer fsys.Close() defer fsys.Close()
files, err := fsys.List(autoPrefix) files, err := fsys.List(autoPrefix)
if err != nil && app.IsDebug() { if err != nil {
// @todo replace after logs generalization app.Logger().Debug(
log.Println(err) "[Backup cron] Failed to list autogenerated backups",
slog.String("error", err.Error()),
)
return return
} }
@ -290,9 +304,12 @@ func (app *BaseApp) initAutobackupHooks() error {
toRemove := files[maxKeep:] toRemove := files[maxKeep:]
for _, f := range toRemove { for _, f := range toRemove {
if err := fsys.Delete(f.Key); err != nil && app.IsDebug() { if err := fsys.Delete(f.Key); err != nil {
// @todo replace after logs generalization app.Logger().Debug(
log.Println(err) "[Backup cron] Failed to remove old autogenerated backup",
slog.String("key", f.Key),
slog.String("error", err.Error()),
)
} }
} }
}) })

View File

@ -25,11 +25,11 @@ func TestCreateBackup(t *testing.T) {
expectedAppNamePrefix := "test_" + strings.Repeat("a", 45) expectedAppNamePrefix := "test_" + strings.Repeat("a", 45)
// test pending error // test pending error
app.Cache().Set(core.CacheKeyActiveBackup, "") app.Store().Set(core.StoreKeyActiveBackup, "")
if err := app.CreateBackup(context.Background(), "test.zip"); err == nil { if err := app.CreateBackup(context.Background(), "test.zip"); err == nil {
t.Fatal("Expected pending error, got nil") t.Fatal("Expected pending error, got nil")
} }
app.Cache().Remove(core.CacheKeyActiveBackup) app.Store().Remove(core.StoreKeyActiveBackup)
// create with auto generated name // create with auto generated name
if err := app.CreateBackup(context.Background(), ""); err != nil { if err := app.CreateBackup(context.Background(), ""); err != nil {
@ -98,11 +98,11 @@ func TestRestoreBackup(t *testing.T) {
} }
// test pending error // test pending error
app.Cache().Set(core.CacheKeyActiveBackup, "") app.Store().Set(core.StoreKeyActiveBackup, "")
if err := app.RestoreBackup(context.Background(), "test"); err == nil { if err := app.RestoreBackup(context.Background(), "test"); err == nil {
t.Fatal("Expected pending error, got nil") t.Fatal("Expected pending error, got nil")
} }
app.Cache().Remove(core.CacheKeyActiveBackup) app.Store().Remove(core.StoreKeyActiveBackup)
// missing backup // missing backup
if err := app.RestoreBackup(context.Background(), "missing"); err == nil { if err := app.RestoreBackup(context.Background(), "missing"); err == nil {

View File

@ -3,8 +3,12 @@ package core
import ( import (
"os" "os"
"testing" "testing"
"time"
"github.com/pocketbase/pocketbase/migrations/logs"
"github.com/pocketbase/pocketbase/tools/logger"
"github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/migrate"
) )
func TestNewBaseApp(t *testing.T) { func TestNewBaseApp(t *testing.T) {
@ -14,7 +18,6 @@ func TestNewBaseApp(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "test_env", EncryptionEnv: "test_env",
IsDebug: true,
}) })
if app.dataDir != testDataDir { if app.dataDir != testDataDir {
@ -25,12 +28,8 @@ func TestNewBaseApp(t *testing.T) {
t.Fatalf("expected encryptionEnv test_env, got %q", app.dataDir) t.Fatalf("expected encryptionEnv test_env, got %q", app.dataDir)
} }
if !app.isDebug { if app.store == nil {
t.Fatalf("expected isDebug true, got %v", app.isDebug) t.Fatal("expected store to be set, got nil")
}
if app.cache == nil {
t.Fatal("expected cache to be set, got nil")
} }
if app.settings == nil { if app.settings == nil {
@ -49,7 +48,6 @@ func TestBaseAppBootstrap(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
defer app.ResetBootstrapState() defer app.ResetBootstrapState()
@ -57,7 +55,6 @@ func TestBaseAppBootstrap(t *testing.T) {
t.Fatal("Didn't expect the application to be bootstrapped.") t.Fatal("Didn't expect the application to be bootstrapped.")
} }
// bootstrap
if err := app.Bootstrap(); err != nil { if err := app.Bootstrap(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -106,6 +103,14 @@ func TestBaseAppBootstrap(t *testing.T) {
t.Fatal("Expected app.settings to be initialized, got nil.") t.Fatal("Expected app.settings to be initialized, got nil.")
} }
if app.logger == nil {
t.Fatal("Expected app.logger to be initialized, got nil.")
}
if _, ok := app.logger.Handler().(*logger.BatchHandler); !ok {
t.Fatal("Expected app.logger handler to be initialized.")
}
// reset // reset
if err := app.ResetBootstrapState(); err != nil { if err := app.ResetBootstrapState(); err != nil {
t.Fatal(err) t.Fatal(err)
@ -127,7 +132,6 @@ func TestBaseAppGetters(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
defer app.ResetBootstrapState() defer app.ResetBootstrapState()
@ -159,16 +163,16 @@ func TestBaseAppGetters(t *testing.T) {
t.Fatalf("Expected app.EncryptionEnv %v, got %v", app.EncryptionEnv(), app.encryptionEnv) t.Fatalf("Expected app.EncryptionEnv %v, got %v", app.EncryptionEnv(), app.encryptionEnv)
} }
if app.isDebug != app.IsDebug() {
t.Fatalf("Expected app.IsDebug %v, got %v", app.IsDebug(), app.isDebug)
}
if app.settings != app.Settings() { if app.settings != app.Settings() {
t.Fatalf("Expected app.Settings %v, got %v", app.Settings(), app.settings) t.Fatalf("Expected app.Settings %v, got %v", app.Settings(), app.settings)
} }
if app.cache != app.Cache() { if app.store != app.Store() {
t.Fatalf("Expected app.Cache %v, got %v", app.Cache(), app.cache) t.Fatalf("Expected app.Store %v, got %v", app.Store(), app.store)
}
if app.logger != app.Logger() {
t.Fatalf("Expected app.Logger %v, got %v", app.Logger(), app.logger)
} }
if app.subscriptionsBroker != app.SubscriptionsBroker() { if app.subscriptionsBroker != app.SubscriptionsBroker() {
@ -187,7 +191,6 @@ func TestBaseAppNewMailClient(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
client1 := app.NewMailClient() client1 := app.NewMailClient()
@ -210,7 +213,6 @@ func TestBaseAppNewFilesystem(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
// local // local
@ -240,7 +242,6 @@ func TestBaseAppNewBackupsFilesystem(t *testing.T) {
app := NewBaseApp(BaseAppConfig{ app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir, DataDir: testDataDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
// local // local
@ -262,3 +263,68 @@ func TestBaseAppNewBackupsFilesystem(t *testing.T) {
t.Fatalf("Expected nil s3 backups filesystem, got %v", s3) t.Fatalf("Expected nil s3 backups filesystem, got %v", s3)
} }
} }
func TestBaseAppLoggerWrites(t *testing.T) {
testDataDir, err := os.MkdirTemp("", "logger_writes")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(testDataDir)
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
})
if err := app.Bootstrap(); err != nil {
t.Fatal(err)
}
// init logs migrations
runner, err := migrate.NewRunner(app.LogsDB(), logs.LogsMigrations)
if err != nil {
t.Fatalf("Logs runner error: %v", err)
}
if _, err := runner.Up(); err != nil {
t.Fatalf("Logs migration execution error: %v", err)
}
// test batch logs writes
{
threshold := 200
for i := 0; i < threshold-1; i++ {
app.Logger().Error("test")
}
if total := totalLogs(app, t); total != 0 {
t.Fatalf("Expected %d logs, got %d", 0, total)
}
// should trigger batch write
app.Logger().Error("test")
// should be added for the next batch write
app.Logger().Error("test")
if total := totalLogs(app, t); total != threshold {
t.Fatalf("Expected %d logs, got %d", threshold, total)
}
// wait for ~3 secs to check the timer trigger
time.Sleep(3200 * time.Millisecond)
if total := totalLogs(app, t); total != threshold+1 {
t.Fatalf("Expected %d logs, got %d", threshold+1, total)
}
}
}
func totalLogs(app App, t *testing.T) int {
var total int
err := app.LogsDao().LogQuery().Select("count(*)").Row(&total)
if err != nil {
t.Fatalf("Failed to fetch total logs: %v", err)
}
return total
}

View File

@ -6,7 +6,6 @@ package daos
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
@ -212,13 +211,7 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error {
} }
} }
if len(errs) > 0 { if len(errs) > 0 {
// @todo after go 1.20+ upgrade consider replacing with errors.Join() return fmt.Errorf("after transaction errors: %w", errors.Join(errs...))
var errsMsg strings.Builder
for _, err := range errs {
errsMsg.WriteString(err.Error())
errsMsg.WriteString("; ")
}
return fmt.Errorf("after transaction errors: %s", errsMsg.String())
} }
return nil return nil

67
daos/log.go Normal file
View File

@ -0,0 +1,67 @@
package daos
import (
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/types"
)
// LogQuery returns a new Log select query.
func (dao *Dao) LogQuery() *dbx.SelectQuery {
return dao.ModelQuery(&models.Log{})
}
// FindLogById finds a single Log entry by its id.
func (dao *Dao) FindLogById(id string) (*models.Log, error) {
model := &models.Log{}
err := dao.LogQuery().
AndWhere(dbx.HashExp{"id": id}).
Limit(1).
One(model)
if err != nil {
return nil, err
}
return model, nil
}
type LogsStatsItem struct {
Total int `db:"total" json:"total"`
Date types.DateTime `db:"date" json:"date"`
}
// LogsStats returns hourly grouped requests logs statistics.
func (dao *Dao) LogsStats(expr dbx.Expression) ([]*LogsStatsItem, error) {
result := []*LogsStatsItem{}
query := dao.LogQuery().
Select("count(id) as total", "strftime('%Y-%m-%d %H:00:00', created) as date").
GroupBy("date")
if expr != nil {
query.AndWhere(expr)
}
err := query.All(&result)
return result, err
}
// DeleteOldLogs delete all requests that are created before createdBefore.
func (dao *Dao) DeleteOldLogs(createdBefore time.Time) error {
formattedDate := createdBefore.UTC().Format(types.DefaultDateLayout)
expr := dbx.NewExp("[[created]] <= {:date}", dbx.Params{"date": formattedDate})
_, err := dao.NonconcurrentDB().Delete((&models.Log{}).TableName(), expr).Execute()
return err
}
// SaveLog upserts the provided Log model.
func (dao *Dao) SaveLog(log *models.Log) error {
return dao.Save(log)
}

View File

@ -11,23 +11,23 @@ import (
"github.com/pocketbase/pocketbase/tools/types" "github.com/pocketbase/pocketbase/tools/types"
) )
func TestRequestQuery(t *testing.T) { func TestLogQuery(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
expected := "SELECT {{_requests}}.* FROM `_requests`" expected := "SELECT {{_logs}}.* FROM `_logs`"
sql := app.Dao().RequestQuery().Build().SQL() sql := app.Dao().LogQuery().Build().SQL()
if sql != expected { if sql != expected {
t.Errorf("Expected sql %s, got %s", expected, sql) t.Errorf("Expected sql %s, got %s", expected, sql)
} }
} }
func TestFindRequestById(t *testing.T) { func TestFindLogById(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
tests.MockRequestLogsData(app) tests.MockLogsData(app)
scenarios := []struct { scenarios := []struct {
id string id string
@ -40,7 +40,7 @@ func TestFindRequestById(t *testing.T) {
} }
for i, scenario := range scenarios { for i, scenario := range scenarios {
admin, err := app.LogsDao().FindRequestById(scenario.id) admin, err := app.LogsDao().FindLogById(scenario.id)
hasErr := err != nil hasErr := err != nil
if hasErr != scenario.expectError { if hasErr != scenario.expectError {
@ -53,17 +53,17 @@ func TestFindRequestById(t *testing.T) {
} }
} }
func TestRequestsStats(t *testing.T) { func TestLogsStats(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
tests.MockRequestLogsData(app) tests.MockLogsData(app)
expected := `[{"total":1,"date":"2022-05-01 10:00:00.000Z"},{"total":1,"date":"2022-05-02 10:00:00.000Z"}]` expected := `[{"total":1,"date":"2022-05-01 10:00:00.000Z"},{"total":1,"date":"2022-05-02 10:00:00.000Z"}]`
now := time.Now().UTC().Format(types.DefaultDateLayout) now := time.Now().UTC().Format(types.DefaultDateLayout)
exp := dbx.NewExp("[[created]] <= {:date}", dbx.Params{"date": now}) exp := dbx.NewExp("[[created]] <= {:date}", dbx.Params{"date": now})
result, err := app.LogsDao().RequestsStats(exp) result, err := app.LogsDao().LogsStats(exp)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -74,20 +74,20 @@ func TestRequestsStats(t *testing.T) {
} }
} }
func TestDeleteOldRequests(t *testing.T) { func TestDeleteOldLogs(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
tests.MockRequestLogsData(app) tests.MockLogsData(app)
scenarios := []struct { scenarios := []struct {
date string date string
expectedTotal int expectedTotal int
}{ }{
{"2022-01-01 10:00:00.000Z", 2}, // no requests to delete before that time {"2022-01-01 10:00:00.000Z", 2}, // no logs to delete before that time
{"2022-05-01 11:00:00.000Z", 1}, // only 1 request should have left {"2022-05-01 11:00:00.000Z", 1}, // only 1 log should have left
{"2022-05-03 11:00:00.000Z", 0}, // no more requests should have left {"2022-05-03 11:00:00.000Z", 0}, // no more logs should have left
{"2022-05-04 11:00:00.000Z", 0}, // no more requests should have left {"2022-05-04 11:00:00.000Z", 0}, // no more logs should have left
} }
for i, scenario := range scenarios { for i, scenario := range scenarios {
@ -96,53 +96,53 @@ func TestDeleteOldRequests(t *testing.T) {
t.Errorf("(%d) Date error %v", i, dateErr) t.Errorf("(%d) Date error %v", i, dateErr)
} }
deleteErr := app.LogsDao().DeleteOldRequests(date) deleteErr := app.LogsDao().DeleteOldLogs(date)
if deleteErr != nil { if deleteErr != nil {
t.Errorf("(%d) Delete error %v", i, deleteErr) t.Errorf("(%d) Delete error %v", i, deleteErr)
} }
// check total remaining requests // check total remaining logs
var total int var total int
countErr := app.LogsDao().RequestQuery().Select("count(*)").Row(&total) countErr := app.LogsDao().LogQuery().Select("count(*)").Row(&total)
if countErr != nil { if countErr != nil {
t.Errorf("(%d) Count error %v", i, countErr) t.Errorf("(%d) Count error %v", i, countErr)
} }
if total != scenario.expectedTotal { if total != scenario.expectedTotal {
t.Errorf("(%d) Expected %d remaining requests, got %d", i, scenario.expectedTotal, total) t.Errorf("(%d) Expected %d remaining logs, got %d", i, scenario.expectedTotal, total)
} }
} }
} }
func TestSaveRequest(t *testing.T) { func TestSaveLog(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
tests.MockRequestLogsData(app) tests.MockLogsData(app)
// create new request // create new log
newRequest := &models.Request{} newLog := &models.Log{}
newRequest.Method = "get" newLog.Level = -4
newRequest.Meta = types.JsonMap{} newLog.Data = types.JsonMap{}
createErr := app.LogsDao().SaveRequest(newRequest) createErr := app.LogsDao().SaveLog(newLog)
if createErr != nil { if createErr != nil {
t.Fatal(createErr) t.Fatal(createErr)
} }
// check if it was really created // check if it was really created
existingRequest, fetchErr := app.LogsDao().FindRequestById(newRequest.Id) existingLog, fetchErr := app.LogsDao().FindLogById(newLog.Id)
if fetchErr != nil { if fetchErr != nil {
t.Fatal(fetchErr) t.Fatal(fetchErr)
} }
existingRequest.Method = "post" existingLog.Level = 4
updateErr := app.LogsDao().SaveRequest(existingRequest) updateErr := app.LogsDao().SaveLog(existingLog)
if updateErr != nil { if updateErr != nil {
t.Fatal(updateErr) t.Fatal(updateErr)
} }
// refresh instance to check if it was really updated // refresh instance to check if it was really updated
existingRequest, _ = app.LogsDao().FindRequestById(existingRequest.Id) existingLog, _ = app.LogsDao().FindLogById(existingLog.Id)
if existingRequest.Method != "post" { if existingLog.Level != 4 {
t.Fatalf("Expected request method to be %s, got %s", "post", existingRequest.Method) t.Fatalf("Expected log level to be %d, got %d", 4, existingLog.Level)
} }
} }

View File

@ -1,70 +0,0 @@
package daos
import (
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/types"
)
// RequestQuery returns a new Request logs select query.
func (dao *Dao) RequestQuery() *dbx.SelectQuery {
return dao.ModelQuery(&models.Request{})
}
// FindRequestById finds a single Request log by its id.
func (dao *Dao) FindRequestById(id string) (*models.Request, error) {
model := &models.Request{}
err := dao.RequestQuery().
AndWhere(dbx.HashExp{"id": id}).
Limit(1).
One(model)
if err != nil {
return nil, err
}
return model, nil
}
type RequestsStatsItem struct {
Total int `db:"total" json:"total"`
Date types.DateTime `db:"date" json:"date"`
}
// RequestsStats returns hourly grouped requests logs statistics.
func (dao *Dao) RequestsStats(expr dbx.Expression) ([]*RequestsStatsItem, error) {
result := []*RequestsStatsItem{}
query := dao.RequestQuery().
Select("count(id) as total", "strftime('%Y-%m-%d %H:00:00', created) as date").
GroupBy("date")
if expr != nil {
query.AndWhere(expr)
}
err := query.All(&result)
return result, err
}
// DeleteOldRequests delete all requests that are created before createdBefore.
func (dao *Dao) DeleteOldRequests(createdBefore time.Time) error {
m := models.Request{}
tableName := m.TableName()
formattedDate := createdBefore.UTC().Format(types.DefaultDateLayout)
expr := dbx.NewExp("[[created]] <= {:date}", dbx.Params{"date": formattedDate})
_, err := dao.NonconcurrentDB().Delete(tableName, expr).Execute()
return err
}
// SaveRequest upserts the provided Request model.
func (dao *Dao) SaveRequest(request *models.Request) error {
return dao.Save(request)
}

View File

@ -3,7 +3,6 @@ package forms
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
@ -78,12 +77,9 @@ func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc[[]*models.
} }
// generic/db failure // generic/db failure
if form.app.IsDebug() {
log.Println("Internal import failure:", importErr)
}
return validation.Errors{"collections": validation.NewError( return validation.Errors{"collections": validation.NewError(
"collections_import_failure", "collections_import_failure",
"Failed to import the collections configuration.", "Failed to import the collections configuration. Raw error:\n"+importErr.Error(),
)} )}
}) })
}, interceptors...) }, interceptors...)

View File

@ -206,7 +206,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
expectError: true, expectError: true,
expectCollectionsCount: totalCollections, expectCollectionsCount: totalCollections,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeDelete": 4, "OnModelBeforeDelete": 3,
}, },
}, },
{ {

View File

@ -128,6 +128,8 @@ func (form *RecordEmailChangeConfirm) Submit(interceptors ...InterceptorFunc[*mo
authRecord.SetEmail(newEmail) authRecord.SetEmail(newEmail)
authRecord.SetVerified(true) authRecord.SetVerified(true)
// @todo consider removing if not necessary anymore
authRecord.RefreshTokenKey() // invalidate old tokens authRecord.RefreshTokenKey() // invalidate old tokens
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error { interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {

View File

@ -54,7 +54,7 @@ func (form *RecordEmailChangeRequest) checkUniqueEmail(value any) error {
v, _ := value.(string) v, _ := value.(string)
if !form.dao.IsRecordValueUnique(form.record.Collection().Id, schema.FieldNameEmail, v) { if !form.dao.IsRecordValueUnique(form.record.Collection().Id, schema.FieldNameEmail, v) {
return validation.NewError("validation_record_email_exists", "User email already exists.") return validation.NewError("validation_record_email_invalid", "User email already exists or it is invalid.")
} }
return nil return nil

View File

@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log" "log/slog"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
@ -200,8 +200,12 @@ func (form *RecordUpsert) extractMultipartFormData(
files, err := rest.FindUploadedFiles(r, fullKey) files, err := rest.FindUploadedFiles(r, fullKey)
if err != nil || len(files) == 0 { if err != nil || len(files) == 0 {
if err != nil && err != http.ErrMissingFile && form.app.IsDebug() { if err != nil && err != http.ErrMissingFile {
log.Printf("%q uploaded file error: %v\n", fullKey, err) form.app.Logger().Debug(
"Uploaded file error",
slog.String("key", fullKey),
slog.String("error", err.Error()),
)
} }
// skip invalid or missing file(s) // skip invalid or missing file(s)
@ -794,8 +798,11 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc[*models.Record]
// //
// for now fail silently to avoid reupload when `form.Submit()` // for now fail silently to avoid reupload when `form.Submit()`
// is called manually (aka. not from an api request)... // is called manually (aka. not from an api request)...
if err := form.processFilesToDelete(); err != nil && form.app.IsDebug() { if err := form.processFilesToDelete(); err != nil {
log.Println(err) form.app.Logger().Debug(
"Failed to delete old files",
slog.String("error", err.Error()),
)
} }
return nil return nil

View File

@ -4,9 +4,12 @@ import (
"os" "os"
"time" "time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos" "github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/settings" "github.com/pocketbase/pocketbase/models/settings"
"github.com/pocketbase/pocketbase/tools/types"
) )
// SettingsUpsert is a [settings.Settings] upsert (create/update) form. // SettingsUpsert is a [settings.Settings] upsert (create/update) form.
@ -58,32 +61,27 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc[*settings.Set
return runInterceptors(form.Settings, func(s *settings.Settings) error { return runInterceptors(form.Settings, func(s *settings.Settings) error {
form.Settings = s form.Settings = s
oldSettings, err := form.app.Settings().Clone()
if err != nil {
return err
}
// eagerly merge the application settings with the form ones
if err := form.app.Settings().Merge(form.Settings); err != nil {
return err
}
// persists settings change // persists settings change
encryptionKey := os.Getenv(form.app.EncryptionEnv()) encryptionKey := os.Getenv(form.app.EncryptionEnv())
if err := form.dao.SaveSettings(form.Settings, encryptionKey); err != nil { if err := form.dao.SaveSettings(form.Settings, encryptionKey); err != nil {
// try to revert app settings
form.app.Settings().Merge(oldSettings)
return err return err
} }
// explicitly trigger old logs deletion // reload app settings
form.app.LogsDao().DeleteOldRequests( if err := form.app.RefreshSettings(); err != nil {
time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays), return err
) }
// try to clear old logs not matching the new settings
createdBefore := time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays).UTC().Format(types.DefaultDateLayout)
expr := dbx.NewExp("[[created]] <= {:date} OR [[level]] < {:level}", dbx.Params{
"date": createdBefore,
"level": form.Settings.Logs.MinLevel,
})
form.app.LogsDao().NonconcurrentDB().Delete((&models.Log{}).TableName(), expr).Execute()
// no logs are allowed -> try to reclaim preserved disk space after the previous delete operation
if form.Settings.Logs.MaxDays == 0 { if form.Settings.Logs.MaxDays == 0 {
// no logs are allowed -> reclaim preserved disk space after the previous delete operation
form.app.LogsDao().Vacuum() form.app.LogsDao().Vacuum()
} }

62
go.mod
View File

@ -1,15 +1,15 @@
module github.com/pocketbase/pocketbase module github.com/pocketbase/pocketbase
go 1.19 go 1.21
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/aws/aws-sdk-go v1.46.6 github.com/aws/aws-sdk-go v1.47.9
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/domodwyer/mailyak/v3 v3.6.2 github.com/domodwyer/mailyak/v3 v3.6.2
github.com/dop251/goja v0.0.0-20230919151941-fc55792775de github.com/dop251/goja v0.0.0-20230919151941-fc55792775de
github.com/dop251/goja_nodejs v0.0.0-20230914102007-198ba9a8b098 github.com/dop251/goja_nodejs v0.0.0-20230914102007-198ba9a8b098
github.com/fatih/color v1.15.0 github.com/fatih/color v1.16.0
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/gabriel-vasile/mimetype v1.4.3 github.com/gabriel-vasile/mimetype v1.4.3
github.com/ganigeorgiev/fexpr v0.3.0 github.com/ganigeorgiev/fexpr v0.3.0
@ -17,40 +17,40 @@ require (
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.2
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.18
github.com/pocketbase/dbx v1.10.1 github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4
github.com/spf13/cast v1.5.1 github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.8.0
gocloud.dev v0.34.0 gocloud.dev v0.34.0
golang.org/x/crypto v0.15.0 golang.org/x/crypto v0.15.0
golang.org/x/net v0.18.0 golang.org/x/net v0.18.0
golang.org/x/oauth2 v0.13.0 golang.org/x/oauth2 v0.14.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0
modernc.org/sqlite v1.26.0 modernc.org/sqlite v1.27.0
) )
require ( require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect github.com/aws/aws-sdk-go-v2 v1.22.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.19.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.23.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.6.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.2 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.42.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.25.1 // indirect
github.com/aws/smithy-go v1.15.0 // indirect github.com/aws/smithy-go v1.16.0 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@ -71,23 +71,23 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/image v0.13.0 // indirect golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.14.0 // indirect golang.org/x/term v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.15.0 // indirect golang.org/x/tools v0.15.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.148.0 // indirect google.golang.org/api v0.150.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.59.0 // indirect google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.28.0 // indirect modernc.org/libc v1.34.2 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect

198
go.sum
View File

@ -1,9 +1,14 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -12,59 +17,53 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= github.com/aws/aws-sdk-go v1.47.9 h1:rarTsos0mA16q+huicGx0e560aYRtOucV5z2Mw23JRY=
github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.47.9/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.46.6 h1:6wFnNC9hETIZLMf6SOTN7IcclrOGwp/n9SLp8Pjt6E8= github.com/aws/aws-sdk-go-v2 v1.22.2 h1:lV0U8fnhAnPz8YcdmZVV60+tr6CakHzqA6P8T46ExJI=
github.com/aws/aws-sdk-go v1.46.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.22.2/go.mod h1:Kd0OJtkW3Q0M0lUWGszapWjEvrXDzRW+D21JNsroB+c=
github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.0 h1:hHgLiIrTRtddC0AKcJr5s7i/hLgcpTt+q/FKxf1Zayk=
github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.0/go.mod h1:w4I/v3NOWgD+qvs1NPEwhd++1h3XPHFaVxasfY6HlYQ=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 h1:Sc82v7tDQ/vdU1WtuSyzZ1I7y/68j//HJ6uozND1IDs= github.com/aws/aws-sdk-go-v2/config v1.23.0 h1:kqzEfGGDIrRJpfJckgwuZfFTbU9NB1jZnRcaO9MpOqE=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14/go.mod h1:9NCTOURS8OpxvoAVHq79LK81/zC78hfRWFn+aL0SPcY= github.com/aws/aws-sdk-go-v2/config v1.23.0/go.mod h1:p7wbxKXXjS1GGQOss7VXOazVMFF9bjUGq85/4wR/fSw=
github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes= github.com/aws/aws-sdk-go-v2/credentials v1.15.2 h1:rKH7khRMxPdD0u3dHecd0Q7NOVw3EUe7AqdkUOkiOGI=
github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= github.com/aws/aws-sdk-go-v2/credentials v1.15.2/go.mod h1:tXM8wmaeAhfC7nZoCxb0FzM/aRaB1m1WQ7x0qlBLq80=
github.com/aws/aws-sdk-go-v2/config v1.19.1 h1:oe3vqcGftyk40icfLymhhhNysAwk0NfiwkDi2GTPMXs= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.3 h1:G5KawTAkyHH6WyKQCdHiW4h3PmAXNJpOgwKg3H7sDRE=
github.com/aws/aws-sdk-go-v2/config v1.19.1/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.3/go.mod h1:hugKmSFnZB+HgNI1sYGT14BUPZkO6alC/e0AWu+0IAQ=
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.13.5 h1:P/xwilRdRLLg1PzfviDq0Zjb74weOoDCrh8J5lRCQAY=
github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.13.5/go.mod h1:9cLHf2IwX6Jyw0KjLVbXly/g6DmzExgUzB1w/AQPGQE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.2 h1:AaQsr5vvGR7rmeSWBtTCcw16tT9r51mWijuCQhzLnq8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.2/go.mod h1:o1IiRn7CWocIFTXJjGKJDOwxv1ibL53NpcvcqGWyRBA=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.90 h1:mtJRt80k1oGw7QQPluAx8AZ6u16MyCA2di/lMhagZ7I= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.2 h1:UZx8SXZ0YtzRiALzYAWcjb9Y9hZUR7MBKaBQ5ouOjPs=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.90/go.mod h1:lYwZTkeMQWPvNU+u7oYArdNhQ8EKiSGU76jVv0w2GH4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.2/go.mod h1:ipuRpcSaklmxR6C39G187TpBAO132gUfleTGccUPs8c=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92 h1:nLA7dGFC6v4P6b+hzqt5GqIGmIuN+jTJzojfdOLXWFE= github.com/aws/aws-sdk-go-v2/internal/ini v1.6.0 h1:hwZB07/beLiCopuRKF0t+dEHmP39iN4YtDh3X5d3hrg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92/go.mod h1:h+ei9z19AhoN+Dac92DwkzfbJ4mFUea92xgl5pKSG0Q= github.com/aws/aws-sdk-go-v2/internal/ini v1.6.0/go.mod h1:rdAuXeHWhI/zkpYcO5n8WCpaIgY9MUxFyBsuqq3kjyA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.2 h1:pyVrNAf7Hwz0u39dLKN5t+n0+K/3rMYKuiOoIum3AsU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.2/go.mod h1:mydrfOb9uiOYCxuCPR8YHQNQyGQwUQ7gPMZGBKbH8NY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.0 h1:CJxo7ZBbaIzmXfV3hjcx36n9V87gJsIUPJflwqEHl3Q=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.0/go.mod h1:yjVfjuY4nD1EW9i387Kau+I6V5cBA5YnC/mWNopjZrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.2 h1:f2LhPofnjcdOQKRtumKjMvIHkfSQ8aH/rwKUDEQ/SB4=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.2/go.mod h1:q+xX0H4OfuWDuBy7y/LDi4v8IBOWuF+vtp8Z6ex+lw4=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 h1:wmGLw2i8ZTlHLw7a9ULGfQbuccw8uIiNr6sol5bFzc8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.2 h1:h7j73yuAVVjic8pqswh+L/7r2IHP43QwRyOu6zcCDDE=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6/go.mod h1:Q0Hq2X/NuL7z8b1Dww8rmOFl+jzusKEcyvkKspwdpyc= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.2/go.mod h1:H07AHdK5LSy8F7EJUQhoxyiCNkePoHj2D8P2yGTWafo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 h1:7R8uRYyXzdD71KWVCL78lJZltah6VVznXBazvKjfH58= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.2 h1:gbIaOzpXixUpoPK+js/bCBK1QBDXM22SigsnzGZio0U=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15/go.mod h1:26SQUPcTNgV1Tapwdt4a1rOsYRsnBsJHLMPoxK2b0d8= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.2/go.mod h1:p+S7RNbdGN8qgHDSg2SCQJ9FeMAmvcETQiVpeGhYnNM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 h1:skaFGzv+3kA+v2BPKhuekeb1Hbb105+44r8ASC+q5SE= github.com/aws/aws-sdk-go-v2/service/s3 v1.42.1 h1:o6MCcX1rJW8Y3g+hvg2xpjF6JR6DftuYhfl3Nc1WV9Q=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38/go.mod h1:epIZoRSSbRIwLPJU5F+OldHhwZPBdpDeQkRdCeY3+00= github.com/aws/aws-sdk-go-v2/service/s3 v1.42.1/go.mod h1:UDtxEWbREX6y4KREapT+jjtjoH0TiVSS6f5nfaY1UaM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k= github.com/aws/aws-sdk-go-v2/service/sso v1.17.1 h1:km+ZNjtLtpXYf42RdaDZnNHm9s7SYAuDGTafy6nd89A=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= github.com/aws/aws-sdk-go-v2/service/sso v1.17.1/go.mod h1:aHBr3pvBSD5MbzOvQtYutyPLLRPbl/y9x86XyJJnUXQ=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 h1:9ulSU5ClouoPIYhDQdg9tpl83d5Yb91PXTKK+17q+ow= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.1 h1:iRFNqZH4a67IqPvK8xxtyQYnyrlsvwmpHOe9r55ggBA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6/go.mod h1:lnc2taBsR9nTlz9meD+lhFZZ9EWY712QHrRflWpTcOA= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.1/go.mod h1:pTy5WM+6sNv2tB24JNKFtn6EvciQ5k40ZJ0pq/Iaxj0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2 h1:Ll5/YVCOzRB+gxPqs2uD0R7/MyATC0w85626glSKmp4= github.com/aws/aws-sdk-go-v2/service/sts v1.25.1 h1:txgVXIXWPXyqdiVn92BV6a/rgtpX31HYdsOYj0sVQQQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2/go.mod h1:Zjfqt7KhQK+PO1bbOsFNzKgaq7TcxzmEoDWN8lM0qzQ= github.com/aws/aws-sdk-go-v2/service/sts v1.25.1/go.mod h1:VAiJiNaoP1L89STFlEMgmHX1bKixY+FaP+TpRFrmyZ4=
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k= github.com/aws/smithy-go v1.16.0 h1:gJZEH/Fqh+RsvlJ1Zt4tVAtV6bKkp3cC+R6FCZMNzik=
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo= github.com/aws/smithy-go v1.16.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg=
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ=
github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -93,9 +92,10 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@ -108,6 +108,7 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyL
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
@ -138,25 +139,27 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ= github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
@ -174,6 +177,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -185,26 +189,17 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA= github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA=
github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/tygoja v0.0.0-20231014201019-43707ad269de h1:7Zg82unjBJq9/pUKf206UWzoGvjtaPwDXlqIAoYMG/U=
github.com/pocketbase/tygoja v0.0.0-20231014201019-43707ad269de/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
github.com/pocketbase/tygoja v0.0.0-20231028131333-2278fd471632 h1:5R+G0Gy05vPxuOVP9Q7Oy4rSIyfcQG5B5FPosnfOTN0=
github.com/pocketbase/tygoja v0.0.0-20231028131333-2278fd471632/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
github.com/pocketbase/tygoja v0.0.0-20231028133025-25a94364de58 h1:1Hu0uwRZrgpQchMEMXz+ZAJxV5l+o06L00svTqeP2RQ=
github.com/pocketbase/tygoja v0.0.0-20231028133025-25a94364de58/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 h1:85kAYIKrKEeau7WgXg8B7Km8etrVavJAyH7XcR5MkFw= github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 h1:85kAYIKrKEeau7WgXg8B7Km8etrVavJAyH7XcR5MkFw=
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI= github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -212,11 +207,12 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -228,6 +224,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
@ -240,21 +237,17 @@ gocloud.dev v0.34.0/go.mod h1:psKOachbnvY3DAOPbsFVmLIErwsbWPUG2H5i65D38vE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -267,23 +260,18 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -298,21 +286,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -324,12 +306,10 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -339,18 +319,14 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= google.golang.org/api v0.150.0 h1:Z9k22qD289SZ8gCJrk4DrWXkNjtfvKAUo/l1ma8eBYE=
google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg=
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@ -359,19 +335,17 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@ -407,22 +381,24 @@ modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= modernc.org/libc v1.34.2 h1:0SVAi/cII7uoNPdzJbDqn4HfxXkp+dGVKVSoJDtMyDM=
modernc.org/libc v1.28.0 h1:kHB6LtDBV8DEAK7aZT1vWvP92abW9fb8cjb1P9UTpUE= modernc.org/libc v1.34.2/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.28.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=

View File

@ -8,8 +8,8 @@ import (
var LogsMigrations migrate.MigrationsList var LogsMigrations migrate.MigrationsList
func init() { func init() {
LogsMigrations.Register(func(db dbx.Builder) (err error) { LogsMigrations.Register(func(db dbx.Builder) error {
_, err = db.NewQuery(` _, err := db.NewQuery(`
CREATE TABLE {{_requests}} ( CREATE TABLE {{_requests}} (
[[id]] TEXT PRIMARY KEY NOT NULL, [[id]] TEXT PRIMARY KEY NOT NULL,
[[url]] TEXT DEFAULT "" NOT NULL, [[url]] TEXT DEFAULT "" NOT NULL,

View File

@ -0,0 +1,58 @@
package logs
import (
"github.com/pocketbase/dbx"
)
func init() {
LogsMigrations.Register(func(db dbx.Builder) error {
if _, err := db.DropTable("_requests").Execute(); err != nil {
return err
}
_, err := db.NewQuery(`
CREATE TABLE {{_logs}} (
[[id]] TEXT PRIMARY KEY DEFAULT ('r'||lower(hex(randomblob(7)))) NOT NULL,
[[level]] INTEGER DEFAULT 0 NOT NULL,
[[message]] TEXT DEFAULT "" NOT NULL,
[[data]] JSON DEFAULT "{}" NOT NULL,
[[created]] TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
[[updated]] TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE INDEX _logs_level_idx on {{_logs}} ([[level]]);
CREATE INDEX _logs_message_idx on {{_logs}} ([[message]]);
CREATE INDEX _logs_data_auth_idx on {{_logs}} (JSON_EXTRACT([[data]], '$.auth'));
CREATE INDEX _logs_created_hour_idx on {{_logs}} (strftime('%Y-%m-%d %H:00:00', [[created]]));
`).Execute()
return err
}, func(db dbx.Builder) error {
if _, err := db.DropTable("_logs").Execute(); err != nil {
return err
}
_, err := db.NewQuery(`
CREATE TABLE {{_requests}} (
[[id]] TEXT PRIMARY KEY NOT NULL,
[[url]] TEXT DEFAULT "" NOT NULL,
[[method]] TEXT DEFAULT "get" NOT NULL,
[[status]] INTEGER DEFAULT 200 NOT NULL,
[[auth]] TEXT DEFAULT "guest" NOT NULL,
[[ip]] TEXT DEFAULT "127.0.0.1" NOT NULL,
[[referer]] TEXT DEFAULT "" NOT NULL,
[[userAgent]] TEXT DEFAULT "" NOT NULL,
[[meta]] JSON DEFAULT "{}" NOT NULL,
[[created]] TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
[[updated]] TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE INDEX _request_status_idx on {{_requests}} ([[status]]);
CREATE INDEX _request_auth_idx on {{_requests}} ([[auth]]);
CREATE INDEX _request_ip_idx on {{_requests}} ([[ip]]);
CREATE INDEX _request_created_hour_idx on {{_requests}} (strftime('%Y-%m-%d %H:00:00', [[created]]));
`).Execute()
return err
})
}

19
models/log.go Normal file
View File

@ -0,0 +1,19 @@
package models
import (
"github.com/pocketbase/pocketbase/tools/types"
)
var _ Model = (*Log)(nil)
type Log struct {
BaseModel
Data types.JsonMap `db:"data" json:"data"`
Message string `db:"message" json:"message"`
Level int `db:"level" json:"level"`
}
func (m *Log) TableName() string {
return "_logs"
}

View File

@ -82,6 +82,7 @@ func New() *Settings {
}, },
Logs: LogsConfig{ Logs: LogsConfig{
MaxDays: 5, MaxDays: 5,
LogIp: true,
}, },
Smtp: SmtpConfig{ Smtp: SmtpConfig{
Enabled: false, Enabled: false,
@ -599,6 +600,8 @@ func (t EmailTemplate) Resolve(
type LogsConfig struct { type LogsConfig struct {
MaxDays int `form:"maxDays" json:"maxDays"` MaxDays int `form:"maxDays" json:"maxDays"`
MinLevel int `form:"minLevel" json:"minLevel"`
LogIp bool `form:"logIp" json:"logIp"`
} }
// Validate makes LogsConfig validatable by implementing [validation.Validatable] interface. // Validate makes LogsConfig validatable by implementing [validation.Validatable] interface.

View File

@ -12,7 +12,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log/slog"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -221,8 +221,13 @@ func (p *plugin) update(withBackup bool) error {
} }
tryToRevertExecChanges := func() { tryToRevertExecChanges := func() {
if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() { if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil {
log.Println(revertErr) p.app.Logger().Debug(
"Failed to revert executable",
slog.String("old", renamedOldExec),
slog.String("new", oldExec),
slog.String("error", revertErr.Error()),
)
} }
} }

View File

@ -5,8 +5,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"log/slog"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -120,8 +120,12 @@ func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
return err return err
}) })
if err != nil && app.IsDebug() { if err != nil {
fmt.Println("[cronAdd] failed to execute cron job " + jobId + ": " + err.Error()) app.Logger().Debug(
"[cronAdd] failed to execute cron job",
slog.String("jobId", jobId),
slog.String("error", err.Error()),
)
} }
}) })
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -429,7 +429,7 @@ declare class DateTime implements types.DateTime {
interface ValidationError extends ozzo_validation.Error{} // merge interface ValidationError extends ozzo_validation.Error{} // merge
/** /**
* ValidationError defines a single formatted data validation error, * ValidationError defines a single formatted data validation error,
* usually used as part of a error response. * usually used as part of an error response.
* *
* ` + "```" + `js * ` + "```" + `js
* new ValidationError("invalid_title", "Title is not valid") * new ValidationError("invalid_title", "Title is not valid")

View File

@ -15,7 +15,7 @@ import (
"github.com/pocketbase/pocketbase/tools/migrate" "github.com/pocketbase/pocketbase/tools/migrate"
) )
const collectionsCacheKey = "migratecmd_collections" const collectionsStoreKey = "migratecmd_collections"
// onCollectionChange handles the automigration snapshot generation on // onCollectionChange handles the automigration snapshot generation on
// collection change event (create/update/delete). // collection change event (create/update/delete).
@ -94,7 +94,7 @@ func (p *plugin) afterCollectionChange() func(*core.ModelEvent) error {
} }
func (p *plugin) updateSingleCachedCollection(new, old *models.Collection) { func (p *plugin) updateSingleCachedCollection(new, old *models.Collection) {
cached, _ := p.app.Cache().Get(collectionsCacheKey).(map[string]*models.Collection) cached, _ := p.app.Store().Get(collectionsStoreKey).(map[string]*models.Collection)
switch { switch {
case new == nil: case new == nil:
@ -103,7 +103,7 @@ func (p *plugin) updateSingleCachedCollection(new, old *models.Collection) {
cached[new.Id] = new cached[new.Id] = new
} }
p.app.Cache().Set(collectionsCacheKey, cached) p.app.Store().Set(collectionsStoreKey, cached)
} }
func (p *plugin) refreshCachedCollections() error { func (p *plugin) refreshCachedCollections() error {
@ -121,19 +121,19 @@ func (p *plugin) refreshCachedCollections() error {
cached[c.Id] = c cached[c.Id] = c
} }
p.app.Cache().Set(collectionsCacheKey, cached) p.app.Store().Set(collectionsStoreKey, cached)
return nil return nil
} }
func (p *plugin) getCachedCollections() (map[string]*models.Collection, error) { func (p *plugin) getCachedCollections() (map[string]*models.Collection, error) {
if !p.app.Cache().Has(collectionsCacheKey) { if !p.app.Store().Has(collectionsStoreKey) {
if err := p.refreshCachedCollections(); err != nil { if err := p.refreshCachedCollections(); err != nil {
return nil, err return nil, err
} }
} }
result, _ := p.app.Cache().Get(collectionsCacheKey).(map[string]*models.Collection) result, _ := p.app.Store().Get(collectionsStoreKey).(map[string]*models.Collection)
return result, nil return result, nil
} }

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/fatih/color"
"github.com/pocketbase/pocketbase/cmd" "github.com/pocketbase/pocketbase/cmd"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/list"
@ -31,7 +30,6 @@ type appWrapper struct {
type PocketBase struct { type PocketBase struct {
*appWrapper *appWrapper
debugFlag bool
dataDirFlag string dataDirFlag string
encryptionEnvFlag string encryptionEnvFlag string
hideStartBanner bool hideStartBanner bool
@ -43,7 +41,6 @@ type PocketBase struct {
// Config is the PocketBase initialization config struct. // Config is the PocketBase initialization config struct.
type Config struct { type Config struct {
// optional default values for the console flags // optional default values for the console flags
DefaultDebug bool
DefaultDataDir string // if not set, it will fallback to "./pb_data" DefaultDataDir string // if not set, it will fallback to "./pb_data"
DefaultEncryptionEnv string DefaultEncryptionEnv string
@ -66,11 +63,7 @@ type Config struct {
// If you want to initialize the application before calling [Start()], // If you want to initialize the application before calling [Start()],
// then you'll have to manually call [Bootstrap()]. // then you'll have to manually call [Bootstrap()].
func New() *PocketBase { func New() *PocketBase {
_, isUsingGoRun := inspectRuntime() return NewWithConfig(Config{})
return NewWithConfig(Config{
DefaultDebug: isUsingGoRun,
})
} }
// NewWithConfig creates a new PocketBase instance with the provided config. // NewWithConfig creates a new PocketBase instance with the provided config.
@ -100,7 +93,6 @@ func NewWithConfig(config Config) *PocketBase {
DisableDefaultCmd: true, DisableDefaultCmd: true,
}, },
}, },
debugFlag: config.DefaultDebug,
dataDirFlag: config.DefaultDataDir, dataDirFlag: config.DefaultDataDir,
encryptionEnvFlag: config.DefaultEncryptionEnv, encryptionEnvFlag: config.DefaultEncryptionEnv,
hideStartBanner: config.HideStartBanner, hideStartBanner: config.HideStartBanner,
@ -114,7 +106,6 @@ func NewWithConfig(config Config) *PocketBase {
pb.appWrapper = &appWrapper{core.NewBaseApp(core.BaseAppConfig{ pb.appWrapper = &appWrapper{core.NewBaseApp(core.BaseAppConfig{
DataDir: pb.dataDirFlag, DataDir: pb.dataDirFlag,
EncryptionEnv: pb.encryptionEnvFlag, EncryptionEnv: pb.encryptionEnvFlag,
IsDebug: pb.debugFlag,
DataMaxOpenConns: config.DataMaxOpenConns, DataMaxOpenConns: config.DataMaxOpenConns,
DataMaxIdleConns: config.DataMaxIdleConns, DataMaxIdleConns: config.DataMaxIdleConns,
LogsMaxOpenConns: config.LogsMaxOpenConns, LogsMaxOpenConns: config.LogsMaxOpenConns,
@ -162,10 +153,7 @@ func (pb *PocketBase) Execute() error {
// execute the root command // execute the root command
go func() { go func() {
if err := pb.RootCmd.Execute(); err != nil { if err := pb.RootCmd.Execute(); err != nil {
// @todo replace with db log once generalized logs are added pb.Logger().Error("rootCmd.Execute error", "error", err)
// and maybe consider reorganizing the code to return os.Exit(1)
// (note may need to update the existing commands to not silence errors)
color.Red(err.Error())
} }
done <- true done <- true
@ -173,9 +161,12 @@ func (pb *PocketBase) Execute() error {
<-done <-done
// trigger app cleanups // trigger cleanups
return pb.OnTerminate().Trigger(&core.TerminateEvent{ return pb.OnTerminate().Trigger(&core.TerminateEvent{
App: pb, App: pb,
}, func(e *core.TerminateEvent) error {
e.App.ResetBootstrapState()
return nil
}) })
} }
@ -196,13 +187,6 @@ func (pb *PocketBase) eagerParseFlags(config *Config) error {
"the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)", "the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)",
) )
pb.RootCmd.PersistentFlags().BoolVar(
&pb.debugFlag,
"debug",
config.DefaultDebug,
"enable debug mode, aka. showing more detailed logs",
)
return pb.RootCmd.ParseFlags(os.Args[1:]) return pb.RootCmd.ParseFlags(os.Args[1:])
} }

View File

@ -47,15 +47,10 @@ func TestNew(t *testing.T) {
if app.EncryptionEnv() != "test_encryption_env" { if app.EncryptionEnv() != "test_encryption_env" {
t.Fatalf("Expected app.DataDir() test_encryption_env, got %q", app.EncryptionEnv()) t.Fatalf("Expected app.DataDir() test_encryption_env, got %q", app.EncryptionEnv())
} }
if app.IsDebug() != true {
t.Fatal("Expected app.IsDebug() true, got false")
}
} }
func TestNewWithConfig(t *testing.T) { func TestNewWithConfig(t *testing.T) {
app := NewWithConfig(Config{ app := NewWithConfig(Config{
DefaultDebug: true,
DefaultDataDir: "test_dir", DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env", DefaultEncryptionEnv: "test_encryption_env",
HideStartBanner: true, HideStartBanner: true,
@ -84,10 +79,6 @@ func TestNewWithConfig(t *testing.T) {
if app.EncryptionEnv() != "test_encryption_env" { if app.EncryptionEnv() != "test_encryption_env" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env", app.EncryptionEnv()) t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env", app.EncryptionEnv())
} }
if app.IsDebug() != true {
t.Fatal("Expected app.IsDebug() true, got false")
}
} }
func TestNewWithConfigAndFlags(t *testing.T) { func TestNewWithConfigAndFlags(t *testing.T) {
@ -109,7 +100,6 @@ func TestNewWithConfigAndFlags(t *testing.T) {
) )
app := NewWithConfig(Config{ app := NewWithConfig(Config{
DefaultDebug: true,
DefaultDataDir: "test_dir", DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env", DefaultEncryptionEnv: "test_encryption_env",
HideStartBanner: true, HideStartBanner: true,
@ -138,10 +128,6 @@ func TestNewWithConfigAndFlags(t *testing.T) {
if app.EncryptionEnv() != "test_encryption_env_flag" { if app.EncryptionEnv() != "test_encryption_env_flag" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env_flag", app.EncryptionEnv()) t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env_flag", app.EncryptionEnv())
} }
if app.IsDebug() != false {
t.Fatal("Expected app.IsDebug() false, got true")
}
} }
func TestSkipBootstrap(t *testing.T) { func TestSkipBootstrap(t *testing.T) {

View File

@ -97,7 +97,6 @@ func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
app := core.NewBaseApp(core.BaseAppConfig{ app := core.NewBaseApp(core.BaseAppConfig{
DataDir: tempDir, DataDir: tempDir,
EncryptionEnv: "pb_test_env", EncryptionEnv: "pb_test_env",
IsDebug: false,
}) })
// load data dir and db connections // load data dir and db connections

Binary file not shown.

Binary file not shown.

View File

@ -1,49 +1,31 @@
package tests package tests
func MockRequestLogsData(app *TestApp) error { func MockLogsData(app *TestApp) error {
_, err := app.LogsDB().NewQuery(` _, err := app.LogsDB().NewQuery(`
delete from {{_requests}}; delete from {{_logs}};
insert into {{_requests}} ( insert into {{_logs}} (
[[id]], [[id]],
[[url]], [[level]],
[[method]], [[message]],
[[status]], [[data]],
[[auth]],
[[userIp]],
[[remoteIp]],
[[referer]],
[[userAgent]],
[[meta]],
[[created]], [[created]],
[[updated]] [[updated]]
) )
values values
( (
"873f2133-9f38-44fb-bf82-c8f53b310d91", "873f2133-9f38-44fb-bf82-c8f53b310d91",
"/test1", 0,
"get", "test_message1",
200, '{"status":200}',
"guest",
"127.0.0.1",
"127.0.0.1",
"",
"",
"{}",
"2022-05-01 10:00:00.123Z", "2022-05-01 10:00:00.123Z",
"2022-05-01 10:00:00.123Z" "2022-05-01 10:00:00.123Z"
), ),
( (
"f2133873-44fb-9f38-bf82-c918f53b310d", "f2133873-44fb-9f38-bf82-c918f53b310d",
"/test2", 8,
"post", "test_message2",
400, '{"status":400}',
"admin",
"127.0.0.1",
"127.0.0.1",
"",
"",
'{"errorDetails":"error_details..."}',
"2022-05-02 10:00:00.123Z", "2022-05-02 10:00:00.123Z",
"2022-05-02 10:00:00.123Z" "2022-05-02 10:00:00.123Z"
); );

View File

@ -0,0 +1,264 @@
package logger
import (
"context"
"log/slog"
"sync"
"github.com/pocketbase/pocketbase/tools/types"
)
var _ slog.Handler = (*BatchHandler)(nil)
// BatchOptions are options for the BatchHandler.
type BatchOptions struct {
// WriteFunc processes the batched logs.
WriteFunc func(ctx context.Context, logs []*Log) error
// BeforeAddFunc is optional function that is invoked every time
// before a new log is added to the batch queue.
//
// Return false to skip adding the log into the batch queue.
BeforeAddFunc func(ctx context.Context, log *Log) bool
// Level reports the minimum level to log.
// Levels with lower levels are discarded.
// If nil, the Handler uses [slog.LevelInfo].
Level slog.Leveler
// BatchSize specifies how many logs to accumulate before calling WriteFunc.
// If not set or 0, fallback to 100 by default.
BatchSize int
}
// NewBatchHandler creates a slog compatible handler that writes JSON
// logs on batches (default to 100), using the given options.
//
// Panics if [BatchOptions.WriteFunc] is not defined.
//
// Example:
//
// l := slog.New(logger.NewBatchHandler(logger.BatchOptions{
// WriteFunc: func(ctx context.Context, logs []*Log) error {
// for _, l := range logs {
// fmt.Println(l.Level, l.Message, l.Data)
// }
// return nil
// }
// }))
// l.Info("Example message", "title", "lorem ipsum")
func NewBatchHandler(options BatchOptions) *BatchHandler {
h := &BatchHandler{
mux: &sync.Mutex{},
options: &options,
}
if h.options.WriteFunc == nil {
panic("options.WriteFunc must be set")
}
if h.options.Level == nil {
h.options.Level = slog.LevelInfo
}
if h.options.BatchSize == 0 {
h.options.BatchSize = 100
}
h.logs = make([]*Log, 0, h.options.BatchSize)
return h
}
// BatchHandler is a slog handler that writes records on batches.
//
// The log records attributes are formatted in JSON.
//
// Requires the [BatchOptions.WriteFunc] option to be defined.
type BatchHandler struct {
mux *sync.Mutex
parent *BatchHandler
options *BatchOptions
group string
attrs []slog.Attr
logs []*Log
}
// Enabled reports whether the handler handles records at the given level.
//
// The handler ignores records whose level is lower.
func (h *BatchHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.options.Level.Level()
}
// WithGroup returns a new BatchHandler that starts a group.
//
// All logger attributes will be resolved under the specified group name.
func (h *BatchHandler) WithGroup(name string) slog.Handler {
if name == "" {
return h
}
return &BatchHandler{
parent: h,
mux: h.mux,
options: h.options,
group: name,
}
}
// WithAttrs returns a new BatchHandler loaded with the specified attributes.
func (h *BatchHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
return &BatchHandler{
parent: h,
mux: h.mux,
options: h.options,
attrs: attrs,
}
}
// Handle formats the slog.Record argument as JSON object and adds it
// to the batch queue.
//
// If the batch queue threshold has been reached, the WriteFunc option
// is invoked with the accumulated logs which in turn will reset the batch queue.
func (h *BatchHandler) Handle(ctx context.Context, r slog.Record) error {
if h.group != "" {
h.mux.Lock()
attrs := make([]any, 0, len(h.attrs)+r.NumAttrs())
for _, a := range h.attrs {
attrs = append(attrs, a)
}
h.mux.Unlock()
r.Attrs(func(a slog.Attr) bool {
attrs = append(attrs, a)
return true
})
r = slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
r.AddAttrs(slog.Group(h.group, attrs...))
} else if len(h.attrs) > 0 {
r = r.Clone()
h.mux.Lock()
r.AddAttrs(h.attrs...)
h.mux.Unlock()
}
if h.parent != nil {
return h.parent.Handle(ctx, r)
}
data := make(map[string]any, r.NumAttrs())
r.Attrs(func(a slog.Attr) bool {
if err := h.resolveAttr(data, a); err != nil {
return false
}
return true
})
log := &Log{
Time: r.Time,
Level: r.Level,
Message: r.Message,
Data: types.JsonMap(data),
}
if h.options.BeforeAddFunc != nil && !h.options.BeforeAddFunc(ctx, log) {
return nil
}
h.mux.Lock()
h.logs = append(h.logs, log)
totalLogs := len(h.logs)
h.mux.Unlock()
if totalLogs >= h.options.BatchSize {
if err := h.WriteAll(ctx); err != nil {
return err
}
}
return nil
}
// SetLevel updates the handler options level to the specified one.
func (h *BatchHandler) SetLevel(level slog.Level) {
h.mux.Lock()
h.options.Level = level
h.mux.Unlock()
}
// WriteAll writes all accumulated Log entries and resets the batch queue.
func (h *BatchHandler) WriteAll(ctx context.Context) error {
if h.parent != nil {
// invoke recursively the parent level handler since the most
// top level one is holding the logs queue.
return h.parent.WriteAll(ctx)
}
h.mux.Lock()
totalLogs := len(h.logs)
// no logs to write
if totalLogs == 0 {
h.mux.Unlock()
return nil
}
// create a copy of the logs slice to prevent blocking during write
logs := make([]*Log, totalLogs)
copy(logs, h.logs)
h.logs = h.logs[:0] // reset
h.mux.Unlock()
return h.options.WriteFunc(ctx, logs)
}
// resolveAttr writes attr into data.
func (h *BatchHandler) resolveAttr(data map[string]any, attr slog.Attr) error {
// ensure that the attr value is resolved before doing anything else
attr.Value = attr.Value.Resolve()
if attr.Equal(slog.Attr{}) {
return nil // ignore empty attrs
}
switch attr.Value.Kind() {
case slog.KindGroup:
attrs := attr.Value.Group()
if len(attrs) == 0 {
return nil // ignore empty groups
}
// create a submap to wrap the resolved group attributes
groupData := make(map[string]any, len(attrs))
for _, subAttr := range attrs {
h.resolveAttr(groupData, subAttr)
}
if len(groupData) > 0 {
data[attr.Key] = groupData
}
default:
v := attr.Value.Any()
if err, ok := v.(error); ok {
data[attr.Key] = err.Error()
} else {
data[attr.Key] = v
}
}
return nil
}

View File

@ -0,0 +1,324 @@
package logger
import (
"context"
"errors"
"fmt"
"log/slog"
"testing"
"time"
)
func TestNewBatchHandlerPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected to panic.")
}
}()
NewBatchHandler(BatchOptions{})
}
func TestNewBatchHandlerDefaults(t *testing.T) {
h := NewBatchHandler(BatchOptions{
WriteFunc: func(ctx context.Context, logs []*Log) error {
return nil
},
})
if h.options.BatchSize != 100 {
t.Fatalf("Expected default BatchSize %d, got %d", 100, h.options.BatchSize)
}
if h.options.Level != slog.LevelInfo {
t.Fatalf("Expected default Level Info, got %v", h.options.Level)
}
if h.options.BeforeAddFunc != nil {
t.Fatal("Expected default BeforeAddFunc to be nil")
}
if h.options.WriteFunc == nil {
t.Fatal("Expected default WriteFunc to be set")
}
if h.group != "" {
t.Fatalf("Expected empty group, got %s", h.group)
}
if len(h.attrs) != 0 {
t.Fatalf("Expected empty attrs, got %v", h.attrs)
}
if len(h.logs) != 0 {
t.Fatalf("Expected empty logs queue, got %v", h.logs)
}
}
func TestBatchHandlerEnabled(t *testing.T) {
h := NewBatchHandler(BatchOptions{
Level: slog.LevelWarn,
WriteFunc: func(ctx context.Context, logs []*Log) error {
return nil
},
})
l := slog.New(h)
scenarios := []struct {
level slog.Level
expected bool
}{
{slog.LevelDebug, false},
{slog.LevelInfo, false},
{slog.LevelWarn, true},
{slog.LevelError, true},
}
for _, s := range scenarios {
t.Run(fmt.Sprintf("Level %v", s.level), func(t *testing.T) {
result := l.Enabled(context.Background(), s.level)
if result != s.expected {
t.Fatalf("Expected %v, got %v", s.expected, result)
}
})
}
}
func TestBatchHandlerSetLevel(t *testing.T) {
h := NewBatchHandler(BatchOptions{
Level: slog.LevelWarn,
WriteFunc: func(ctx context.Context, logs []*Log) error {
return nil
},
})
if h.options.Level != slog.LevelWarn {
t.Fatalf("Expected the initial level to be %d, got %d", slog.LevelWarn, h.options.Level)
}
h.SetLevel(slog.LevelDebug)
if h.options.Level != slog.LevelDebug {
t.Fatalf("Expected the updated level to be %d, got %d", slog.LevelDebug, h.options.Level)
}
}
func TestBatchHandlerWithAttrsAndWithGroup(t *testing.T) {
h0 := NewBatchHandler(BatchOptions{
WriteFunc: func(ctx context.Context, logs []*Log) error {
return nil
},
})
h1 := h0.WithAttrs([]slog.Attr{slog.Int("test1", 1)}).(*BatchHandler)
h2 := h1.WithGroup("h2_group").(*BatchHandler)
h3 := h2.WithAttrs([]slog.Attr{slog.Int("test2", 2)}).(*BatchHandler)
scenarios := []struct {
name string
handler *BatchHandler
expectedParent *BatchHandler
expectedGroup string
expectedAttrs int
}{
{
"h0",
h0,
nil,
"",
0,
},
{
"h1",
h1,
h0,
"",
1,
},
{
"h2",
h2,
h1,
"h2_group",
0,
},
{
"h3",
h3,
h2,
"",
1,
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
if s.handler.group != s.expectedGroup {
t.Fatalf("Expected group %q, got %q", s.expectedGroup, s.handler.group)
}
if s.handler.parent != s.expectedParent {
t.Fatalf("Expected parent %v, got %v", s.expectedParent, s.handler.parent)
}
if totalAttrs := len(s.handler.attrs); totalAttrs != s.expectedAttrs {
t.Fatalf("Expected %d attrs, got %d", s.expectedAttrs, totalAttrs)
}
})
}
}
func TestBatchHandlerHandle(t *testing.T) {
ctx := context.Background()
beforeLogs := []*Log{}
writeLogs := []*Log{}
h := NewBatchHandler(BatchOptions{
BatchSize: 3,
BeforeAddFunc: func(_ context.Context, log *Log) bool {
beforeLogs = append(beforeLogs, log)
if log.Message == "test2" {
return false // skip test2 log
}
return true
},
WriteFunc: func(_ context.Context, logs []*Log) error {
writeLogs = logs
return nil
},
})
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test1", 0))
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test2", 0))
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test3", 0))
// no batch write
{
checkLogMessages([]string{"test1", "test2", "test3"}, beforeLogs, t)
checkLogMessages([]string{"test1", "test3"}, h.logs, t)
// should be empty because no batch write has happened yet
if totalWriteLogs := len(writeLogs); totalWriteLogs != 0 {
t.Fatalf("Expected %d writeLogs, got %d", 0, totalWriteLogs)
}
}
// add one more log to trigger the batch write
{
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test4", 0))
// should be empty after the batch write
checkLogMessages([]string{}, h.logs, t)
checkLogMessages([]string{"test1", "test3", "test4"}, writeLogs, t)
}
}
func TestBatchHandlerWriteAll(t *testing.T) {
ctx := context.Background()
beforeLogs := []*Log{}
writeLogs := []*Log{}
h := NewBatchHandler(BatchOptions{
BatchSize: 3,
BeforeAddFunc: func(_ context.Context, log *Log) bool {
beforeLogs = append(beforeLogs, log)
return true
},
WriteFunc: func(_ context.Context, logs []*Log) error {
writeLogs = logs
return nil
},
})
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test1", 0))
h.Handle(ctx, slog.NewRecord(time.Now(), slog.LevelInfo, "test2", 0))
checkLogMessages([]string{"test1", "test2"}, beforeLogs, t)
checkLogMessages([]string{"test1", "test2"}, h.logs, t)
checkLogMessages([]string{}, writeLogs, t) // empty because the batch size hasn't been reached
// force trigger the batch write
h.WriteAll(ctx)
checkLogMessages([]string{"test1", "test2"}, beforeLogs, t)
checkLogMessages([]string{}, h.logs, t) // reset
checkLogMessages([]string{"test1", "test2"}, writeLogs, t)
}
func TestBatchHandlerAttrsFormat(t *testing.T) {
ctx := context.Background()
beforeLogs := []*Log{}
h0 := NewBatchHandler(BatchOptions{
BeforeAddFunc: func(_ context.Context, log *Log) bool {
beforeLogs = append(beforeLogs, log)
return true
},
WriteFunc: func(_ context.Context, logs []*Log) error {
return nil
},
})
h1 := h0.WithAttrs([]slog.Attr{slog.Int("a", 1), slog.String("b", "123")})
h2 := h1.WithGroup("sub").WithAttrs([]slog.Attr{
slog.Int("c", 3),
slog.Any("d", map[string]any{"d.1": 1}),
slog.Any("e", errors.New("example error")),
})
record := slog.NewRecord(time.Now(), slog.LevelInfo, "hello", 0)
record.AddAttrs(slog.String("name", "test"))
h0.Handle(ctx, record)
h1.Handle(ctx, record)
h2.Handle(ctx, record)
expected := []string{
`{"name":"test"}`,
`{"a":1,"b":"123","name":"test"}`,
`{"a":1,"b":"123","sub":{"c":3,"d":{"d.1":1},"e":"example error","name":"test"}}`,
}
if len(beforeLogs) != len(expected) {
t.Fatalf("Expected %d logs, got %d", len(beforeLogs), len(expected))
}
for i, data := range expected {
t.Run(fmt.Sprintf("log handler %d", i), func(t *testing.T) {
log := beforeLogs[i]
raw, _ := log.Data.MarshalJSON()
if string(raw) != data {
t.Fatalf("Expected \n%s \ngot \n%s", data, raw)
}
})
}
}
func checkLogMessages(expected []string, logs []*Log, t *testing.T) {
if len(logs) != len(expected) {
t.Fatalf("Expected %d batched logs, got %d (expected: %v)", len(expected), len(logs), expected)
}
for _, message := range expected {
exists := false
for _, l := range logs {
if l.Message == message {
exists = true
continue
}
}
if !exists {
t.Fatalf("Missing %q log message", message)
}
}
}

17
tools/logger/log.go Normal file
View File

@ -0,0 +1,17 @@
package logger
import (
"log/slog"
"time"
"github.com/pocketbase/pocketbase/tools/types"
)
// Log is similar to [slog.Record] bit contains the log attributes as
// preformatted JSON map.
type Log struct {
Time time.Time
Message string
Level slog.Level
Data types.JsonMap
}

View File

@ -1,7 +1,7 @@
package osutils package osutils
import ( import (
"log" "errors"
"os" "os"
"path/filepath" "path/filepath"
@ -65,9 +65,8 @@ func MoveDirContent(src string, dest string, rootExclude ...string) error {
if err := os.Rename(old, new); err != nil { if err := os.Rename(old, new); err != nil {
if errs := tryRollback(); len(errs) > 0 { if errs := tryRollback(); len(errs) > 0 {
// currently just log the rollback errors errs = append(errs, err)
// in the future we may require go 1.20+ to use errors.Join() err = errors.Join(errs...)
log.Println(errs)
} }
return err return err

View File

@ -22,8 +22,8 @@ func FireAndForget(f func(), wg ...*sync.WaitGroup) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Printf("RECOVERED FROM PANIC: %v", err) log.Printf("RECOVERED FROM PANIC (safe to ignore): %v", err)
log.Printf("%s\n", string(debug.Stack())) log.Println(string(debug.Stack()))
} }
}() }()

View File

@ -13,7 +13,7 @@ import (
) )
func TestFilterDataBuildExpr(t *testing.T) { func TestFilterDataBuildExpr(t *testing.T) {
resolver := search.NewSimpleFieldResolver("test1", "test2", "test3", `^test4.\w+$`) resolver := search.NewSimpleFieldResolver("test1", "test2", "test3", `^test4_\w+$`)
scenarios := []struct { scenarios := []struct {
name string name string
@ -96,35 +96,35 @@ func TestFilterDataBuildExpr(t *testing.T) {
{ {
"macros", "macros",
` `
test4.1 > @now && test4_1 > @now &&
test4.2 > @second && test4_2 > @second &&
test4.3 > @minute && test4_3 > @minute &&
test4.4 > @hour && test4_4 > @hour &&
test4.5 > @day && test4_5 > @day &&
test4.6 > @year && test4_6 > @year &&
test4.7 > @month && test4_7 > @month &&
test4.9 > @weekday && test4_9 > @weekday &&
test4.9 > @todayStart && test4_9 > @todayStart &&
test4.10 > @todayEnd && test4_10 > @todayEnd &&
test4.11 > @monthStart && test4_11 > @monthStart &&
test4.12 > @monthEnd && test4_12 > @monthEnd &&
test4.13 > @yearStart && test4_13 > @yearStart &&
test4.14 > @yearEnd test4_14 > @yearEnd
`, `,
false, false,
"([[test4.1]] > {:TEST} AND [[test4.2]] > {:TEST} AND [[test4.3]] > {:TEST} AND [[test4.4]] > {:TEST} AND [[test4.5]] > {:TEST} AND [[test4.6]] > {:TEST} AND [[test4.7]] > {:TEST} AND [[test4.9]] > {:TEST} AND [[test4.9]] > {:TEST} AND [[test4.10]] > {:TEST} AND [[test4.11]] > {:TEST} AND [[test4.12]] > {:TEST} AND [[test4.13]] > {:TEST} AND [[test4.14]] > {:TEST})", "([[test4_1]] > {:TEST} AND [[test4_2]] > {:TEST} AND [[test4_3]] > {:TEST} AND [[test4_4]] > {:TEST} AND [[test4_5]] > {:TEST} AND [[test4_6]] > {:TEST} AND [[test4_7]] > {:TEST} AND [[test4_9]] > {:TEST} AND [[test4_9]] > {:TEST} AND [[test4_10]] > {:TEST} AND [[test4_11]] > {:TEST} AND [[test4_12]] > {:TEST} AND [[test4_13]] > {:TEST} AND [[test4_14]] > {:TEST})",
}, },
{ {
"complex expression", "complex expression",
"((test1 > 1) || (test2 != 2)) && test3 ~ '%%example' && test4.sub = null", "((test1 > 1) || (test2 != 2)) && test3 ~ '%%example' && test4_sub = null",
false, false,
"(([[test1]] > {:TEST} OR [[test2]] != {:TEST}) AND [[test3]] LIKE {:TEST} ESCAPE '\\' AND ([[test4.sub]] = '' OR [[test4.sub]] IS NULL))", "(([[test1]] > {:TEST} OR [[test2]] != {:TEST}) AND [[test3]] LIKE {:TEST} ESCAPE '\\' AND ([[test4_sub]] = '' OR [[test4_sub]] IS NULL))",
}, },
{ {
"combination of special literals (null, true, false)", "combination of special literals (null, true, false)",
"test1=true && test2 != false && null = test3 || null != test4.sub", "test1=true && test2 != false && null = test3 || null != test4_sub",
false, false,
"([[test1]] = 1 AND [[test2]] != 0 AND ('' = [[test3]] OR [[test3]] IS NULL) OR ('' != [[test4.sub]] AND [[test4.sub]] IS NOT NULL))", "([[test1]] = 1 AND [[test2]] != 0 AND ('' = [[test3]] OR [[test3]] IS NULL) OR ('' != [[test4_sub]] AND [[test4_sub]] IS NOT NULL))",
}, },
{ {
"all operators", "all operators",

View File

@ -2,6 +2,8 @@ package search
import ( import (
"fmt" "fmt"
"strconv"
"strings"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/tools/inflector" "github.com/pocketbase/pocketbase/tools/inflector"
@ -73,7 +75,34 @@ func (r *SimpleFieldResolver) Resolve(field string) (*ResolverResult, error) {
return nil, fmt.Errorf("Failed to resolve field %q.", field) return nil, fmt.Errorf("Failed to resolve field %q.", field)
} }
parts := strings.Split(field, ".")
// single regular field
if len(parts) == 1 {
return &ResolverResult{ return &ResolverResult{
Identifier: "[[" + inflector.Columnify(field) + "]]", Identifier: "[[" + inflector.Columnify(parts[0]) + "]]",
}, nil
}
// treat as json path
var jsonPath strings.Builder
jsonPath.WriteString("$")
for _, part := range parts[1:] {
if _, err := strconv.Atoi(part); err == nil {
jsonPath.WriteString("[")
jsonPath.WriteString(inflector.Columnify(part))
jsonPath.WriteString("]")
} else {
jsonPath.WriteString(".")
jsonPath.WriteString(inflector.Columnify(part))
}
}
return &ResolverResult{
Identifier: fmt.Sprintf(
"JSON_EXTRACT([[%s]], '%s')",
inflector.Columnify(parts[0]),
jsonPath.String(),
),
}, nil }, nil
} }

View File

@ -43,7 +43,7 @@ func TestSimpleFieldResolverUpdateQuery(t *testing.T) {
} }
func TestSimpleFieldResolverResolve(t *testing.T) { func TestSimpleFieldResolverResolve(t *testing.T) {
r := search.NewSimpleFieldResolver("test", `^test_regex\d+$`, "Test columnify!") r := search.NewSimpleFieldResolver("test", `^test_regex\d+$`, "Test columnify!", "data.test")
scenarios := []struct { scenarios := []struct {
fieldName string fieldName string
@ -58,6 +58,7 @@ func TestSimpleFieldResolverResolve(t *testing.T) {
{"test_regex", true, ""}, {"test_regex", true, ""},
{"test_regex1", false, "[[test_regex1]]"}, {"test_regex1", false, "[[test_regex1]]"},
{"Test columnify!", false, "[[Testcolumnify]]"}, {"Test columnify!", false, "[[Testcolumnify]]"},
{"data.test", false, "JSON_EXTRACT([[data]], '$.test')"},
} }
for i, s := range scenarios { for i, s := range scenarios {

View File

@ -4,8 +4,8 @@ import "sync"
// Store defines a concurrent safe in memory key-value data store. // Store defines a concurrent safe in memory key-value data store.
type Store[T any] struct { type Store[T any] struct {
mux sync.RWMutex
data map[string]T data map[string]T
mux sync.RWMutex
} }
// New creates a new Store[T] instance with a shallow copy of the provided data (if any). // New creates a new Store[T] instance with a shallow copy of the provided data (if any).

View File

@ -9,4 +9,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs/"
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk" PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk" PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases" PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
PB_VERSION = "v0.20.0-rc3" PB_VERSION = "v0.20.0-rc4"

View File

@ -1,4 +1,4 @@
import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as a,m as ae,x as U,P as ve,Q as je,k as Ae,R as Be,n as Oe,t as W,a as V,o as u,d as ne,C as Fe,p as Qe,r as L,u as Ne,N as He}from"./index-7d33ef4c.js";import{S as Ke}from"./SdkTabs-f0532e58.js";import{F as qe}from"./FieldsQueryParam-f6b769d1.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l,o){const s=n.slice();return s[5]=l[o],s}function $e(n,l){let o,s=l[5].code+"",_,f,i,h;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),f=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,f),i||(h=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&U(_,s),C&6&&L(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,h()}}}function Me(n,l){let o,s,_,f;return s=new He({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),se(s.$$.fragment),_=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(i,h){d(i,o,h),ae(s,o,null),a(o,_),f=!0},p(i,h){l=i;const m={};h&4&&(m.content=l[5].body),s.$set(m),(!f||h&6)&&L(o,"active",l[1]===l[5].code)},i(i){f||(W(s.$$.fragment,i),f=!0)},o(i){V(s.$$.fragment,i),f=!1},d(i){i&&u(o),ne(s)}}}function ze(n){var be,ke;let l,o,s=n[0].name+"",_,f,i,h,m,v,C,H=n[0].name+"",E,ie,I,P,J,j,Y,$,K,ce,q,A,re,R,z=n[0].name+"",X,de,Z,B,x,M,ee,ue,te,T,le,O,oe,S,F,g=[],he=new Map,me,Q,b=[],fe=new Map,y;P=new Ke({props:{js:` import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as a,m as ae,x as U,P as ve,Q as je,k as Ae,R as Be,n as Oe,t as W,a as V,o as u,d as ne,C as Fe,p as Qe,r as L,u as Ne,N as He}from"./index-6c8f1731.js";import{S as Ke}from"./SdkTabs-821a5c7e.js";import{F as qe}from"./FieldsQueryParam-2d478986.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l,o){const s=n.slice();return s[5]=l[o],s}function $e(n,l){let o,s=l[5].code+"",_,f,i,h;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),f=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,f),i||(h=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&U(_,s),C&6&&L(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,h()}}}function Me(n,l){let o,s,_,f;return s=new He({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),se(s.$$.fragment),_=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(i,h){d(i,o,h),ae(s,o,null),a(o,_),f=!0},p(i,h){l=i;const m={};h&4&&(m.content=l[5].body),s.$set(m),(!f||h&6)&&L(o,"active",l[1]===l[5].code)},i(i){f||(W(s.$$.fragment,i),f=!0)},o(i){V(s.$$.fragment,i),f=!1},d(i){i&&u(o),ne(s)}}}function ze(n){var be,ke;let l,o,s=n[0].name+"",_,f,i,h,m,v,C,H=n[0].name+"",E,ie,I,P,J,j,Y,$,K,ce,q,A,re,R,z=n[0].name+"",X,de,Z,B,x,M,ee,ue,te,T,le,O,oe,S,F,g=[],he=new Map,me,Q,b=[],fe=new Map,y;P=new Ke({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}'); const pb = new PocketBase('${n[3]}');

View File

@ -1,4 +1,4 @@
import{S as je,i as xe,s as Je,N as Ue,O as J,e as s,w as k,b as p,c as K,f as b,g as d,h as o,m as I,x as de,P as Ee,Q as Ke,k as Ie,R as We,n as Ge,t as N,a as V,o as u,d as W,C as Le,p as Xe,r as G,u as Ye}from"./index-7d33ef4c.js";import{S as Ze}from"./SdkTabs-f0532e58.js";import{F as et}from"./FieldsQueryParam-f6b769d1.js";function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l,a){const n=r.slice();return n[5]=l[a],n}function ze(r,l){let a,n=l[5].code+"",m,_,i,h;function g(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(v,w){d(v,a,w),o(a,m),o(a,_),i||(h=Ye(a,"click",g),i=!0)},p(v,w){l=v,w&4&&n!==(n=l[5].code+"")&&de(m,n),w&6&&G(a,"active",l[1]===l[5].code)},d(v){v&&u(a),i=!1,h()}}}function Qe(r,l){let a,n,m,_;return n=new Ue({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),K(n.$$.fragment),m=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(i,h){d(i,a,h),I(n,a,null),o(a,m),_=!0},p(i,h){l=i;const g={};h&4&&(g.content=l[5].body),n.$set(g),(!_||h&6)&&G(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),W(n)}}}function tt(r){var De,Fe;let l,a,n=r[0].name+"",m,_,i,h,g,v,w,M,X,S,z,ue,Q,q,pe,Y,U=r[0].name+"",Z,he,fe,j,ee,D,te,T,oe,be,F,C,le,me,ae,_e,f,ke,R,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Pe,A,ie,O,ce,P,H,y=[],Re=new Map,Ae,E,$=[],Be=new Map,B;v=new Ze({props:{js:` import{S as je,i as xe,s as Je,N as Ue,O as J,e as s,w as k,b as p,c as K,f as b,g as d,h as o,m as I,x as de,P as Ee,Q as Ke,k as Ie,R as We,n as Ge,t as N,a as V,o as u,d as W,C as Le,p as Xe,r as G,u as Ye}from"./index-6c8f1731.js";import{S as Ze}from"./SdkTabs-821a5c7e.js";import{F as et}from"./FieldsQueryParam-2d478986.js";function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l,a){const n=r.slice();return n[5]=l[a],n}function ze(r,l){let a,n=l[5].code+"",m,_,i,h;function g(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(v,w){d(v,a,w),o(a,m),o(a,_),i||(h=Ye(a,"click",g),i=!0)},p(v,w){l=v,w&4&&n!==(n=l[5].code+"")&&de(m,n),w&6&&G(a,"active",l[1]===l[5].code)},d(v){v&&u(a),i=!1,h()}}}function Qe(r,l){let a,n,m,_;return n=new Ue({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),K(n.$$.fragment),m=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(i,h){d(i,a,h),I(n,a,null),o(a,m),_=!0},p(i,h){l=i;const g={};h&4&&(g.content=l[5].body),n.$set(g),(!_||h&6)&&G(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),W(n)}}}function tt(r){var De,Fe;let l,a,n=r[0].name+"",m,_,i,h,g,v,w,M,X,S,z,ue,Q,q,pe,Y,U=r[0].name+"",Z,he,fe,j,ee,D,te,T,oe,be,F,C,le,me,ae,_e,f,ke,R,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Pe,A,ie,O,ce,P,H,y=[],Re=new Map,Ae,E,$=[],Be=new Map,B;v=new Ze({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${r[3]}'); const pb = new PocketBase('${r[3]}');

View File

@ -1,4 +1,4 @@
import{S as Ee,i as Je,s as Ne,N as Le,O as z,e as o,w as k,b as h,c as I,f as p,g as r,h as a,m as K,x as pe,P as Ue,Q as Qe,k as xe,R as ze,n as Ie,t as L,a as E,o as c,d as G,C as Be,p as Ke,r as X,u as Ge}from"./index-7d33ef4c.js";import{S as Xe}from"./SdkTabs-f0532e58.js";import{F as Ye}from"./FieldsQueryParam-f6b769d1.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function je(s,l){let n,i=l[5].code+"",f,g,d,b;function _(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=k(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(b=Ge(n,"click",_),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,b()}}}function Ve(s,l){let n,i,f,g;return i=new Le({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),I(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,b){r(d,n,b),K(i,n,null),a(n,f),g=!0},p(d,b){l=d;const _={};b&4&&(_.content=l[5].body),i.$set(_),(!g||b&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(L(i.$$.fragment,d),g=!0)},o(d){E(i.$$.fragment,d),g=!1},d(d){d&&c(n),G(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,b,_,v,O,P,Y,A,J,be,N,R,me,Z,Q=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,S,oe,ge,B,y,se,ke,ie,_e,m,ve,C,we,$e,Oe,re,Ae,ce,Se,ye,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],De=new Map,Pe,j,w=[],Re=new Map,D;v=new Xe({props:{js:` import{S as Ee,i as Je,s as Ne,N as Le,O as z,e as o,w as k,b as h,c as I,f as p,g as r,h as a,m as K,x as pe,P as Ue,Q as Qe,k as xe,R as ze,n as Ie,t as L,a as E,o as c,d as G,C as Be,p as Ke,r as X,u as Ge}from"./index-6c8f1731.js";import{S as Xe}from"./SdkTabs-821a5c7e.js";import{F as Ye}from"./FieldsQueryParam-2d478986.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function je(s,l){let n,i=l[5].code+"",f,g,d,b;function _(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=k(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(b=Ge(n,"click",_),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,b()}}}function Ve(s,l){let n,i,f,g;return i=new Le({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),I(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,b){r(d,n,b),K(i,n,null),a(n,f),g=!0},p(d,b){l=d;const _={};b&4&&(_.content=l[5].body),i.$set(_),(!g||b&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(L(i.$$.fragment,d),g=!0)},o(d){E(i.$$.fragment,d),g=!1},d(d){d&&c(n),G(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,b,_,v,O,P,Y,A,J,be,N,R,me,Z,Q=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,S,oe,ge,B,y,se,ke,ie,_e,m,ve,C,we,$e,Oe,re,Ae,ce,Se,ye,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],De=new Map,Pe,j,w=[],Re=new Map,D;v=new Xe({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${s[3]}'); const pb = new PocketBase('${s[3]}');

View File

@ -1,4 +1,4 @@
import{S as we,i as ye,s as $e,N as ve,O as ot,e as n,w as p,b as d,c as nt,f as m,g as r,h as e,m as st,x as Dt,P as pe,Q as Pe,k as Re,R as Ce,n as Oe,t as Z,a as x,o as c,d as it,C as fe,p as Ae,r as rt,u as Te}from"./index-7d33ef4c.js";import{S as Ue}from"./SdkTabs-f0532e58.js";import{F as Me}from"./FieldsQueryParam-f6b769d1.js";function he(s,l,a){const i=s.slice();return i[8]=l[a],i}function be(s,l,a){const i=s.slice();return i[8]=l[a],i}function De(s){let l;return{c(){l=p("email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Ee(s){let l;return{c(){l=p("username")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function We(s){let l;return{c(){l=p("username/email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function me(s){let l;return{c(){l=n("strong"),l.textContent="username"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function _e(s){let l;return{c(){l=p("or")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function ke(s){let l;return{c(){l=n("strong"),l.textContent="email"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function ge(s,l){let a,i=l[8].code+"",g,b,f,u;function _(){return l[7](l[8])}return{key:s,first:null,c(){a=n("button"),g=p(i),b=d(),m(a,"class","tab-item"),rt(a,"active",l[3]===l[8].code),this.first=a},m(R,C){r(R,a,C),e(a,g),e(a,b),f||(u=Te(a,"click",_),f=!0)},p(R,C){l=R,C&16&&i!==(i=l[8].code+"")&&Dt(g,i),C&24&&rt(a,"active",l[3]===l[8].code)},d(R){R&&c(a),f=!1,u()}}}function Se(s,l){let a,i,g,b;return i=new ve({props:{content:l[8].body}}),{key:s,first:null,c(){a=n("div"),nt(i.$$.fragment),g=d(),m(a,"class","tab-item"),rt(a,"active",l[3]===l[8].code),this.first=a},m(f,u){r(f,a,u),st(i,a,null),e(a,g),b=!0},p(f,u){l=f;const _={};u&16&&(_.content=l[8].body),i.$set(_),(!b||u&24)&&rt(a,"active",l[3]===l[8].code)},i(f){b||(Z(i.$$.fragment,f),b=!0)},o(f){x(i.$$.fragment,f),b=!1},d(f){f&&c(a),it(i)}}}function Le(s){var re,ce;let l,a,i=s[0].name+"",g,b,f,u,_,R,C,O,B,Et,ct,T,dt,N,ut,U,tt,Wt,et,I,Lt,pt,lt=s[0].name+"",ft,Bt,ht,V,bt,M,mt,qt,Q,D,_t,Ft,kt,Ht,$,Yt,gt,St,vt,Nt,wt,yt,j,$t,E,Pt,It,J,W,Rt,Vt,Ct,Qt,k,jt,q,Jt,Kt,zt,Ot,Gt,At,Xt,Zt,xt,Tt,te,ee,F,Ut,K,Mt,L,z,A=[],le=new Map,ae,G,S=[],oe=new Map,H;function ne(t,o){if(t[1]&&t[2])return We;if(t[1])return Ee;if(t[2])return De}let Y=ne(s),P=Y&&Y(s);T=new Ue({props:{js:` import{S as we,i as ye,s as $e,N as ve,O as ot,e as n,w as p,b as d,c as nt,f as m,g as r,h as e,m as st,x as Dt,P as pe,Q as Pe,k as Re,R as Ce,n as Oe,t as Z,a as x,o as c,d as it,C as fe,p as Ae,r as rt,u as Te}from"./index-6c8f1731.js";import{S as Ue}from"./SdkTabs-821a5c7e.js";import{F as Me}from"./FieldsQueryParam-2d478986.js";function he(s,l,a){const i=s.slice();return i[8]=l[a],i}function be(s,l,a){const i=s.slice();return i[8]=l[a],i}function De(s){let l;return{c(){l=p("email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Ee(s){let l;return{c(){l=p("username")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function We(s){let l;return{c(){l=p("username/email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function me(s){let l;return{c(){l=n("strong"),l.textContent="username"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function _e(s){let l;return{c(){l=p("or")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function ke(s){let l;return{c(){l=n("strong"),l.textContent="email"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function ge(s,l){let a,i=l[8].code+"",g,b,f,u;function _(){return l[7](l[8])}return{key:s,first:null,c(){a=n("button"),g=p(i),b=d(),m(a,"class","tab-item"),rt(a,"active",l[3]===l[8].code),this.first=a},m(R,C){r(R,a,C),e(a,g),e(a,b),f||(u=Te(a,"click",_),f=!0)},p(R,C){l=R,C&16&&i!==(i=l[8].code+"")&&Dt(g,i),C&24&&rt(a,"active",l[3]===l[8].code)},d(R){R&&c(a),f=!1,u()}}}function Se(s,l){let a,i,g,b;return i=new ve({props:{content:l[8].body}}),{key:s,first:null,c(){a=n("div"),nt(i.$$.fragment),g=d(),m(a,"class","tab-item"),rt(a,"active",l[3]===l[8].code),this.first=a},m(f,u){r(f,a,u),st(i,a,null),e(a,g),b=!0},p(f,u){l=f;const _={};u&16&&(_.content=l[8].body),i.$set(_),(!b||u&24)&&rt(a,"active",l[3]===l[8].code)},i(f){b||(Z(i.$$.fragment,f),b=!0)},o(f){x(i.$$.fragment,f),b=!1},d(f){f&&c(a),it(i)}}}function Le(s){var re,ce;let l,a,i=s[0].name+"",g,b,f,u,_,R,C,O,B,Et,ct,T,dt,N,ut,U,tt,Wt,et,I,Lt,pt,lt=s[0].name+"",ft,Bt,ht,V,bt,M,mt,qt,Q,D,_t,Ft,kt,Ht,$,Yt,gt,St,vt,Nt,wt,yt,j,$t,E,Pt,It,J,W,Rt,Vt,Ct,Qt,k,jt,q,Jt,Kt,zt,Ot,Gt,At,Xt,Zt,xt,Tt,te,ee,F,Ut,K,Mt,L,z,A=[],le=new Map,ae,G,S=[],oe=new Map,H;function ne(t,o){if(t[1]&&t[2])return We;if(t[1])return Ee;if(t[2])return De}let Y=ne(s),P=Y&&Y(s);T=new Ue({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${s[6]}'); const pb = new PocketBase('${s[6]}');

14
ui/dist/assets/CodeEditor-c4a370cf.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import{S as $e,i as Pe,s as Se,O as Y,e as r,w as v,b as k,c as ge,f as b,g as d,h as o,m as ve,x as j,P as ue,Q as we,k as Oe,R as Re,n as Te,t as x,a as ee,o as m,d as Ce,C as ye,p as Ee,r as H,u as Be,N as qe}from"./index-7d33ef4c.js";import{S as Ae}from"./SdkTabs-f0532e58.js";function be(n,l,s){const a=n.slice();return a[5]=l[s],a}function _e(n,l,s){const a=n.slice();return a[5]=l[s],a}function he(n,l){let s,a=l[5].code+"",_,u,i,p;function f(){return l[4](l[5])}return{key:n,first:null,c(){s=r("button"),_=v(a),u=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(C,$){d(C,s,$),o(s,_),o(s,u),i||(p=Be(s,"click",f),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&j(_,a),$&6&&H(s,"active",l[1]===l[5].code)},d(C){C&&m(s),i=!1,p()}}}function ke(n,l){let s,a,_,u;return a=new qe({props:{content:l[5].body}}),{key:n,first:null,c(){s=r("div"),ge(a.$$.fragment),_=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(i,p){d(i,s,p),ve(a,s,null),o(s,_),u=!0},p(i,p){l=i;const f={};p&4&&(f.content=l[5].body),a.$set(f),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(i){u||(x(a.$$.fragment,i),u=!0)},o(i){ee(a.$$.fragment,i),u=!1},d(i){i&&m(s),Ce(a)}}}function Ue(n){var de,me;let l,s,a=n[0].name+"",_,u,i,p,f,C,$,D=n[0].name+"",F,te,I,P,L,R,Q,S,N,le,K,T,se,z,M=n[0].name+"",G,ae,J,y,V,E,X,B,Z,w,q,g=[],ne=new Map,oe,A,h=[],ie=new Map,O;P=new Ae({props:{js:` import{S as $e,i as Pe,s as Se,O as Y,e as r,w as v,b as k,c as ge,f as b,g as d,h as o,m as ve,x as j,P as ue,Q as we,k as Oe,R as Re,n as Te,t as x,a as ee,o as m,d as Ce,C as ye,p as Ee,r as H,u as Be,N as qe}from"./index-6c8f1731.js";import{S as Ae}from"./SdkTabs-821a5c7e.js";function be(n,l,s){const a=n.slice();return a[5]=l[s],a}function _e(n,l,s){const a=n.slice();return a[5]=l[s],a}function he(n,l){let s,a=l[5].code+"",_,u,i,p;function f(){return l[4](l[5])}return{key:n,first:null,c(){s=r("button"),_=v(a),u=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(C,$){d(C,s,$),o(s,_),o(s,u),i||(p=Be(s,"click",f),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&j(_,a),$&6&&H(s,"active",l[1]===l[5].code)},d(C){C&&m(s),i=!1,p()}}}function ke(n,l){let s,a,_,u;return a=new qe({props:{content:l[5].body}}),{key:n,first:null,c(){s=r("div"),ge(a.$$.fragment),_=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(i,p){d(i,s,p),ve(a,s,null),o(s,_),u=!0},p(i,p){l=i;const f={};p&4&&(f.content=l[5].body),a.$set(f),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(i){u||(x(a.$$.fragment,i),u=!0)},o(i){ee(a.$$.fragment,i),u=!1},d(i){i&&m(s),Ce(a)}}}function Ue(n){var de,me;let l,s,a=n[0].name+"",_,u,i,p,f,C,$,D=n[0].name+"",F,te,I,P,L,R,Q,S,N,le,K,T,se,z,M=n[0].name+"",G,ae,J,y,V,E,X,B,Z,w,q,g=[],ne=new Map,oe,A,h=[],ie=new Map,O;P=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}'); const pb = new PocketBase('${n[3]}');

View File

@ -1,4 +1,4 @@
import{S as Re,i as Oe,s as Ce,O as K,e as r,w,b as h,c as ge,f as b,g as d,h as n,m as Pe,x as U,P as _e,Q as Ne,k as We,R as $e,n as Ee,t as ee,a as te,o as p,d as Se,C as ye,p as Ae,r as j,u as Te,N as De}from"./index-7d33ef4c.js";import{S as qe}from"./SdkTabs-f0532e58.js";function ke(o,s,l){const a=o.slice();return a[5]=s[l],a}function he(o,s,l){const a=o.slice();return a[5]=s[l],a}function ve(o,s){let l,a=s[5].code+"",_,u,i,f;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=r("button"),_=w(a),u=h(),b(l,"class","tab-item"),j(l,"active",s[1]===s[5].code),this.first=l},m(g,P){d(g,l,P),n(l,_),n(l,u),i||(f=Te(l,"click",m),i=!0)},p(g,P){s=g,P&4&&a!==(a=s[5].code+"")&&U(_,a),P&6&&j(l,"active",s[1]===s[5].code)},d(g){g&&p(l),i=!1,f()}}}function we(o,s){let l,a,_,u;return a=new De({props:{content:s[5].body}}),{key:o,first:null,c(){l=r("div"),ge(a.$$.fragment),_=h(),b(l,"class","tab-item"),j(l,"active",s[1]===s[5].code),this.first=l},m(i,f){d(i,l,f),Pe(a,l,null),n(l,_),u=!0},p(i,f){s=i;const m={};f&4&&(m.content=s[5].body),a.$set(m),(!u||f&6)&&j(l,"active",s[1]===s[5].code)},i(i){u||(ee(a.$$.fragment,i),u=!0)},o(i){te(a.$$.fragment,i),u=!1},d(i){i&&p(l),Se(a)}}}function Be(o){var fe,me;let s,l,a=o[0].name+"",_,u,i,f,m,g,P,q=o[0].name+"",H,se,le,L,Q,S,z,N,G,R,B,ae,M,W,ne,J,F=o[0].name+"",V,oe,X,$,Y,E,Z,y,x,O,A,v=[],ie=new Map,ce,T,k=[],re=new Map,C;S=new qe({props:{js:` import{S as Re,i as Oe,s as Ce,O as K,e as r,w,b as h,c as ge,f as b,g as d,h as n,m as Pe,x as U,P as _e,Q as Ne,k as We,R as $e,n as Ee,t as ee,a as te,o as p,d as Se,C as ye,p as Ae,r as j,u as Te,N as De}from"./index-6c8f1731.js";import{S as qe}from"./SdkTabs-821a5c7e.js";function ke(o,s,l){const a=o.slice();return a[5]=s[l],a}function he(o,s,l){const a=o.slice();return a[5]=s[l],a}function ve(o,s){let l,a=s[5].code+"",_,u,i,f;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=r("button"),_=w(a),u=h(),b(l,"class","tab-item"),j(l,"active",s[1]===s[5].code),this.first=l},m(g,P){d(g,l,P),n(l,_),n(l,u),i||(f=Te(l,"click",m),i=!0)},p(g,P){s=g,P&4&&a!==(a=s[5].code+"")&&U(_,a),P&6&&j(l,"active",s[1]===s[5].code)},d(g){g&&p(l),i=!1,f()}}}function we(o,s){let l,a,_,u;return a=new De({props:{content:s[5].body}}),{key:o,first:null,c(){l=r("div"),ge(a.$$.fragment),_=h(),b(l,"class","tab-item"),j(l,"active",s[1]===s[5].code),this.first=l},m(i,f){d(i,l,f),Pe(a,l,null),n(l,_),u=!0},p(i,f){s=i;const m={};f&4&&(m.content=s[5].body),a.$set(m),(!u||f&6)&&j(l,"active",s[1]===s[5].code)},i(i){u||(ee(a.$$.fragment,i),u=!0)},o(i){te(a.$$.fragment,i),u=!1},d(i){i&&p(l),Se(a)}}}function Be(o){var fe,me;let s,l,a=o[0].name+"",_,u,i,f,m,g,P,q=o[0].name+"",H,se,le,L,Q,S,z,N,G,R,B,ae,M,W,ne,J,F=o[0].name+"",V,oe,X,$,Y,E,Z,y,x,O,A,v=[],ie=new Map,ce,T,k=[],re=new Map,C;S=new qe({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as Se,i as Te,s as Be,O as D,e as r,w as g,b as k,c as ye,f as h,g as f,h as n,m as Ce,x as H,P as ke,Q as Re,k as qe,R as Oe,n as Ee,t as x,a as ee,o as d,d as Pe,C as Ne,p as Ve,r as F,u as Ke,N as Me}from"./index-7d33ef4c.js";import{S as Ae}from"./SdkTabs-f0532e58.js";function ve(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function we(o,l){let s,a=l[5].code+"",b,m,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),b=g(a),m=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(w,$){f(w,s,$),n(s,b),n(s,m),i||(p=Ke(s,"click",u),i=!0)},p(w,$){l=w,$&4&&a!==(a=l[5].code+"")&&H(b,a),$&6&&F(s,"active",l[1]===l[5].code)},d(w){w&&d(s),i=!1,p()}}}function $e(o,l){let s,a,b,m;return a=new Me({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ye(a.$$.fragment),b=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(i,p){f(i,s,p),Ce(a,s,null),n(s,b),m=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!m||p&6)&&F(s,"active",l[1]===l[5].code)},i(i){m||(x(a.$$.fragment,i),m=!0)},o(i){ee(a.$$.fragment,i),m=!1},d(i){i&&d(s),Pe(a)}}}function Ue(o){var fe,de,pe,ue;let l,s,a=o[0].name+"",b,m,i,p,u,w,$,K=o[0].name+"",I,te,L,y,Q,T,z,C,M,le,A,B,se,G,U=o[0].name+"",J,ae,W,R,X,q,Y,O,Z,P,E,v=[],oe=new Map,ne,N,_=[],ie=new Map,S;y=new Ae({props:{js:` import{S as Se,i as Te,s as Be,O as D,e as r,w as g,b as k,c as ye,f as h,g as f,h as n,m as Ce,x as H,P as ke,Q as Re,k as qe,R as Oe,n as Ee,t as x,a as ee,o as d,d as Pe,C as Ne,p as Ve,r as F,u as Ke,N as Me}from"./index-6c8f1731.js";import{S as Ae}from"./SdkTabs-821a5c7e.js";function ve(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function we(o,l){let s,a=l[5].code+"",b,m,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),b=g(a),m=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(w,$){f(w,s,$),n(s,b),n(s,m),i||(p=Ke(s,"click",u),i=!0)},p(w,$){l=w,$&4&&a!==(a=l[5].code+"")&&H(b,a),$&6&&F(s,"active",l[1]===l[5].code)},d(w){w&&d(s),i=!1,p()}}}function $e(o,l){let s,a,b,m;return a=new Me({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ye(a.$$.fragment),b=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(i,p){f(i,s,p),Ce(a,s,null),n(s,b),m=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!m||p&6)&&F(s,"active",l[1]===l[5].code)},i(i){m||(x(a.$$.fragment,i),m=!0)},o(i){ee(a.$$.fragment,i),m=!1},d(i){i&&d(s),Pe(a)}}}function Ue(o){var fe,de,pe,ue;let l,s,a=o[0].name+"",b,m,i,p,u,w,$,K=o[0].name+"",I,te,L,y,Q,T,z,C,M,le,A,B,se,G,U=o[0].name+"",J,ae,W,R,X,q,Y,O,Z,P,E,v=[],oe=new Map,ne,N,_=[],ie=new Map,S;y=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as qt,i as Ot,s as Mt,C as Q,O as ne,N as Tt,e as i,w as _,b as u,c as _e,f as v,g as r,h as n,m as he,x,P as Be,Q as ht,k as Ht,R as Lt,n as Pt,t as ue,a as fe,o as d,d as ke,p as Ft,r as ye,u as Rt,y as ae}from"./index-7d33ef4c.js";import{S as At}from"./SdkTabs-f0532e58.js";import{F as Bt}from"./FieldsQueryParam-f6b769d1.js";function kt(o,e,t){const a=o.slice();return a[8]=e[t],a}function yt(o,e,t){const a=o.slice();return a[8]=e[t],a}function vt(o,e,t){const a=o.slice();return a[13]=e[t],a}function gt(o){let e;return{c(){e=i("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",v(e,"class","txt-hint txt-sm txt-right")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function wt(o){let e,t,a,f,m,c,p,y,S,T,w,H,D,E,P,I,j,B,$,N,q,g,b;function O(h,C){var ee,K;return(K=(ee=h[0])==null?void 0:ee.options)!=null&&K.requireEmail?Dt:jt}let z=O(o),F=z(o);return{c(){e=i("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',t=u(),a=i("tr"),a.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>username</span></div></td> <td><span class="label">String</span></td> <td>The username of the auth record. import{S as qt,i as Ot,s as Mt,C as Q,O as ne,N as Tt,e as i,w as _,b as u,c as _e,f as v,g as r,h as n,m as he,x,P as Be,Q as ht,k as Ht,R as Lt,n as Pt,t as ue,a as fe,o as d,d as ke,p as Ft,r as ye,u as Rt,y as ae}from"./index-6c8f1731.js";import{S as At}from"./SdkTabs-821a5c7e.js";import{F as Bt}from"./FieldsQueryParam-2d478986.js";function kt(o,e,t){const a=o.slice();return a[8]=e[t],a}function yt(o,e,t){const a=o.slice();return a[8]=e[t],a}function vt(o,e,t){const a=o.slice();return a[13]=e[t],a}function gt(o){let e;return{c(){e=i("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",v(e,"class","txt-hint txt-sm txt-right")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function wt(o){let e,t,a,f,m,c,p,y,S,T,w,H,D,E,P,I,j,B,$,N,q,g,b;function O(h,C){var ee,K;return(K=(ee=h[0])==null?void 0:ee.options)!=null&&K.requireEmail?Dt:jt}let z=O(o),F=z(o);return{c(){e=i("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',t=u(),a=i("tr"),a.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>username</span></div></td> <td><span class="label">String</span></td> <td>The username of the auth record.
<br/> <br/>
If not set, it will be auto generated.</td>`,f=u(),m=i("tr"),c=i("td"),p=i("div"),F.c(),y=u(),S=i("span"),S.textContent="email",T=u(),w=i("td"),w.innerHTML='<span class="label">String</span>',H=u(),D=i("td"),D.textContent="Auth record email address.",E=u(),P=i("tr"),P.innerHTML='<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>emailVisibility</span></div></td> <td><span class="label">Boolean</span></td> <td>Whether to show/hide the auth record email when fetching the record data.</td>',I=u(),j=i("tr"),j.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>Auth record password.</td>',B=u(),$=i("tr"),$.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>Auth record password confirmation.</td>',N=u(),q=i("tr"),q.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>verified</span></div></td> <td><span class="label">Boolean</span></td> <td>Indicates whether the auth record is verified or not. If not set, it will be auto generated.</td>`,f=u(),m=i("tr"),c=i("td"),p=i("div"),F.c(),y=u(),S=i("span"),S.textContent="email",T=u(),w=i("td"),w.innerHTML='<span class="label">String</span>',H=u(),D=i("td"),D.textContent="Auth record email address.",E=u(),P=i("tr"),P.innerHTML='<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>emailVisibility</span></div></td> <td><span class="label">Boolean</span></td> <td>Whether to show/hide the auth record email when fetching the record data.</td>',I=u(),j=i("tr"),j.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>Auth record password.</td>',B=u(),$=i("tr"),$.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>Auth record password confirmation.</td>',N=u(),q=i("tr"),q.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>verified</span></div></td> <td><span class="label">Boolean</span></td> <td>Indicates whether the auth record is verified or not.
<br/> <br/>

View File

@ -1,4 +1,4 @@
import{S as Re,i as Pe,s as Ee,O as j,e as c,w as y,b as k,c as De,f as m,g as p,h as i,m as Ce,x as ee,P as he,Q as Oe,k as Te,R as Be,n as Ie,t as te,a as le,o as u,d as we,C as Ae,p as Me,r as N,u as Se,N as qe}from"./index-7d33ef4c.js";import{S as He}from"./SdkTabs-f0532e58.js";function ke(a,l,s){const o=a.slice();return o[6]=l[s],o}function ge(a,l,s){const o=a.slice();return o[6]=l[s],o}function ve(a){let l;return{c(){l=c("p"),l.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,o){p(s,l,o)},d(s){s&&u(l)}}}function ye(a,l){let s,o,h;function d(){return l[5](l[6])}return{key:a,first:null,c(){s=c("button"),s.textContent=`${l[6].code} `,m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(n,r){p(n,s,r),o||(h=Se(s,"click",d),o=!0)},p(n,r){l=n,r&20&&N(s,"active",l[2]===l[6].code)},d(n){n&&u(s),o=!1,h()}}}function $e(a,l){let s,o,h,d;return o=new qe({props:{content:l[6].body}}),{key:a,first:null,c(){s=c("div"),De(o.$$.fragment),h=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(n,r){p(n,s,r),Ce(o,s,null),i(s,h),d=!0},p(n,r){l=n,(!d||r&20)&&N(s,"active",l[2]===l[6].code)},i(n){d||(te(o.$$.fragment,n),d=!0)},o(n){le(o.$$.fragment,n),d=!1},d(n){n&&u(s),we(o)}}}function Le(a){var ue,me;let l,s,o=a[0].name+"",h,d,n,r,$,D,z,S=a[0].name+"",F,se,K,C,Q,E,G,g,q,ae,H,P,oe,J,L=a[0].name+"",V,ne,W,ie,X,O,Y,T,Z,B,x,w,I,v=[],ce=new Map,de,A,b=[],re=new Map,R;C=new He({props:{js:` import{S as Re,i as Pe,s as Ee,O as j,e as c,w as y,b as k,c as De,f as m,g as p,h as i,m as Ce,x as ee,P as he,Q as Oe,k as Te,R as Be,n as Ie,t as te,a as le,o as u,d as we,C as Ae,p as Me,r as N,u as Se,N as qe}from"./index-6c8f1731.js";import{S as He}from"./SdkTabs-821a5c7e.js";function ke(a,l,s){const o=a.slice();return o[6]=l[s],o}function ge(a,l,s){const o=a.slice();return o[6]=l[s],o}function ve(a){let l;return{c(){l=c("p"),l.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,o){p(s,l,o)},d(s){s&&u(l)}}}function ye(a,l){let s,o,h;function d(){return l[5](l[6])}return{key:a,first:null,c(){s=c("button"),s.textContent=`${l[6].code} `,m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(n,r){p(n,s,r),o||(h=Se(s,"click",d),o=!0)},p(n,r){l=n,r&20&&N(s,"active",l[2]===l[6].code)},d(n){n&&u(s),o=!1,h()}}}function $e(a,l){let s,o,h,d;return o=new qe({props:{content:l[6].body}}),{key:a,first:null,c(){s=c("div"),De(o.$$.fragment),h=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(n,r){p(n,s,r),Ce(o,s,null),i(s,h),d=!0},p(n,r){l=n,(!d||r&20)&&N(s,"active",l[2]===l[6].code)},i(n){d||(te(o.$$.fragment,n),d=!0)},o(n){le(o.$$.fragment,n),d=!1},d(n){n&&u(s),we(o)}}}function Le(a){var ue,me;let l,s,o=a[0].name+"",h,d,n,r,$,D,z,S=a[0].name+"",F,se,K,C,Q,E,G,g,q,ae,H,P,oe,J,L=a[0].name+"",V,ne,W,ie,X,O,Y,T,Z,B,x,w,I,v=[],ce=new Map,de,A,b=[],re=new Map,R;C=new He({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

View File

@ -1,4 +1,4 @@
import{S as L,i as S,s as k,N as E,e as s,b as o,w as $,c as F,f as H,g as M,h as e,m as T,y as q,t as N,a as B,o as I,d as J}from"./index-7d33ef4c.js";function O(v){let t,i,x,p,g,n,a,h,c,_,r,b,f,y,u,C,m,d;return r=new E({props:{content:` import{S as L,i as S,s as k,N as E,e as s,b as o,w as $,c as F,f as H,g as M,h as e,m as T,y as q,t as N,a as B,o as I,d as J}from"./index-6c8f1731.js";function O(v){let t,i,x,p,g,n,a,h,c,_,r,b,f,y,u,C,m,d;return r=new E({props:{content:`
?fields=*,expand.relField.name ?fields=*,expand.relField.name
`}}),{c(){t=s("tr"),i=s("td"),i.textContent="fields",x=o(),p=s("td"),p.innerHTML='<span class="label">String</span>',g=o(),n=s("td"),a=s("p"),h=$(`Comma separated string of the fields to return in the JSON response `}}),{c(){t=s("tr"),i=s("td"),i.textContent="fields",x=o(),p=s("td"),p.innerHTML='<span class="label">String</span>',g=o(),n=s("td"),a=s("p"),h=$(`Comma separated string of the fields to return in the JSON response
`),c=s("em"),c.textContent="(by default returns all fields)",_=$(`. Ex.: `),c=s("em"),c.textContent="(by default returns all fields)",_=$(`. Ex.:

View File

@ -1,4 +1,4 @@
import{S as Ze,i as tl,s as el,e,b as s,E as sl,f as a,g as u,u as ll,y as Qe,o as m,w as _,h as t,N as Fe,O as se,c as Qt,m as Ut,x as ke,P as Ue,Q as nl,k as ol,R as al,n as il,t as $t,a as Ct,d as jt,T as rl,C as ve,p as cl,r as Le}from"./index-7d33ef4c.js";import{S as dl}from"./SdkTabs-f0532e58.js";import{F as pl}from"./FieldsQueryParam-f6b769d1.js";function fl(d){let n,o,i;return{c(){n=e("span"),n.textContent="Show details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-down-s-line")},m(f,h){u(f,n,h),u(f,o,h),u(f,i,h)},d(f){f&&(m(n),m(o),m(i))}}}function ul(d){let n,o,i;return{c(){n=e("span"),n.textContent="Hide details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-up-s-line")},m(f,h){u(f,n,h),u(f,o,h),u(f,i,h)},d(f){f&&(m(n),m(o),m(i))}}}function je(d){let n,o,i,f,h,r,b,$,C,g,p,tt,kt,zt,E,Kt,H,rt,R,et,ne,Q,U,oe,ct,yt,lt,vt,ae,dt,pt,st,N,Jt,Ft,y,nt,Lt,Vt,At,j,ot,Tt,Wt,Pt,F,ft,Rt,ie,ut,re,M,Ot,at,St,O,mt,ce,z,Et,Xt,Nt,de,q,Yt,K,ht,pe,I,fe,B,ue,P,qt,J,bt,me,gt,he,x,Dt,it,Ht,be,Mt,Zt,V,_t,ge,It,_e,wt,we,W,G,xe,xt,te,X,ee,L,Y,S,Bt,$e,Z,v,Gt;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format import{S as Ze,i as tl,s as el,e,b as s,E as sl,f as a,g as u,u as ll,y as Qe,o as m,w as _,h as t,N as Fe,O as se,c as Qt,m as Ut,x as ke,P as Ue,Q as nl,k as ol,R as al,n as il,t as $t,a as Ct,d as jt,T as rl,C as ve,p as cl,r as Le}from"./index-6c8f1731.js";import{S as dl}from"./SdkTabs-821a5c7e.js";import{F as pl}from"./FieldsQueryParam-2d478986.js";function fl(d){let n,o,i;return{c(){n=e("span"),n.textContent="Show details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-down-s-line")},m(f,h){u(f,n,h),u(f,o,h),u(f,i,h)},d(f){f&&(m(n),m(o),m(i))}}}function ul(d){let n,o,i;return{c(){n=e("span"),n.textContent="Hide details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-up-s-line")},m(f,h){u(f,n,h),u(f,o,h),u(f,i,h)},d(f){f&&(m(n),m(o),m(i))}}}function je(d){let n,o,i,f,h,r,b,$,C,g,p,tt,kt,zt,E,Kt,H,rt,R,et,ne,Q,U,oe,ct,yt,lt,vt,ae,dt,pt,st,N,Jt,Ft,y,nt,Lt,Vt,At,j,ot,Tt,Wt,Pt,F,ft,Rt,ie,ut,re,M,Ot,at,St,O,mt,ce,z,Et,Xt,Nt,de,q,Yt,K,ht,pe,I,fe,B,ue,P,qt,J,bt,me,gt,he,x,Dt,it,Ht,be,Mt,Zt,V,_t,ge,It,_e,wt,we,W,G,xe,xt,te,X,ee,L,Y,S,Bt,$e,Z,v,Gt;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format
<code><span class="txt-success">OPERAND</span> <span class="txt-danger">OPERATOR</span> <span class="txt-success">OPERAND</span></code>, where:`,o=s(),i=e("ul"),f=e("li"),f.innerHTML=`<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single <code><span class="txt-success">OPERAND</span> <span class="txt-danger">OPERATOR</span> <span class="txt-success">OPERAND</span></code>, where:`,o=s(),i=e("ul"),f=e("li"),f.innerHTML=`<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single
or double quoted), number, null, true, false`,h=s(),r=e("li"),b=e("code"),b.textContent="OPERATOR",$=_(` - is one of: or double quoted), number, null, true, false`,h=s(),r=e("li"),b=e("code"),b.textContent="OPERATOR",$=_(` - is one of:
`),C=e("br"),g=s(),p=e("ul"),tt=e("li"),kt=e("code"),kt.textContent="=",zt=s(),E=e("span"),E.textContent="Equal",Kt=s(),H=e("li"),rt=e("code"),rt.textContent="!=",R=s(),et=e("span"),et.textContent="NOT equal",ne=s(),Q=e("li"),U=e("code"),U.textContent=">",oe=s(),ct=e("span"),ct.textContent="Greater than",yt=s(),lt=e("li"),vt=e("code"),vt.textContent=">=",ae=s(),dt=e("span"),dt.textContent="Greater than or equal",pt=s(),st=e("li"),N=e("code"),N.textContent="<",Jt=s(),Ft=e("span"),Ft.textContent="Less than",y=s(),nt=e("li"),Lt=e("code"),Lt.textContent="<=",Vt=s(),At=e("span"),At.textContent="Less than or equal",j=s(),ot=e("li"),Tt=e("code"),Tt.textContent="~",Wt=s(),Pt=e("span"),Pt.textContent=`Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for `),C=e("br"),g=s(),p=e("ul"),tt=e("li"),kt=e("code"),kt.textContent="=",zt=s(),E=e("span"),E.textContent="Equal",Kt=s(),H=e("li"),rt=e("code"),rt.textContent="!=",R=s(),et=e("span"),et.textContent="NOT equal",ne=s(),Q=e("li"),U=e("code"),U.textContent=">",oe=s(),ct=e("span"),ct.textContent="Greater than",yt=s(),lt=e("li"),vt=e("code"),vt.textContent=">=",ae=s(),dt=e("span"),dt.textContent="Greater than or equal",pt=s(),st=e("li"),N=e("code"),N.textContent="<",Jt=s(),Ft=e("span"),Ft.textContent="Less than",y=s(),nt=e("li"),Lt=e("code"),Lt.textContent="<=",Vt=s(),At=e("span"),At.textContent="Less than or equal",j=s(),ot=e("li"),Tt=e("code"),Tt.textContent="~",Wt=s(),Pt=e("span"),Pt.textContent=`Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for

View File

@ -1,4 +1,4 @@
import{S as ze,i as Qe,s as Ue,O as F,e as i,w as v,b as m,c as pe,f as b,g as c,h as a,m as ue,x as N,P as Oe,Q as je,k as Fe,R as Ne,n as Ge,t as G,a as K,o as d,d as me,C as Ke,p as Je,r as J,u as Ve,N as Xe}from"./index-7d33ef4c.js";import{S as Ye}from"./SdkTabs-f0532e58.js";import{F as Ze}from"./FieldsQueryParam-f6b769d1.js";function De(o,l,s){const n=o.slice();return n[5]=l[s],n}function He(o,l,s){const n=o.slice();return n[5]=l[s],n}function Re(o,l){let s,n=l[5].code+"",f,_,r,u;function h(){return l[4](l[5])}return{key:o,first:null,c(){s=i("button"),f=v(n),_=m(),b(s,"class","tab-item"),J(s,"active",l[1]===l[5].code),this.first=s},m(w,y){c(w,s,y),a(s,f),a(s,_),r||(u=Ve(s,"click",h),r=!0)},p(w,y){l=w,y&4&&n!==(n=l[5].code+"")&&N(f,n),y&6&&J(s,"active",l[1]===l[5].code)},d(w){w&&d(s),r=!1,u()}}}function We(o,l){let s,n,f,_;return n=new Xe({props:{content:l[5].body}}),{key:o,first:null,c(){s=i("div"),pe(n.$$.fragment),f=m(),b(s,"class","tab-item"),J(s,"active",l[1]===l[5].code),this.first=s},m(r,u){c(r,s,u),ue(n,s,null),a(s,f),_=!0},p(r,u){l=r;const h={};u&4&&(h.content=l[5].body),n.$set(h),(!_||u&6)&&J(s,"active",l[1]===l[5].code)},i(r){_||(G(n.$$.fragment,r),_=!0)},o(r){K(n.$$.fragment,r),_=!1},d(r){r&&d(s),me(n)}}}function xe(o){var Ce,Se,Ee,Ie;let l,s,n=o[0].name+"",f,_,r,u,h,w,y,R=o[0].name+"",V,be,fe,X,Y,P,Z,I,x,$,W,he,z,T,_e,ee,Q=o[0].name+"",te,ke,le,ve,ge,U,se,B,ae,q,oe,L,ne,A,ie,$e,ce,E,de,M,re,C,O,g=[],we=new Map,ye,D,k=[],Pe=new Map,S;P=new Ye({props:{js:` import{S as ze,i as Qe,s as Ue,O as F,e as i,w as v,b as m,c as pe,f as b,g as c,h as a,m as ue,x as N,P as Oe,Q as je,k as Fe,R as Ne,n as Ge,t as G,a as K,o as d,d as me,C as Ke,p as Je,r as J,u as Ve,N as Xe}from"./index-6c8f1731.js";import{S as Ye}from"./SdkTabs-821a5c7e.js";import{F as Ze}from"./FieldsQueryParam-2d478986.js";function De(o,l,s){const n=o.slice();return n[5]=l[s],n}function He(o,l,s){const n=o.slice();return n[5]=l[s],n}function Re(o,l){let s,n=l[5].code+"",f,_,r,u;function h(){return l[4](l[5])}return{key:o,first:null,c(){s=i("button"),f=v(n),_=m(),b(s,"class","tab-item"),J(s,"active",l[1]===l[5].code),this.first=s},m(w,y){c(w,s,y),a(s,f),a(s,_),r||(u=Ve(s,"click",h),r=!0)},p(w,y){l=w,y&4&&n!==(n=l[5].code+"")&&N(f,n),y&6&&J(s,"active",l[1]===l[5].code)},d(w){w&&d(s),r=!1,u()}}}function We(o,l){let s,n,f,_;return n=new Xe({props:{content:l[5].body}}),{key:o,first:null,c(){s=i("div"),pe(n.$$.fragment),f=m(),b(s,"class","tab-item"),J(s,"active",l[1]===l[5].code),this.first=s},m(r,u){c(r,s,u),ue(n,s,null),a(s,f),_=!0},p(r,u){l=r;const h={};u&4&&(h.content=l[5].body),n.$set(h),(!_||u&6)&&J(s,"active",l[1]===l[5].code)},i(r){_||(G(n.$$.fragment,r),_=!0)},o(r){K(n.$$.fragment,r),_=!1},d(r){r&&d(s),me(n)}}}function xe(o){var Ce,Se,Ee,Ie;let l,s,n=o[0].name+"",f,_,r,u,h,w,y,R=o[0].name+"",V,be,fe,X,Y,P,Z,I,x,$,W,he,z,T,_e,ee,Q=o[0].name+"",te,ke,le,ve,ge,U,se,B,ae,q,oe,L,ne,A,ie,$e,ce,E,de,M,re,C,O,g=[],we=new Map,ye,D,k=[],Pe=new Map,S;P=new Ye({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,2 +1,2 @@
import{S as E,i as G,s as I,F as K,c as R,m as A,t as B,a as N,d as T,C as M,q as J,e as _,w as P,b as k,f,r as L,g as b,h as c,u as j,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as q}from"./index-7d33ef4c.js";function y(i){let e,n,s;return{c(){e=P("for "),n=_("strong"),s=P(i[3]),f(n,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,n,t),c(n,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&(w(e),w(n))}}}function x(i){let e,n,s,l,t,u,p,d;return{c(){e=_("label"),n=P("New password"),l=k(),t=_("input"),f(e,"for",s=i[8]),f(t,"type","password"),f(t,"id",u=i[8]),t.required=!0,t.autofocus=!0},m(r,a){b(r,e,a),c(e,n),b(r,l,a),b(r,t,a),q(t,i[0]),t.focus(),p||(d=j(t,"input",i[6]),p=!0)},p(r,a){a&256&&s!==(s=r[8])&&f(e,"for",s),a&256&&u!==(u=r[8])&&f(t,"id",u),a&1&&t.value!==r[0]&&q(t,r[0])},d(r){r&&(w(e),w(l),w(t)),p=!1,d()}}}function ee(i){let e,n,s,l,t,u,p,d;return{c(){e=_("label"),n=P("New password confirm"),l=k(),t=_("input"),f(e,"for",s=i[8]),f(t,"type","password"),f(t,"id",u=i[8]),t.required=!0},m(r,a){b(r,e,a),c(e,n),b(r,l,a),b(r,t,a),q(t,i[1]),p||(d=j(t,"input",i[7]),p=!0)},p(r,a){a&256&&s!==(s=r[8])&&f(e,"for",s),a&256&&u!==(u=r[8])&&f(t,"id",u),a&2&&t.value!==r[1]&&q(t,r[1])},d(r){r&&(w(e),w(l),w(t)),p=!1,d()}}}function te(i){let e,n,s,l,t,u,p,d,r,a,g,S,C,v,h,F,z,m=i[3]&&y(i);return u=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:o})=>({8:o}),({uniqueId:o})=>o?256:0]},$$scope:{ctx:i}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:o})=>({8:o}),({uniqueId:o})=>o?256:0]},$$scope:{ctx:i}}}),{c(){e=_("form"),n=_("div"),s=_("h4"),l=P(`Reset your admin password import{S as E,i as G,s as I,F as K,c as R,m as A,t as B,a as N,d as T,C as M,q as J,e as _,w as P,b as k,f,r as L,g as b,h as c,u as j,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as q}from"./index-6c8f1731.js";function y(i){let e,n,s;return{c(){e=P("for "),n=_("strong"),s=P(i[3]),f(n,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,n,t),c(n,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&(w(e),w(n))}}}function x(i){let e,n,s,l,t,u,p,d;return{c(){e=_("label"),n=P("New password"),l=k(),t=_("input"),f(e,"for",s=i[8]),f(t,"type","password"),f(t,"id",u=i[8]),t.required=!0,t.autofocus=!0},m(r,a){b(r,e,a),c(e,n),b(r,l,a),b(r,t,a),q(t,i[0]),t.focus(),p||(d=j(t,"input",i[6]),p=!0)},p(r,a){a&256&&s!==(s=r[8])&&f(e,"for",s),a&256&&u!==(u=r[8])&&f(t,"id",u),a&1&&t.value!==r[0]&&q(t,r[0])},d(r){r&&(w(e),w(l),w(t)),p=!1,d()}}}function ee(i){let e,n,s,l,t,u,p,d;return{c(){e=_("label"),n=P("New password confirm"),l=k(),t=_("input"),f(e,"for",s=i[8]),f(t,"type","password"),f(t,"id",u=i[8]),t.required=!0},m(r,a){b(r,e,a),c(e,n),b(r,l,a),b(r,t,a),q(t,i[1]),p||(d=j(t,"input",i[7]),p=!0)},p(r,a){a&256&&s!==(s=r[8])&&f(e,"for",s),a&256&&u!==(u=r[8])&&f(t,"id",u),a&2&&t.value!==r[1]&&q(t,r[1])},d(r){r&&(w(e),w(l),w(t)),p=!1,d()}}}function te(i){let e,n,s,l,t,u,p,d,r,a,g,S,C,v,h,F,z,m=i[3]&&y(i);return u=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:o})=>({8:o}),({uniqueId:o})=>o?256:0]},$$scope:{ctx:i}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:o})=>({8:o}),({uniqueId:o})=>o?256:0]},$$scope:{ctx:i}}}),{c(){e=_("form"),n=_("div"),s=_("h4"),l=P(`Reset your admin password
`),m&&m.c(),t=k(),R(u.$$.fragment),p=k(),R(d.$$.fragment),r=k(),a=_("button"),g=_("span"),g.textContent="Set new password",S=k(),C=_("div"),v=_("a"),v.textContent="Back to login",f(s,"class","m-b-xs"),f(n,"class","content txt-center m-b-sm"),f(g,"class","txt"),f(a,"type","submit"),f(a,"class","btn btn-lg btn-block"),a.disabled=i[2],L(a,"btn-loading",i[2]),f(e,"class","m-b-base"),f(v,"href","/login"),f(v,"class","link-hint"),f(C,"class","content txt-center")},m(o,$){b(o,e,$),c(e,n),c(n,s),c(s,l),m&&m.m(s,null),c(e,t),A(u,e,null),c(e,p),A(d,e,null),c(e,r),c(e,a),c(a,g),b(o,S,$),b(o,C,$),c(C,v),h=!0,F||(z=[j(e,"submit",O(i[4])),Q(U.call(null,v))],F=!0)},p(o,$){o[3]?m?m.p(o,$):(m=y(o),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const D={};$&769&&(D.$$scope={dirty:$,ctx:o}),u.$set(D);const H={};$&770&&(H.$$scope={dirty:$,ctx:o}),d.$set(H),(!h||$&4)&&(a.disabled=o[2]),(!h||$&4)&&L(a,"btn-loading",o[2])},i(o){h||(B(u.$$.fragment,o),B(d.$$.fragment,o),h=!0)},o(o){N(u.$$.fragment,o),N(d.$$.fragment,o),h=!1},d(o){o&&(w(e),w(S),w(C)),m&&m.d(),T(u),T(d),F=!1,V(z)}}}function se(i){let e,n;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:i}}}),{c(){R(e.$$.fragment)},m(s,l){A(e,s,l),n=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){n||(B(e.$$.fragment,s),n=!0)},o(s){N(e.$$.fragment,s),n=!1},d(s){T(e,s)}}}function le(i,e,n){let s,{params:l}=e,t="",u="",p=!1;async function d(){if(!p){n(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,u),X("Successfully set a new admin password."),Y("/")}catch(g){W.error(g)}n(2,p=!1)}}function r(){t=this.value,n(0,t)}function a(){u=this.value,n(1,u)}return i.$$set=g=>{"params"in g&&n(5,l=g.params)},i.$$.update=()=>{i.$$.dirty&32&&n(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,u,p,s,d,l,r,a]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default}; `),m&&m.c(),t=k(),R(u.$$.fragment),p=k(),R(d.$$.fragment),r=k(),a=_("button"),g=_("span"),g.textContent="Set new password",S=k(),C=_("div"),v=_("a"),v.textContent="Back to login",f(s,"class","m-b-xs"),f(n,"class","content txt-center m-b-sm"),f(g,"class","txt"),f(a,"type","submit"),f(a,"class","btn btn-lg btn-block"),a.disabled=i[2],L(a,"btn-loading",i[2]),f(e,"class","m-b-base"),f(v,"href","/login"),f(v,"class","link-hint"),f(C,"class","content txt-center")},m(o,$){b(o,e,$),c(e,n),c(n,s),c(s,l),m&&m.m(s,null),c(e,t),A(u,e,null),c(e,p),A(d,e,null),c(e,r),c(e,a),c(a,g),b(o,S,$),b(o,C,$),c(C,v),h=!0,F||(z=[j(e,"submit",O(i[4])),Q(U.call(null,v))],F=!0)},p(o,$){o[3]?m?m.p(o,$):(m=y(o),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const D={};$&769&&(D.$$scope={dirty:$,ctx:o}),u.$set(D);const H={};$&770&&(H.$$scope={dirty:$,ctx:o}),d.$set(H),(!h||$&4)&&(a.disabled=o[2]),(!h||$&4)&&L(a,"btn-loading",o[2])},i(o){h||(B(u.$$.fragment,o),B(d.$$.fragment,o),h=!0)},o(o){N(u.$$.fragment,o),N(d.$$.fragment,o),h=!1},d(o){o&&(w(e),w(S),w(C)),m&&m.d(),T(u),T(d),F=!1,V(z)}}}function se(i){let e,n;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:i}}}),{c(){R(e.$$.fragment)},m(s,l){A(e,s,l),n=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){n||(B(e.$$.fragment,s),n=!0)},o(s){N(e.$$.fragment,s),n=!1},d(s){T(e,s)}}}function le(i,e,n){let s,{params:l}=e,t="",u="",p=!1;async function d(){if(!p){n(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,u),X("Successfully set a new admin password."),Y("/")}catch(g){W.error(g)}n(2,p=!1)}}function r(){t=this.value,n(0,t)}function a(){u=this.value,n(1,u)}return i.$$set=g=>{"params"in g&&n(5,l=g.params)},i.$$.update=()=>{i.$$.dirty&32&&n(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,u,p,s,d,l,r,a]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default};

View File

@ -1 +1 @@
import{S as M,i as T,s as j,F as z,c as R,m as S,t as w,a as y,d as E,b as v,e as _,f as p,g,h as d,j as A,l as B,k as N,n as D,o as k,p as C,q as G,r as F,u as H,v as I,w as h,x as J,y as P,z as L}from"./index-7d33ef4c.js";function K(u){let e,s,n,l,t,o,c,m,r,a,b,f;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:i})=>({5:i}),({uniqueId:i})=>i?32:0]},$$scope:{ctx:u}}}),{c(){e=_("form"),s=_("div"),s.innerHTML='<h4 class="m-b-xs">Forgotten admin password</h4> <p>Enter the email associated with your account and well send you a recovery link:</p>',n=v(),R(l.$$.fragment),t=v(),o=_("button"),c=_("i"),m=v(),r=_("span"),r.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(c,"class","ri-mail-send-line"),p(r,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=u[1],F(o,"btn-loading",u[1]),p(e,"class","m-b-base")},m(i,$){g(i,e,$),d(e,s),d(e,n),S(l,e,null),d(e,t),d(e,o),d(o,c),d(o,m),d(o,r),a=!0,b||(f=H(e,"submit",I(u[3])),b=!0)},p(i,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:i}),l.$set(q),(!a||$&2)&&(o.disabled=i[1]),(!a||$&2)&&F(o,"btn-loading",i[1])},i(i){a||(w(l.$$.fragment,i),a=!0)},o(i){y(l.$$.fragment,i),a=!1},d(i){i&&k(e),E(l),b=!1,f()}}}function O(u){let e,s,n,l,t,o,c,m,r;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=v(),l=_("div"),t=_("p"),o=h("Check "),c=_("strong"),m=h(u[0]),r=h(" for the recovery link."),p(s,"class","icon"),p(c,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){g(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,c),d(c,m),d(t,r)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&k(e)}}}function Q(u){let e,s,n,l,t,o,c,m;return{c(){e=_("label"),s=h("Email"),l=v(),t=_("input"),p(e,"for",n=u[5]),p(t,"type","email"),p(t,"id",o=u[5]),t.required=!0,t.autofocus=!0},m(r,a){g(r,e,a),d(e,s),g(r,l,a),g(r,t,a),L(t,u[0]),t.focus(),c||(m=H(t,"input",u[4]),c=!0)},p(r,a){a&32&&n!==(n=r[5])&&p(e,"for",n),a&32&&o!==(o=r[5])&&p(t,"id",o),a&1&&t.value!==r[0]&&L(t,r[0])},d(r){r&&(k(e),k(l),k(t)),c=!1,m()}}}function U(u){let e,s,n,l,t,o,c,m;const r=[O,K],a=[];function b(f,i){return f[2]?0:1}return e=b(u),s=a[e]=r[e](u),{c(){s.c(),n=v(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(f,i){a[e].m(f,i),g(f,n,i),g(f,l,i),d(l,t),o=!0,c||(m=A(B.call(null,t)),c=!0)},p(f,i){let $=e;e=b(f),e===$?a[e].p(f,i):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(f,i):(s=a[e]=r[e](f),s.c()),w(s,1),s.m(n.parentNode,n))},i(f){o||(w(s),o=!0)},o(f){y(s),o=!1},d(f){f&&(k(n),k(l)),a[e].d(f),c=!1,m()}}}function V(u){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:u}}}),{c(){R(e.$$.fragment)},m(n,l){S(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){E(e,n)}}}function W(u,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.error(m)}s(1,l=!1)}}function c(){n=this.value,s(0,n)}return[n,l,t,o,c]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default}; import{S as M,i as T,s as j,F as z,c as R,m as S,t as w,a as y,d as E,b as v,e as _,f as p,g,h as d,j as A,l as B,k as N,n as D,o as k,p as C,q as G,r as F,u as H,v as I,w as h,x as J,y as P,z as L}from"./index-6c8f1731.js";function K(u){let e,s,n,l,t,o,c,m,r,a,b,f;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:i})=>({5:i}),({uniqueId:i})=>i?32:0]},$$scope:{ctx:u}}}),{c(){e=_("form"),s=_("div"),s.innerHTML='<h4 class="m-b-xs">Forgotten admin password</h4> <p>Enter the email associated with your account and well send you a recovery link:</p>',n=v(),R(l.$$.fragment),t=v(),o=_("button"),c=_("i"),m=v(),r=_("span"),r.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(c,"class","ri-mail-send-line"),p(r,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=u[1],F(o,"btn-loading",u[1]),p(e,"class","m-b-base")},m(i,$){g(i,e,$),d(e,s),d(e,n),S(l,e,null),d(e,t),d(e,o),d(o,c),d(o,m),d(o,r),a=!0,b||(f=H(e,"submit",I(u[3])),b=!0)},p(i,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:i}),l.$set(q),(!a||$&2)&&(o.disabled=i[1]),(!a||$&2)&&F(o,"btn-loading",i[1])},i(i){a||(w(l.$$.fragment,i),a=!0)},o(i){y(l.$$.fragment,i),a=!1},d(i){i&&k(e),E(l),b=!1,f()}}}function O(u){let e,s,n,l,t,o,c,m,r;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=v(),l=_("div"),t=_("p"),o=h("Check "),c=_("strong"),m=h(u[0]),r=h(" for the recovery link."),p(s,"class","icon"),p(c,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){g(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,c),d(c,m),d(t,r)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&k(e)}}}function Q(u){let e,s,n,l,t,o,c,m;return{c(){e=_("label"),s=h("Email"),l=v(),t=_("input"),p(e,"for",n=u[5]),p(t,"type","email"),p(t,"id",o=u[5]),t.required=!0,t.autofocus=!0},m(r,a){g(r,e,a),d(e,s),g(r,l,a),g(r,t,a),L(t,u[0]),t.focus(),c||(m=H(t,"input",u[4]),c=!0)},p(r,a){a&32&&n!==(n=r[5])&&p(e,"for",n),a&32&&o!==(o=r[5])&&p(t,"id",o),a&1&&t.value!==r[0]&&L(t,r[0])},d(r){r&&(k(e),k(l),k(t)),c=!1,m()}}}function U(u){let e,s,n,l,t,o,c,m;const r=[O,K],a=[];function b(f,i){return f[2]?0:1}return e=b(u),s=a[e]=r[e](u),{c(){s.c(),n=v(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(f,i){a[e].m(f,i),g(f,n,i),g(f,l,i),d(l,t),o=!0,c||(m=A(B.call(null,t)),c=!0)},p(f,i){let $=e;e=b(f),e===$?a[e].p(f,i):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(f,i):(s=a[e]=r[e](f),s.c()),w(s,1),s.m(n.parentNode,n))},i(f){o||(w(s),o=!0)},o(f){y(s),o=!1},d(f){f&&(k(n),k(l)),a[e].d(f),c=!1,m()}}}function V(u){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:u}}}),{c(){R(e.$$.fragment)},m(n,l){S(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){E(e,n)}}}function W(u,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.error(m)}s(1,l=!1)}}function c(){n=this.value,s(0,n)}return[n,l,t,o,c]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default};

View File

@ -1 +1 @@
import{S as o,i,s as c,e as r,f as l,g as u,y as s,o as d,I as h}from"./index-7d33ef4c.js";function f(n){let t;return{c(){t=r("div"),t.innerHTML='<h3 class="m-b-sm">Auth completed.</h3> <h5>You can go back to the app if this window is not automatically closed.</h5>',l(t,"class","content txt-hint txt-center p-base")},m(e,a){u(e,t,a)},p:s,i:s,o:s,d(e){e&&d(t)}}}function m(n){return h(()=>{window.close()}),[]}class x extends o{constructor(t){super(),i(this,t,m,f,c,{})}}export{x as default}; import{S as o,i,s as c,e as r,f as l,g as u,y as s,o as d,I as h}from"./index-6c8f1731.js";function f(n){let t;return{c(){t=r("div"),t.innerHTML='<h3 class="m-b-sm">Auth completed.</h3> <h5>You can go back to the app if this window is not automatically closed.</h5>',l(t,"class","content txt-hint txt-center p-base")},m(e,a){u(e,t,a)},p:s,i:s,o:s,d(e){e&&d(t)}}}function m(n){return h(()=>{window.close()}),[]}class x extends o{constructor(t){super(),i(this,t,m,f,c,{})}}export{x as default};

View File

@ -1,2 +1,2 @@
import{S as G,i as I,s as J,F as M,c as S,m as L,t as h,a as v,d as z,C as N,E as R,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as y,b as C,f as p,r as T,h as g,u as P,v as K,y as E,x as O,z as F}from"./index-7d33ef4c.js";function Q(i){let e,t,n,l,s,o,f,a,r,u,k,$,d=i[3]&&H(i);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:c})=>({8:c}),({uniqueId:c})=>c?256:0]},$$scope:{ctx:i}}}),{c(){e=m("form"),t=m("div"),n=m("h5"),l=y(`Type your password to confirm changing your email address import{S as G,i as I,s as J,F as M,c as S,m as L,t as h,a as v,d as z,C as N,E as R,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as y,b as C,f as p,r as T,h as g,u as P,v as K,y as E,x as O,z as F}from"./index-6c8f1731.js";function Q(i){let e,t,n,l,s,o,f,a,r,u,k,$,d=i[3]&&H(i);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:c})=>({8:c}),({uniqueId:c})=>c?256:0]},$$scope:{ctx:i}}}),{c(){e=m("form"),t=m("div"),n=m("h5"),l=y(`Type your password to confirm changing your email address
`),d&&d.c(),s=C(),S(o.$$.fragment),f=C(),a=m("button"),r=m("span"),r.textContent="Confirm new email",p(t,"class","content txt-center m-b-base"),p(r,"class","txt"),p(a,"type","submit"),p(a,"class","btn btn-lg btn-block"),a.disabled=i[1],T(a,"btn-loading",i[1])},m(c,w){_(c,e,w),g(e,t),g(t,n),g(n,l),d&&d.m(n,null),g(e,s),L(o,e,null),g(e,f),g(e,a),g(a,r),u=!0,k||($=P(e,"submit",K(i[4])),k=!0)},p(c,w){c[3]?d?d.p(c,w):(d=H(c),d.c(),d.m(n,null)):d&&(d.d(1),d=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:c}),o.$set(q),(!u||w&2)&&(a.disabled=c[1]),(!u||w&2)&&T(a,"btn-loading",c[1])},i(c){u||(h(o.$$.fragment,c),u=!0)},o(c){v(o.$$.fragment,c),u=!1},d(c){c&&b(e),d&&d.d(),z(o),k=!1,$()}}}function U(i){let e,t,n,l,s;return{c(){e=m("div"),e.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully changed the user email address.</p> <p>You can now sign in with your new email address.</p></div>',t=C(),n=m("button"),n.textContent="Close",p(e,"class","alert alert-success"),p(n,"type","button"),p(n,"class","btn btn-transparent btn-block")},m(o,f){_(o,e,f),_(o,t,f),_(o,n,f),l||(s=P(n,"click",i[6]),l=!0)},p:E,i:E,o:E,d(o){o&&(b(e),b(t),b(n)),l=!1,s()}}}function H(i){let e,t,n;return{c(){e=y("to "),t=m("strong"),n=y(i[3]),p(t,"class","txt-nowrap")},m(l,s){_(l,e,s),_(l,t,s),g(t,n)},p(l,s){s&8&&O(n,l[3])},d(l){l&&(b(e),b(t))}}}function V(i){let e,t,n,l,s,o,f,a;return{c(){e=m("label"),t=y("Password"),l=C(),s=m("input"),p(e,"for",n=i[8]),p(s,"type","password"),p(s,"id",o=i[8]),s.required=!0,s.autofocus=!0},m(r,u){_(r,e,u),g(e,t),_(r,l,u),_(r,s,u),F(s,i[0]),s.focus(),f||(a=P(s,"input",i[7]),f=!0)},p(r,u){u&256&&n!==(n=r[8])&&p(e,"for",n),u&256&&o!==(o=r[8])&&p(s,"id",o),u&1&&s.value!==r[0]&&F(s,r[0])},d(r){r&&(b(e),b(l),b(s)),f=!1,a()}}}function X(i){let e,t,n,l;const s=[U,Q],o=[];function f(a,r){return a[2]?0:1}return e=f(i),t=o[e]=s[e](i),{c(){t.c(),n=R()},m(a,r){o[e].m(a,r),_(a,n,r),l=!0},p(a,r){let u=e;e=f(a),e===u?o[e].p(a,r):(W(),v(o[u],1,1,()=>{o[u]=null}),Y(),t=o[e],t?t.p(a,r):(t=o[e]=s[e](a),t.c()),h(t,1),t.m(n.parentNode,n))},i(a){l||(h(t),l=!0)},o(a){v(t),l=!1},d(a){a&&b(n),o[e].d(a)}}}function Z(i){let e,t;return e=new M({props:{nobranding:!0,$$slots:{default:[X]},$$scope:{ctx:i}}}),{c(){S(e.$$.fragment)},m(n,l){L(e,n,l),t=!0},p(n,[l]){const s={};l&527&&(s.$$scope={dirty:l,ctx:n}),e.$set(s)},i(n){t||(h(e.$$.fragment,n),t=!0)},o(n){v(e.$$.fragment,n),t=!1},d(n){z(e,n)}}}function x(i,e,t){let n,{params:l}=e,s="",o=!1,f=!1;async function a(){if(o)return;t(1,o=!0);const k=new j("../");try{const $=A(l==null?void 0:l.token);await k.collection($.collectionId).confirmEmailChange(l==null?void 0:l.token,s),t(2,f=!0)}catch($){B.error($)}t(1,o=!1)}const r=()=>window.close();function u(){s=this.value,t(0,s)}return i.$$set=k=>{"params"in k&&t(5,l=k.params)},i.$$.update=()=>{i.$$.dirty&32&&t(3,n=N.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[s,o,f,n,a,l,r,u]}class te extends G{constructor(e){super(),I(this,e,x,Z,J,{params:5})}}export{te as default}; `),d&&d.c(),s=C(),S(o.$$.fragment),f=C(),a=m("button"),r=m("span"),r.textContent="Confirm new email",p(t,"class","content txt-center m-b-base"),p(r,"class","txt"),p(a,"type","submit"),p(a,"class","btn btn-lg btn-block"),a.disabled=i[1],T(a,"btn-loading",i[1])},m(c,w){_(c,e,w),g(e,t),g(t,n),g(n,l),d&&d.m(n,null),g(e,s),L(o,e,null),g(e,f),g(e,a),g(a,r),u=!0,k||($=P(e,"submit",K(i[4])),k=!0)},p(c,w){c[3]?d?d.p(c,w):(d=H(c),d.c(),d.m(n,null)):d&&(d.d(1),d=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:c}),o.$set(q),(!u||w&2)&&(a.disabled=c[1]),(!u||w&2)&&T(a,"btn-loading",c[1])},i(c){u||(h(o.$$.fragment,c),u=!0)},o(c){v(o.$$.fragment,c),u=!1},d(c){c&&b(e),d&&d.d(),z(o),k=!1,$()}}}function U(i){let e,t,n,l,s;return{c(){e=m("div"),e.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully changed the user email address.</p> <p>You can now sign in with your new email address.</p></div>',t=C(),n=m("button"),n.textContent="Close",p(e,"class","alert alert-success"),p(n,"type","button"),p(n,"class","btn btn-transparent btn-block")},m(o,f){_(o,e,f),_(o,t,f),_(o,n,f),l||(s=P(n,"click",i[6]),l=!0)},p:E,i:E,o:E,d(o){o&&(b(e),b(t),b(n)),l=!1,s()}}}function H(i){let e,t,n;return{c(){e=y("to "),t=m("strong"),n=y(i[3]),p(t,"class","txt-nowrap")},m(l,s){_(l,e,s),_(l,t,s),g(t,n)},p(l,s){s&8&&O(n,l[3])},d(l){l&&(b(e),b(t))}}}function V(i){let e,t,n,l,s,o,f,a;return{c(){e=m("label"),t=y("Password"),l=C(),s=m("input"),p(e,"for",n=i[8]),p(s,"type","password"),p(s,"id",o=i[8]),s.required=!0,s.autofocus=!0},m(r,u){_(r,e,u),g(e,t),_(r,l,u),_(r,s,u),F(s,i[0]),s.focus(),f||(a=P(s,"input",i[7]),f=!0)},p(r,u){u&256&&n!==(n=r[8])&&p(e,"for",n),u&256&&o!==(o=r[8])&&p(s,"id",o),u&1&&s.value!==r[0]&&F(s,r[0])},d(r){r&&(b(e),b(l),b(s)),f=!1,a()}}}function X(i){let e,t,n,l;const s=[U,Q],o=[];function f(a,r){return a[2]?0:1}return e=f(i),t=o[e]=s[e](i),{c(){t.c(),n=R()},m(a,r){o[e].m(a,r),_(a,n,r),l=!0},p(a,r){let u=e;e=f(a),e===u?o[e].p(a,r):(W(),v(o[u],1,1,()=>{o[u]=null}),Y(),t=o[e],t?t.p(a,r):(t=o[e]=s[e](a),t.c()),h(t,1),t.m(n.parentNode,n))},i(a){l||(h(t),l=!0)},o(a){v(t),l=!1},d(a){a&&b(n),o[e].d(a)}}}function Z(i){let e,t;return e=new M({props:{nobranding:!0,$$slots:{default:[X]},$$scope:{ctx:i}}}),{c(){S(e.$$.fragment)},m(n,l){L(e,n,l),t=!0},p(n,[l]){const s={};l&527&&(s.$$scope={dirty:l,ctx:n}),e.$set(s)},i(n){t||(h(e.$$.fragment,n),t=!0)},o(n){v(e.$$.fragment,n),t=!1},d(n){z(e,n)}}}function x(i,e,t){let n,{params:l}=e,s="",o=!1,f=!1;async function a(){if(o)return;t(1,o=!0);const k=new j("../");try{const $=A(l==null?void 0:l.token);await k.collection($.collectionId).confirmEmailChange(l==null?void 0:l.token,s),t(2,f=!0)}catch($){B.error($)}t(1,o=!1)}const r=()=>window.close();function u(){s=this.value,t(0,s)}return i.$$set=k=>{"params"in k&&t(5,l=k.params)},i.$$.update=()=>{i.$$.dirty&32&&t(3,n=N.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[s,o,f,n,a,l,r,u]}class te extends G{constructor(e){super(),I(this,e,x,Z,J,{params:5})}}export{te as default};

View File

@ -1,2 +1,2 @@
import{S as J,i as M,s as W,F as Y,c as H,m as N,t as P,a as y,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as q,b as C,f as p,r as G,h as w,u as S,v as U,y as F,x as V,z as R}from"./index-7d33ef4c.js";function X(a){let e,l,s,n,t,o,c,r,i,u,v,g,k,h,d=a[4]&&I(a);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:a}}}),r=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:a}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=q(`Reset your user password import{S as J,i as M,s as W,F as Y,c as H,m as N,t as P,a as y,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as q,b as C,f as p,r as G,h as w,u as S,v as U,y as F,x as V,z as R}from"./index-6c8f1731.js";function X(a){let e,l,s,n,t,o,c,r,i,u,v,g,k,h,d=a[4]&&I(a);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:a}}}),r=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:a}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=q(`Reset your user password
`),d&&d.c(),t=C(),H(o.$$.fragment),c=C(),H(r.$$.fragment),i=C(),u=b("button"),v=b("span"),v.textContent="Set new password",p(l,"class","content txt-center m-b-base"),p(v,"class","txt"),p(u,"type","submit"),p(u,"class","btn btn-lg btn-block"),u.disabled=a[2],G(u,"btn-loading",a[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,c),N(r,e,null),w(e,i),w(e,u),w(u,v),g=!0,k||(h=S(e,"submit",U(a[5])),k=!0)},p(f,$){f[4]?d?d.p(f,$):(d=I(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const L={};$&3073&&(L.$$scope={dirty:$,ctx:f}),o.$set(L);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),r.$set(z),(!g||$&4)&&(u.disabled=f[2]),(!g||$&4)&&G(u,"btn-loading",f[2])},i(f){g||(P(o.$$.fragment,f),P(r.$$.fragment,f),g=!0)},o(f){y(o.$$.fragment,f),y(r.$$.fragment,f),g=!1},d(f){f&&m(e),d&&d.d(),T(o),T(r),k=!1,h()}}}function Z(a){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully changed the user password.</p> <p>You can now sign in with your new password.</p></div>',l=C(),s=b("button"),s.textContent="Close",p(e,"class","alert alert-success"),p(s,"type","button"),p(s,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,l,c),_(o,s,c),n||(t=S(s,"click",a[7]),n=!0)},p:F,i:F,o:F,d(o){o&&(m(e),m(l),m(s)),n=!1,t()}}}function I(a){let e,l,s;return{c(){e=q("for "),l=b("strong"),s=q(a[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&V(s,n[4])},d(n){n&&(m(e),m(l))}}}function x(a){let e,l,s,n,t,o,c,r;return{c(){e=b("label"),l=q("New password"),n=C(),t=b("input"),p(e,"for",s=a[10]),p(t,"type","password"),p(t,"id",o=a[10]),t.required=!0,t.autofocus=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),R(t,a[0]),t.focus(),c||(r=S(t,"input",a[8]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&(m(e),m(n),m(t)),c=!1,r()}}}function ee(a){let e,l,s,n,t,o,c,r;return{c(){e=b("label"),l=q("New password confirm"),n=C(),t=b("input"),p(e,"for",s=a[10]),p(t,"type","password"),p(t,"id",o=a[10]),t.required=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),R(t,a[1]),c||(r=S(t,"input",a[9]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&2&&t.value!==i[1]&&R(t,i[1])},d(i){i&&(m(e),m(n),m(t)),c=!1,r()}}}function te(a){let e,l,s,n;const t=[Z,X],o=[];function c(r,i){return r[3]?0:1}return e=c(a),l=o[e]=t[e](a),{c(){l.c(),s=A()},m(r,i){o[e].m(r,i),_(r,s,i),n=!0},p(r,i){let u=e;e=c(r),e===u?o[e].p(r,i):(B(),y(o[u],1,1,()=>{o[u]=null}),D(),l=o[e],l?l.p(r,i):(l=o[e]=t[e](r),l.c()),P(l,1),l.m(s.parentNode,s))},i(r){n||(P(l),n=!0)},o(r){y(l),n=!1},d(r){r&&m(s),o[e].d(r)}}}function se(a){let e,l;return e=new Y({props:{nobranding:!0,$$slots:{default:[te]},$$scope:{ctx:a}}}),{c(){H(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(P(e.$$.fragment,s),l=!0)},o(s){y(e.$$.fragment,s),l=!1},d(s){T(e,s)}}}function le(a,e,l){let s,{params:n}=e,t="",o="",c=!1,r=!1;async function i(){if(c)return;l(2,c=!0);const k=new K("../");try{const h=O(n==null?void 0:n.token);await k.collection(h.collectionId).confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,r=!0)}catch(h){Q.error(h)}l(2,c=!1)}const u=()=>window.close();function v(){t=this.value,l(0,t)}function g(){o=this.value,l(1,o)}return a.$$set=k=>{"params"in k&&l(6,n=k.params)},a.$$.update=()=>{a.$$.dirty&64&&l(4,s=j.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,c,r,s,i,n,u,v,g]}class oe extends J{constructor(e){super(),M(this,e,le,se,W,{params:6})}}export{oe as default}; `),d&&d.c(),t=C(),H(o.$$.fragment),c=C(),H(r.$$.fragment),i=C(),u=b("button"),v=b("span"),v.textContent="Set new password",p(l,"class","content txt-center m-b-base"),p(v,"class","txt"),p(u,"type","submit"),p(u,"class","btn btn-lg btn-block"),u.disabled=a[2],G(u,"btn-loading",a[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,c),N(r,e,null),w(e,i),w(e,u),w(u,v),g=!0,k||(h=S(e,"submit",U(a[5])),k=!0)},p(f,$){f[4]?d?d.p(f,$):(d=I(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const L={};$&3073&&(L.$$scope={dirty:$,ctx:f}),o.$set(L);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),r.$set(z),(!g||$&4)&&(u.disabled=f[2]),(!g||$&4)&&G(u,"btn-loading",f[2])},i(f){g||(P(o.$$.fragment,f),P(r.$$.fragment,f),g=!0)},o(f){y(o.$$.fragment,f),y(r.$$.fragment,f),g=!1},d(f){f&&m(e),d&&d.d(),T(o),T(r),k=!1,h()}}}function Z(a){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully changed the user password.</p> <p>You can now sign in with your new password.</p></div>',l=C(),s=b("button"),s.textContent="Close",p(e,"class","alert alert-success"),p(s,"type","button"),p(s,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,l,c),_(o,s,c),n||(t=S(s,"click",a[7]),n=!0)},p:F,i:F,o:F,d(o){o&&(m(e),m(l),m(s)),n=!1,t()}}}function I(a){let e,l,s;return{c(){e=q("for "),l=b("strong"),s=q(a[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&V(s,n[4])},d(n){n&&(m(e),m(l))}}}function x(a){let e,l,s,n,t,o,c,r;return{c(){e=b("label"),l=q("New password"),n=C(),t=b("input"),p(e,"for",s=a[10]),p(t,"type","password"),p(t,"id",o=a[10]),t.required=!0,t.autofocus=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),R(t,a[0]),t.focus(),c||(r=S(t,"input",a[8]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&(m(e),m(n),m(t)),c=!1,r()}}}function ee(a){let e,l,s,n,t,o,c,r;return{c(){e=b("label"),l=q("New password confirm"),n=C(),t=b("input"),p(e,"for",s=a[10]),p(t,"type","password"),p(t,"id",o=a[10]),t.required=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),R(t,a[1]),c||(r=S(t,"input",a[9]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&2&&t.value!==i[1]&&R(t,i[1])},d(i){i&&(m(e),m(n),m(t)),c=!1,r()}}}function te(a){let e,l,s,n;const t=[Z,X],o=[];function c(r,i){return r[3]?0:1}return e=c(a),l=o[e]=t[e](a),{c(){l.c(),s=A()},m(r,i){o[e].m(r,i),_(r,s,i),n=!0},p(r,i){let u=e;e=c(r),e===u?o[e].p(r,i):(B(),y(o[u],1,1,()=>{o[u]=null}),D(),l=o[e],l?l.p(r,i):(l=o[e]=t[e](r),l.c()),P(l,1),l.m(s.parentNode,s))},i(r){n||(P(l),n=!0)},o(r){y(l),n=!1},d(r){r&&m(s),o[e].d(r)}}}function se(a){let e,l;return e=new Y({props:{nobranding:!0,$$slots:{default:[te]},$$scope:{ctx:a}}}),{c(){H(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(P(e.$$.fragment,s),l=!0)},o(s){y(e.$$.fragment,s),l=!1},d(s){T(e,s)}}}function le(a,e,l){let s,{params:n}=e,t="",o="",c=!1,r=!1;async function i(){if(c)return;l(2,c=!0);const k=new K("../");try{const h=O(n==null?void 0:n.token);await k.collection(h.collectionId).confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,r=!0)}catch(h){Q.error(h)}l(2,c=!1)}const u=()=>window.close();function v(){t=this.value,l(0,t)}function g(){o=this.value,l(1,o)}return a.$$set=k=>{"params"in k&&l(6,n=k.params)},a.$$.update=()=>{a.$$.dirty&64&&l(4,s=j.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,c,r,s,i,n,u,v,g]}class oe extends J{constructor(e){super(),M(this,e,le,se,W,{params:6})}}export{oe as default};

View File

@ -1 +1 @@
import{S as v,i as y,s as g,F as w,c as C,m as x,t as $,a as H,d as L,G as P,H as T,E as M,g as a,o as r,e as f,b as _,f as d,u as b,y as p}from"./index-7d33ef4c.js";function S(c){let t,s,e,n,l;return{c(){t=f("div"),t.innerHTML='<div class="icon"><i class="ri-error-warning-line"></i></div> <div class="content txt-bold"><p>Invalid or expired verification token.</p></div>',s=_(),e=f("button"),e.textContent="Close",d(t,"class","alert alert-danger"),d(e,"type","button"),d(e,"class","btn btn-transparent btn-block")},m(i,o){a(i,t,o),a(i,s,o),a(i,e,o),n||(l=b(e,"click",c[4]),n=!0)},p,d(i){i&&(r(t),r(s),r(e)),n=!1,l()}}}function h(c){let t,s,e,n,l;return{c(){t=f("div"),t.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully verified email address.</p></div>',s=_(),e=f("button"),e.textContent="Close",d(t,"class","alert alert-success"),d(e,"type","button"),d(e,"class","btn btn-transparent btn-block")},m(i,o){a(i,t,o),a(i,s,o),a(i,e,o),n||(l=b(e,"click",c[3]),n=!0)},p,d(i){i&&(r(t),r(s),r(e)),n=!1,l()}}}function F(c){let t;return{c(){t=f("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',d(t,"class","txt-center")},m(s,e){a(s,t,e)},p,d(s){s&&r(t)}}}function I(c){let t;function s(l,i){return l[1]?F:l[0]?h:S}let e=s(c),n=e(c);return{c(){n.c(),t=M()},m(l,i){n.m(l,i),a(l,t,i)},p(l,i){e===(e=s(l))&&n?n.p(l,i):(n.d(1),n=e(l),n&&(n.c(),n.m(t.parentNode,t)))},d(l){l&&r(t),n.d(l)}}}function V(c){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[I]},$$scope:{ctx:c}}}),{c(){C(t.$$.fragment)},m(e,n){x(t,e,n),s=!0},p(e,[n]){const l={};n&67&&(l.$$scope={dirty:n,ctx:e}),t.$set(l)},i(e){s||($(t.$$.fragment,e),s=!0)},o(e){H(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function q(c,t,s){let{params:e}=t,n=!1,l=!1;i();async function i(){s(1,l=!0);const u=new P("../");try{const m=T(e==null?void 0:e.token);await u.collection(m.collectionId).confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,l=!1)}const o=()=>window.close(),k=()=>window.close();return c.$$set=u=>{"params"in u&&s(2,e=u.params)},[n,l,e,o,k]}class G extends v{constructor(t){super(),y(this,t,q,V,g,{params:2})}}export{G as default}; import{S as v,i as y,s as g,F as w,c as C,m as x,t as $,a as H,d as L,G as P,H as T,E as M,g as a,o as r,e as f,b as _,f as d,u as b,y as p}from"./index-6c8f1731.js";function S(c){let t,s,e,n,l;return{c(){t=f("div"),t.innerHTML='<div class="icon"><i class="ri-error-warning-line"></i></div> <div class="content txt-bold"><p>Invalid or expired verification token.</p></div>',s=_(),e=f("button"),e.textContent="Close",d(t,"class","alert alert-danger"),d(e,"type","button"),d(e,"class","btn btn-transparent btn-block")},m(i,o){a(i,t,o),a(i,s,o),a(i,e,o),n||(l=b(e,"click",c[4]),n=!0)},p,d(i){i&&(r(t),r(s),r(e)),n=!1,l()}}}function h(c){let t,s,e,n,l;return{c(){t=f("div"),t.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully verified email address.</p></div>',s=_(),e=f("button"),e.textContent="Close",d(t,"class","alert alert-success"),d(e,"type","button"),d(e,"class","btn btn-transparent btn-block")},m(i,o){a(i,t,o),a(i,s,o),a(i,e,o),n||(l=b(e,"click",c[3]),n=!0)},p,d(i){i&&(r(t),r(s),r(e)),n=!1,l()}}}function F(c){let t;return{c(){t=f("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',d(t,"class","txt-center")},m(s,e){a(s,t,e)},p,d(s){s&&r(t)}}}function I(c){let t;function s(l,i){return l[1]?F:l[0]?h:S}let e=s(c),n=e(c);return{c(){n.c(),t=M()},m(l,i){n.m(l,i),a(l,t,i)},p(l,i){e===(e=s(l))&&n?n.p(l,i):(n.d(1),n=e(l),n&&(n.c(),n.m(t.parentNode,t)))},d(l){l&&r(t),n.d(l)}}}function V(c){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[I]},$$scope:{ctx:c}}}),{c(){C(t.$$.fragment)},m(e,n){x(t,e,n),s=!0},p(e,[n]){const l={};n&67&&(l.$$scope={dirty:n,ctx:e}),t.$set(l)},i(e){s||($(t.$$.fragment,e),s=!0)},o(e){H(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function q(c,t,s){let{params:e}=t,n=!1,l=!1;i();async function i(){s(1,l=!0);const u=new P("../");try{const m=T(e==null?void 0:e.token);await u.collection(m.collectionId).confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,l=!1)}const o=()=>window.close(),k=()=>window.close();return c.$$set=u=>{"params"in u&&s(2,e=u.params)},[n,l,e,o,k]}class G extends v{constructor(t){super(),y(this,t,q,V,g,{params:2})}}export{G as default};

View File

@ -1,4 +1,4 @@
import{S as re,i as ae,s as be,N as pe,C as P,e as p,w as y,b as a,c as se,f as u,g as s,h as I,m as ne,x as ue,t as ie,a as ce,o as n,d as le,p as me}from"./index-7d33ef4c.js";import{S as de}from"./SdkTabs-f0532e58.js";function fe(t){var B,U,W,A,H,L,T,q,M,N,j,J;let i,m,c=t[0].name+"",b,d,D,f,_,$,k,l,S,g,C,v,w,h,E,r,R;return l=new de({props:{js:` import{S as re,i as ae,s as be,N as pe,C as P,e as p,w as y,b as a,c as se,f as u,g as s,h as I,m as ne,x as ue,t as ie,a as ce,o as n,d as le,p as me}from"./index-6c8f1731.js";import{S as de}from"./SdkTabs-821a5c7e.js";function fe(t){var B,U,W,A,H,L,T,q,M,N,j,J;let i,m,c=t[0].name+"",b,d,D,f,_,$,k,l,S,g,C,v,w,h,E,r,R;return l=new de({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${t[1]}'); const pb = new PocketBase('${t[1]}');

View File

@ -1,4 +1,4 @@
import{S as Ee,i as Be,s as Se,O as L,e as r,w as v,b as k,c as Ce,f as b,g as d,h as n,m as ye,x as N,P as ve,Q as Re,k as Me,R as Ae,n as We,t as ee,a as te,o as m,d as Te,C as ze,p as He,r as F,u as Oe,N as Ue}from"./index-7d33ef4c.js";import{S as je}from"./SdkTabs-f0532e58.js";function we(o,l,a){const s=o.slice();return s[5]=l[a],s}function $e(o,l,a){const s=o.slice();return s[5]=l[a],s}function qe(o,l){let a,s=l[5].code+"",h,f,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){a=r("button"),h=v(s),f=k(),b(a,"class","tab-item"),F(a,"active",l[1]===l[5].code),this.first=a},m($,q){d($,a,q),n(a,h),n(a,f),i||(p=Oe(a,"click",u),i=!0)},p($,q){l=$,q&4&&s!==(s=l[5].code+"")&&N(h,s),q&6&&F(a,"active",l[1]===l[5].code)},d($){$&&m(a),i=!1,p()}}}function Pe(o,l){let a,s,h,f;return s=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){a=r("div"),Ce(s.$$.fragment),h=k(),b(a,"class","tab-item"),F(a,"active",l[1]===l[5].code),this.first=a},m(i,p){d(i,a,p),ye(s,a,null),n(a,h),f=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),s.$set(u),(!f||p&6)&&F(a,"active",l[1]===l[5].code)},i(i){f||(ee(s.$$.fragment,i),f=!0)},o(i){te(s.$$.fragment,i),f=!1},d(i){i&&m(a),Te(s)}}}function De(o){var pe,ue,be,fe;let l,a,s=o[0].name+"",h,f,i,p,u,$,q,z=o[0].name+"",I,le,K,P,Q,T,G,w,H,ae,O,E,se,J,U=o[0].name+"",V,oe,ne,j,X,B,Y,S,Z,R,x,C,M,g=[],ie=new Map,ce,A,_=[],re=new Map,y;P=new je({props:{js:` import{S as Ee,i as Be,s as Se,O as L,e as r,w as v,b as k,c as Ce,f as b,g as d,h as n,m as ye,x as N,P as ve,Q as Re,k as Me,R as Ae,n as We,t as ee,a as te,o as m,d as Te,C as ze,p as He,r as F,u as Oe,N as Ue}from"./index-6c8f1731.js";import{S as je}from"./SdkTabs-821a5c7e.js";function we(o,l,a){const s=o.slice();return s[5]=l[a],s}function $e(o,l,a){const s=o.slice();return s[5]=l[a],s}function qe(o,l){let a,s=l[5].code+"",h,f,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){a=r("button"),h=v(s),f=k(),b(a,"class","tab-item"),F(a,"active",l[1]===l[5].code),this.first=a},m($,q){d($,a,q),n(a,h),n(a,f),i||(p=Oe(a,"click",u),i=!0)},p($,q){l=$,q&4&&s!==(s=l[5].code+"")&&N(h,s),q&6&&F(a,"active",l[1]===l[5].code)},d($){$&&m(a),i=!1,p()}}}function Pe(o,l){let a,s,h,f;return s=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){a=r("div"),Ce(s.$$.fragment),h=k(),b(a,"class","tab-item"),F(a,"active",l[1]===l[5].code),this.first=a},m(i,p){d(i,a,p),ye(s,a,null),n(a,h),f=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),s.$set(u),(!f||p&6)&&F(a,"active",l[1]===l[5].code)},i(i){f||(ee(s.$$.fragment,i),f=!0)},o(i){te(s.$$.fragment,i),f=!1},d(i){i&&m(a),Te(s)}}}function De(o){var pe,ue,be,fe;let l,a,s=o[0].name+"",h,f,i,p,u,$,q,z=o[0].name+"",I,le,K,P,Q,T,G,w,H,ae,O,E,se,J,U=o[0].name+"",V,oe,ne,j,X,B,Y,S,Z,R,x,C,M,g=[],ie=new Map,ce,A,_=[],re=new Map,y;P=new je({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as Pe,i as $e,s as qe,O as I,e as r,w as g,b as h,c as ve,f as b,g as d,h as n,m as ge,x as L,P as fe,Q as ye,k as Re,R as Be,n as Ce,t as x,a as ee,o as p,d as we,C as Se,p as Te,r as N,u as Me,N as Ae}from"./index-7d33ef4c.js";import{S as Ue}from"./SdkTabs-f0532e58.js";function be(o,s,l){const a=o.slice();return a[5]=s[l],a}function _e(o,s,l){const a=o.slice();return a[5]=s[l],a}function ke(o,s){let l,a=s[5].code+"",_,f,i,u;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=r("button"),_=g(a),f=h(),b(l,"class","tab-item"),N(l,"active",s[1]===s[5].code),this.first=l},m(w,P){d(w,l,P),n(l,_),n(l,f),i||(u=Me(l,"click",m),i=!0)},p(w,P){s=w,P&4&&a!==(a=s[5].code+"")&&L(_,a),P&6&&N(l,"active",s[1]===s[5].code)},d(w){w&&p(l),i=!1,u()}}}function he(o,s){let l,a,_,f;return a=new Ae({props:{content:s[5].body}}),{key:o,first:null,c(){l=r("div"),ve(a.$$.fragment),_=h(),b(l,"class","tab-item"),N(l,"active",s[1]===s[5].code),this.first=l},m(i,u){d(i,l,u),ge(a,l,null),n(l,_),f=!0},p(i,u){s=i;const m={};u&4&&(m.content=s[5].body),a.$set(m),(!f||u&6)&&N(l,"active",s[1]===s[5].code)},i(i){f||(x(a.$$.fragment,i),f=!0)},o(i){ee(a.$$.fragment,i),f=!1},d(i){i&&p(l),we(a)}}}function je(o){var de,pe;let s,l,a=o[0].name+"",_,f,i,u,m,w,P,D=o[0].name+"",Q,te,z,$,G,B,J,q,H,se,O,C,le,K,E=o[0].name+"",V,ae,W,S,X,T,Y,M,Z,y,A,v=[],oe=new Map,ne,U,k=[],ie=new Map,R;$=new Ue({props:{js:` import{S as Pe,i as $e,s as qe,O as I,e as r,w as g,b as h,c as ve,f as b,g as d,h as n,m as ge,x as L,P as fe,Q as ye,k as Re,R as Be,n as Ce,t as x,a as ee,o as p,d as we,C as Se,p as Te,r as N,u as Me,N as Ae}from"./index-6c8f1731.js";import{S as Ue}from"./SdkTabs-821a5c7e.js";function be(o,s,l){const a=o.slice();return a[5]=s[l],a}function _e(o,s,l){const a=o.slice();return a[5]=s[l],a}function ke(o,s){let l,a=s[5].code+"",_,f,i,u;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=r("button"),_=g(a),f=h(),b(l,"class","tab-item"),N(l,"active",s[1]===s[5].code),this.first=l},m(w,P){d(w,l,P),n(l,_),n(l,f),i||(u=Me(l,"click",m),i=!0)},p(w,P){s=w,P&4&&a!==(a=s[5].code+"")&&L(_,a),P&6&&N(l,"active",s[1]===s[5].code)},d(w){w&&p(l),i=!1,u()}}}function he(o,s){let l,a,_,f;return a=new Ae({props:{content:s[5].body}}),{key:o,first:null,c(){l=r("div"),ve(a.$$.fragment),_=h(),b(l,"class","tab-item"),N(l,"active",s[1]===s[5].code),this.first=l},m(i,u){d(i,l,u),ge(a,l,null),n(l,_),f=!0},p(i,u){s=i;const m={};u&4&&(m.content=s[5].body),a.$set(m),(!f||u&6)&&N(l,"active",s[1]===s[5].code)},i(i){f||(x(a.$$.fragment,i),f=!0)},o(i){ee(a.$$.fragment,i),f=!1},d(i){i&&p(l),we(a)}}}function je(o){var de,pe;let s,l,a=o[0].name+"",_,f,i,u,m,w,P,D=o[0].name+"",Q,te,z,$,G,B,J,q,H,se,O,C,le,K,E=o[0].name+"",V,ae,W,S,X,T,Y,M,Z,y,A,v=[],oe=new Map,ne,U,k=[],ie=new Map,R;$=new Ue({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as qe,i as we,s as Pe,O as F,e as r,w as g,b as h,c as ve,f as b,g as d,h as n,m as ge,x as I,P as pe,Q as ye,k as Be,R as Ce,n as Se,t as x,a as ee,o as f,d as $e,C as Te,p as Re,r as L,u as Ve,N as Me}from"./index-7d33ef4c.js";import{S as Ae}from"./SdkTabs-f0532e58.js";function be(o,l,s){const a=o.slice();return a[5]=l[s],a}function _e(o,l,s){const a=o.slice();return a[5]=l[s],a}function ke(o,l){let s,a=l[5].code+"",_,p,i,u;function m(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),_=g(a),p=h(),b(s,"class","tab-item"),L(s,"active",l[1]===l[5].code),this.first=s},m($,q){d($,s,q),n(s,_),n(s,p),i||(u=Ve(s,"click",m),i=!0)},p($,q){l=$,q&4&&a!==(a=l[5].code+"")&&I(_,a),q&6&&L(s,"active",l[1]===l[5].code)},d($){$&&f(s),i=!1,u()}}}function he(o,l){let s,a,_,p;return a=new Me({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ve(a.$$.fragment),_=h(),b(s,"class","tab-item"),L(s,"active",l[1]===l[5].code),this.first=s},m(i,u){d(i,s,u),ge(a,s,null),n(s,_),p=!0},p(i,u){l=i;const m={};u&4&&(m.content=l[5].body),a.$set(m),(!p||u&6)&&L(s,"active",l[1]===l[5].code)},i(i){p||(x(a.$$.fragment,i),p=!0)},o(i){ee(a.$$.fragment,i),p=!1},d(i){i&&f(s),$e(a)}}}function Ue(o){var de,fe;let l,s,a=o[0].name+"",_,p,i,u,m,$,q,j=o[0].name+"",N,te,Q,w,z,C,G,P,D,le,H,S,se,J,O=o[0].name+"",K,ae,W,T,X,R,Y,V,Z,y,M,v=[],oe=new Map,ne,A,k=[],ie=new Map,B;w=new Ae({props:{js:` import{S as qe,i as we,s as Pe,O as F,e as r,w as g,b as h,c as ve,f as b,g as d,h as n,m as ge,x as I,P as pe,Q as ye,k as Be,R as Ce,n as Se,t as x,a as ee,o as f,d as $e,C as Te,p as Re,r as L,u as Ve,N as Me}from"./index-6c8f1731.js";import{S as Ae}from"./SdkTabs-821a5c7e.js";function be(o,l,s){const a=o.slice();return a[5]=l[s],a}function _e(o,l,s){const a=o.slice();return a[5]=l[s],a}function ke(o,l){let s,a=l[5].code+"",_,p,i,u;function m(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),_=g(a),p=h(),b(s,"class","tab-item"),L(s,"active",l[1]===l[5].code),this.first=s},m($,q){d($,s,q),n(s,_),n(s,p),i||(u=Ve(s,"click",m),i=!0)},p($,q){l=$,q&4&&a!==(a=l[5].code+"")&&I(_,a),q&6&&L(s,"active",l[1]===l[5].code)},d($){$&&f(s),i=!1,u()}}}function he(o,l){let s,a,_,p;return a=new Me({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ve(a.$$.fragment),_=h(),b(s,"class","tab-item"),L(s,"active",l[1]===l[5].code),this.first=s},m(i,u){d(i,s,u),ge(a,s,null),n(s,_),p=!0},p(i,u){l=i;const m={};u&4&&(m.content=l[5].body),a.$set(m),(!p||u&6)&&L(s,"active",l[1]===l[5].code)},i(i){p||(x(a.$$.fragment,i),p=!0)},o(i){ee(a.$$.fragment,i),p=!1},d(i){i&&f(s),$e(a)}}}function Ue(o){var de,fe;let l,s,a=o[0].name+"",_,p,i,u,m,$,q,j=o[0].name+"",N,te,Q,w,z,C,G,P,D,le,H,S,se,J,O=o[0].name+"",K,ae,W,T,X,R,Y,V,Z,y,M,v=[],oe=new Map,ne,A,k=[],ie=new Map,B;w=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1 +1 @@
import{S as B,i as F,s as J,O as j,e as v,b as S,f as h,g as w,h as m,P as D,Q as O,k as Q,R as Y,n as z,t as N,a as P,o as C,w as E,r as y,u as A,x as q,N as G,c as H,m as L,d as U}from"./index-7d33ef4c.js";function K(o,e,l){const s=o.slice();return s[6]=e[l],s}function R(o,e,l){const s=o.slice();return s[6]=e[l],s}function T(o,e){let l,s,g=e[6].title+"",r,i,n,k;function c(){return e[5](e[6])}return{key:o,first:null,c(){l=v("button"),s=v("div"),r=E(g),i=S(),h(s,"class","txt"),h(l,"class","tab-item svelte-1maocj6"),y(l,"active",e[1]===e[6].language),this.first=l},m(_,f){w(_,l,f),m(l,s),m(s,r),m(l,i),n||(k=A(l,"click",c),n=!0)},p(_,f){e=_,f&4&&g!==(g=e[6].title+"")&&q(r,g),f&6&&y(l,"active",e[1]===e[6].language)},d(_){_&&C(l),n=!1,k()}}}function I(o,e){let l,s,g,r,i,n,k=e[6].title+"",c,_,f,p,d;return s=new G({props:{language:e[6].language,content:e[6].content}}),{key:o,first:null,c(){l=v("div"),H(s.$$.fragment),g=S(),r=v("div"),i=v("em"),n=v("a"),c=E(k),_=E(" SDK"),p=S(),h(n,"href",f=e[6].url),h(n,"target","_blank"),h(n,"rel","noopener noreferrer"),h(i,"class","txt-sm txt-hint"),h(r,"class","txt-right"),h(l,"class","tab-item svelte-1maocj6"),y(l,"active",e[1]===e[6].language),this.first=l},m(b,t){w(b,l,t),L(s,l,null),m(l,g),m(l,r),m(r,i),m(i,n),m(n,c),m(n,_),m(l,p),d=!0},p(b,t){e=b;const a={};t&4&&(a.language=e[6].language),t&4&&(a.content=e[6].content),s.$set(a),(!d||t&4)&&k!==(k=e[6].title+"")&&q(c,k),(!d||t&4&&f!==(f=e[6].url))&&h(n,"href",f),(!d||t&6)&&y(l,"active",e[1]===e[6].language)},i(b){d||(N(s.$$.fragment,b),d=!0)},o(b){P(s.$$.fragment,b),d=!1},d(b){b&&C(l),U(s)}}}function V(o){let e,l,s=[],g=new Map,r,i,n=[],k=new Map,c,_,f=j(o[2]);const p=t=>t[6].language;for(let t=0;t<f.length;t+=1){let a=R(o,f,t),u=p(a);g.set(u,s[t]=T(u,a))}let d=j(o[2]);const b=t=>t[6].language;for(let t=0;t<d.length;t+=1){let a=K(o,d,t),u=b(a);k.set(u,n[t]=I(u,a))}return{c(){e=v("div"),l=v("div");for(let t=0;t<s.length;t+=1)s[t].c();r=S(),i=v("div");for(let t=0;t<n.length;t+=1)n[t].c();h(l,"class","tabs-header compact combined left"),h(i,"class","tabs-content"),h(e,"class",c="tabs sdk-tabs "+o[0]+" svelte-1maocj6")},m(t,a){w(t,e,a),m(e,l);for(let u=0;u<s.length;u+=1)s[u]&&s[u].m(l,null);m(e,r),m(e,i);for(let u=0;u<n.length;u+=1)n[u]&&n[u].m(i,null);_=!0},p(t,[a]){a&6&&(f=j(t[2]),s=D(s,a,p,1,t,f,g,l,O,T,null,R)),a&6&&(d=j(t[2]),Q(),n=D(n,a,b,1,t,d,k,i,Y,I,null,K),z()),(!_||a&1&&c!==(c="tabs sdk-tabs "+t[0]+" svelte-1maocj6"))&&h(e,"class",c)},i(t){if(!_){for(let a=0;a<d.length;a+=1)N(n[a]);_=!0}},o(t){for(let a=0;a<n.length;a+=1)P(n[a]);_=!1},d(t){t&&C(e);for(let a=0;a<s.length;a+=1)s[a].d();for(let a=0;a<n.length;a+=1)n[a].d()}}}const M="pb_sdk_preference";function W(o,e,l){let s,{class:g="m-b-sm"}=e,{js:r=""}=e,{dart:i=""}=e,n=localStorage.getItem(M)||"javascript";const k=c=>l(1,n=c.language);return o.$$set=c=>{"class"in c&&l(0,g=c.class),"js"in c&&l(3,r=c.js),"dart"in c&&l(4,i=c.dart)},o.$$.update=()=>{o.$$.dirty&2&&n&&localStorage.setItem(M,n),o.$$.dirty&24&&l(2,s=[{title:"JavaScript",language:"javascript",content:r,url:"https://github.com/pocketbase/js-sdk"},{title:"Dart",language:"dart",content:i,url:"https://github.com/pocketbase/dart-sdk"}])},[g,n,s,r,i,k]}class Z extends B{constructor(e){super(),F(this,e,W,V,J,{class:0,js:3,dart:4})}}export{Z as S}; import{S as B,i as F,s as J,O as j,e as v,b as S,f as h,g as w,h as m,P as D,Q as O,k as Q,R as Y,n as z,t as N,a as P,o as C,w as E,r as y,u as A,x as q,N as G,c as H,m as L,d as U}from"./index-6c8f1731.js";function K(o,e,l){const s=o.slice();return s[6]=e[l],s}function R(o,e,l){const s=o.slice();return s[6]=e[l],s}function T(o,e){let l,s,g=e[6].title+"",r,i,n,k;function c(){return e[5](e[6])}return{key:o,first:null,c(){l=v("button"),s=v("div"),r=E(g),i=S(),h(s,"class","txt"),h(l,"class","tab-item svelte-1maocj6"),y(l,"active",e[1]===e[6].language),this.first=l},m(_,f){w(_,l,f),m(l,s),m(s,r),m(l,i),n||(k=A(l,"click",c),n=!0)},p(_,f){e=_,f&4&&g!==(g=e[6].title+"")&&q(r,g),f&6&&y(l,"active",e[1]===e[6].language)},d(_){_&&C(l),n=!1,k()}}}function I(o,e){let l,s,g,r,i,n,k=e[6].title+"",c,_,f,p,d;return s=new G({props:{language:e[6].language,content:e[6].content}}),{key:o,first:null,c(){l=v("div"),H(s.$$.fragment),g=S(),r=v("div"),i=v("em"),n=v("a"),c=E(k),_=E(" SDK"),p=S(),h(n,"href",f=e[6].url),h(n,"target","_blank"),h(n,"rel","noopener noreferrer"),h(i,"class","txt-sm txt-hint"),h(r,"class","txt-right"),h(l,"class","tab-item svelte-1maocj6"),y(l,"active",e[1]===e[6].language),this.first=l},m(b,t){w(b,l,t),L(s,l,null),m(l,g),m(l,r),m(r,i),m(i,n),m(n,c),m(n,_),m(l,p),d=!0},p(b,t){e=b;const a={};t&4&&(a.language=e[6].language),t&4&&(a.content=e[6].content),s.$set(a),(!d||t&4)&&k!==(k=e[6].title+"")&&q(c,k),(!d||t&4&&f!==(f=e[6].url))&&h(n,"href",f),(!d||t&6)&&y(l,"active",e[1]===e[6].language)},i(b){d||(N(s.$$.fragment,b),d=!0)},o(b){P(s.$$.fragment,b),d=!1},d(b){b&&C(l),U(s)}}}function V(o){let e,l,s=[],g=new Map,r,i,n=[],k=new Map,c,_,f=j(o[2]);const p=t=>t[6].language;for(let t=0;t<f.length;t+=1){let a=R(o,f,t),u=p(a);g.set(u,s[t]=T(u,a))}let d=j(o[2]);const b=t=>t[6].language;for(let t=0;t<d.length;t+=1){let a=K(o,d,t),u=b(a);k.set(u,n[t]=I(u,a))}return{c(){e=v("div"),l=v("div");for(let t=0;t<s.length;t+=1)s[t].c();r=S(),i=v("div");for(let t=0;t<n.length;t+=1)n[t].c();h(l,"class","tabs-header compact combined left"),h(i,"class","tabs-content"),h(e,"class",c="tabs sdk-tabs "+o[0]+" svelte-1maocj6")},m(t,a){w(t,e,a),m(e,l);for(let u=0;u<s.length;u+=1)s[u]&&s[u].m(l,null);m(e,r),m(e,i);for(let u=0;u<n.length;u+=1)n[u]&&n[u].m(i,null);_=!0},p(t,[a]){a&6&&(f=j(t[2]),s=D(s,a,p,1,t,f,g,l,O,T,null,R)),a&6&&(d=j(t[2]),Q(),n=D(n,a,b,1,t,d,k,i,Y,I,null,K),z()),(!_||a&1&&c!==(c="tabs sdk-tabs "+t[0]+" svelte-1maocj6"))&&h(e,"class",c)},i(t){if(!_){for(let a=0;a<d.length;a+=1)N(n[a]);_=!0}},o(t){for(let a=0;a<n.length;a+=1)P(n[a]);_=!1},d(t){t&&C(e);for(let a=0;a<s.length;a+=1)s[a].d();for(let a=0;a<n.length;a+=1)n[a].d()}}}const M="pb_sdk_preference";function W(o,e,l){let s,{class:g="m-b-sm"}=e,{js:r=""}=e,{dart:i=""}=e,n=localStorage.getItem(M)||"javascript";const k=c=>l(1,n=c.language);return o.$$set=c=>{"class"in c&&l(0,g=c.class),"js"in c&&l(3,r=c.js),"dart"in c&&l(4,i=c.dart)},o.$$.update=()=>{o.$$.dirty&2&&n&&localStorage.setItem(M,n),o.$$.dirty&24&&l(2,s=[{title:"JavaScript",language:"javascript",content:r,url:"https://github.com/pocketbase/js-sdk"},{title:"Dart",language:"dart",content:i,url:"https://github.com/pocketbase/dart-sdk"}])},[g,n,s,r,i,k]}class Z extends B{constructor(e){super(),F(this,e,W,V,J,{class:0,js:3,dart:4})}}export{Z as S};

View File

@ -1,4 +1,4 @@
import{S as Oe,i as De,s as Me,O as j,e as i,w as g,b as f,c as Be,f as b,g as d,h as a,m as Ue,x as I,P as Ae,Q as We,k as ze,R as He,n as Le,t as oe,a as ae,o as u,d as qe,C as Re,p as je,r as N,u as Ie,N as Ne}from"./index-7d33ef4c.js";import{S as Ke}from"./SdkTabs-f0532e58.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Te(n,l,o){const s=n.slice();return s[5]=l[o],s}function Ee(n,l){let o,s=l[5].code+"",_,h,c,p;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=i("button"),_=g(s),h=f(),b(o,"class","tab-item"),N(o,"active",l[1]===l[5].code),this.first=o},m($,P){d($,o,P),a(o,_),a(o,h),c||(p=Ie(o,"click",m),c=!0)},p($,P){l=$,P&4&&s!==(s=l[5].code+"")&&I(_,s),P&6&&N(o,"active",l[1]===l[5].code)},d($){$&&u(o),c=!1,p()}}}function Se(n,l){let o,s,_,h;return s=new Ne({props:{content:l[5].body}}),{key:n,first:null,c(){o=i("div"),Be(s.$$.fragment),_=f(),b(o,"class","tab-item"),N(o,"active",l[1]===l[5].code),this.first=o},m(c,p){d(c,o,p),Ue(s,o,null),a(o,_),h=!0},p(c,p){l=c;const m={};p&4&&(m.content=l[5].body),s.$set(m),(!h||p&6)&&N(o,"active",l[1]===l[5].code)},i(c){h||(oe(s.$$.fragment,c),h=!0)},o(c){ae(s.$$.fragment,c),h=!1},d(c){c&&u(o),qe(s)}}}function Qe(n){var _e,ke,ge,ve;let l,o,s=n[0].name+"",_,h,c,p,m,$,P,M=n[0].name+"",K,se,ne,Q,F,A,G,E,J,w,W,ie,z,y,ce,V,H=n[0].name+"",X,re,Y,de,Z,ue,L,x,S,ee,B,te,U,le,C,q,v=[],pe=new Map,me,O,k=[],be=new Map,T;A=new Ke({props:{js:` import{S as Oe,i as De,s as Me,O as j,e as i,w as g,b as f,c as Be,f as b,g as d,h as a,m as Ue,x as I,P as Ae,Q as We,k as ze,R as He,n as Le,t as oe,a as ae,o as u,d as qe,C as Re,p as je,r as N,u as Ie,N as Ne}from"./index-6c8f1731.js";import{S as Ke}from"./SdkTabs-821a5c7e.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Te(n,l,o){const s=n.slice();return s[5]=l[o],s}function Ee(n,l){let o,s=l[5].code+"",_,h,c,p;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=i("button"),_=g(s),h=f(),b(o,"class","tab-item"),N(o,"active",l[1]===l[5].code),this.first=o},m($,P){d($,o,P),a(o,_),a(o,h),c||(p=Ie(o,"click",m),c=!0)},p($,P){l=$,P&4&&s!==(s=l[5].code+"")&&I(_,s),P&6&&N(o,"active",l[1]===l[5].code)},d($){$&&u(o),c=!1,p()}}}function Se(n,l){let o,s,_,h;return s=new Ne({props:{content:l[5].body}}),{key:n,first:null,c(){o=i("div"),Be(s.$$.fragment),_=f(),b(o,"class","tab-item"),N(o,"active",l[1]===l[5].code),this.first=o},m(c,p){d(c,o,p),Ue(s,o,null),a(o,_),h=!0},p(c,p){l=c;const m={};p&4&&(m.content=l[5].body),s.$set(m),(!h||p&6)&&N(o,"active",l[1]===l[5].code)},i(c){h||(oe(s.$$.fragment,c),h=!0)},o(c){ae(s.$$.fragment,c),h=!1},d(c){c&&u(o),qe(s)}}}function Qe(n){var _e,ke,ge,ve;let l,o,s=n[0].name+"",_,h,c,p,m,$,P,M=n[0].name+"",K,se,ne,Q,F,A,G,E,J,w,W,ie,z,y,ce,V,H=n[0].name+"",X,re,Y,de,Z,ue,L,x,S,ee,B,te,U,le,C,q,v=[],pe=new Map,me,O,k=[],be=new Map,T;A=new Ke({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}'); const pb = new PocketBase('${n[3]}');

View File

@ -1,4 +1,4 @@
import{S as $t,i as Mt,s as qt,C as I,O as Z,N as Ot,e as r,w as b,b as f,c as he,f as v,g as i,h as s,m as ye,x as J,P as Ee,Q as _t,k as Ht,R as Rt,n as Dt,t as ce,a as pe,o as d,d as ke,p as Lt,r as ve,u as Pt,y as ee}from"./index-7d33ef4c.js";import{S as Ft}from"./SdkTabs-f0532e58.js";import{F as Nt}from"./FieldsQueryParam-f6b769d1.js";function ht(c,e,t){const n=c.slice();return n[8]=e[t],n}function yt(c,e,t){const n=c.slice();return n[8]=e[t],n}function kt(c,e,t){const n=c.slice();return n[13]=e[t],n}function vt(c){let e;return{c(){e=r("p"),e.innerHTML=`<em>Note that in case of a password change all previously issued tokens for the current record import{S as $t,i as Mt,s as qt,C as I,O as Z,N as Ot,e as r,w as b,b as f,c as he,f as v,g as i,h as s,m as ye,x as J,P as Ee,Q as _t,k as Ht,R as Rt,n as Dt,t as ce,a as pe,o as d,d as ke,p as Lt,r as ve,u as Pt,y as ee}from"./index-6c8f1731.js";import{S as Ft}from"./SdkTabs-821a5c7e.js";import{F as Nt}from"./FieldsQueryParam-2d478986.js";function ht(c,e,t){const n=c.slice();return n[8]=e[t],n}function yt(c,e,t){const n=c.slice();return n[8]=e[t],n}function kt(c,e,t){const n=c.slice();return n[13]=e[t],n}function vt(c){let e;return{c(){e=r("p"),e.innerHTML=`<em>Note that in case of a password change all previously issued tokens for the current record
will be automatically invalidated and if you want your user to remain signed in you need to will be automatically invalidated and if you want your user to remain signed in you need to
reauthenticate manually after the update call.</em>`},m(t,n){i(t,e,n)},d(t){t&&d(e)}}}function gt(c){let e;return{c(){e=r("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",v(e,"class","txt-hint txt-sm txt-right")},m(t,n){i(t,e,n)},d(t){t&&d(e)}}}function wt(c){let e,t,n,u,m,o,p,h,w,S,g,$,P,E,M,U,F;return{c(){e=r("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',t=f(),n=r("tr"),n.innerHTML='<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>username</span></div></td> <td><span class="label">String</span></td> <td>The username of the auth record.</td>',u=f(),m=r("tr"),m.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>email</span></div></td> <td><span class="label">String</span></td> <td>The auth record email address. reauthenticate manually after the update call.</em>`},m(t,n){i(t,e,n)},d(t){t&&d(e)}}}function gt(c){let e;return{c(){e=r("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",v(e,"class","txt-hint txt-sm txt-right")},m(t,n){i(t,e,n)},d(t){t&&d(e)}}}function wt(c){let e,t,n,u,m,o,p,h,w,S,g,$,P,E,M,U,F;return{c(){e=r("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',t=f(),n=r("tr"),n.innerHTML='<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>username</span></div></td> <td><span class="label">String</span></td> <td>The username of the auth record.</td>',u=f(),m=r("tr"),m.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>email</span></div></td> <td><span class="label">String</span></td> <td>The auth record email address.
<br/> <br/>

View File

@ -1,4 +1,4 @@
import{S as lt,i as nt,s as st,N as tt,O as K,e as o,w as _,b as m,c as W,f as b,g as r,h as l,m as X,x as ve,P as Je,Q as ot,k as at,R as it,n as rt,t as Q,a as U,o as d,d as Y,C as Ke,p as dt,r as Z,u as ct}from"./index-7d33ef4c.js";import{S as pt}from"./SdkTabs-f0532e58.js";import{F as ut}from"./FieldsQueryParam-f6b769d1.js";function We(a,n,s){const i=a.slice();return i[6]=n[s],i}function Xe(a,n,s){const i=a.slice();return i[6]=n[s],i}function Ye(a){let n;return{c(){n=o("p"),n.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",b(n,"class","txt-hint txt-sm txt-right")},m(s,i){r(s,n,i)},d(s){s&&d(n)}}}function Ze(a,n){let s,i,v;function p(){return n[5](n[6])}return{key:a,first:null,c(){s=o("button"),s.textContent=`${n[6].code} `,b(s,"class","tab-item"),Z(s,"active",n[2]===n[6].code),this.first=s},m(c,f){r(c,s,f),i||(v=ct(s,"click",p),i=!0)},p(c,f){n=c,f&20&&Z(s,"active",n[2]===n[6].code)},d(c){c&&d(s),i=!1,v()}}}function et(a,n){let s,i,v,p;return i=new tt({props:{content:n[6].body}}),{key:a,first:null,c(){s=o("div"),W(i.$$.fragment),v=m(),b(s,"class","tab-item"),Z(s,"active",n[2]===n[6].code),this.first=s},m(c,f){r(c,s,f),X(i,s,null),l(s,v),p=!0},p(c,f){n=c,(!p||f&20)&&Z(s,"active",n[2]===n[6].code)},i(c){p||(Q(i.$$.fragment,c),p=!0)},o(c){U(i.$$.fragment,c),p=!1},d(c){c&&d(s),Y(i)}}}function ft(a){var je,Ve;let n,s,i=a[0].name+"",v,p,c,f,w,C,ee,j=a[0].name+"",te,$e,le,F,ne,x,se,$,V,ye,z,T,we,oe,G=a[0].name+"",ae,Ce,ie,Fe,re,B,de,A,ce,I,pe,R,ue,Re,M,O,fe,Oe,me,Pe,h,De,E,Te,Ee,Se,be,xe,_e,Be,Ae,Ie,he,Me,qe,S,ke,q,ge,P,H,y=[],He=new Map,Le,L,k=[],Ne=new Map,D;F=new pt({props:{js:` import{S as lt,i as nt,s as st,N as tt,O as K,e as o,w as _,b as m,c as W,f as b,g as r,h as l,m as X,x as ve,P as Je,Q as ot,k as at,R as it,n as rt,t as Q,a as U,o as d,d as Y,C as Ke,p as dt,r as Z,u as ct}from"./index-6c8f1731.js";import{S as pt}from"./SdkTabs-821a5c7e.js";import{F as ut}from"./FieldsQueryParam-2d478986.js";function We(a,n,s){const i=a.slice();return i[6]=n[s],i}function Xe(a,n,s){const i=a.slice();return i[6]=n[s],i}function Ye(a){let n;return{c(){n=o("p"),n.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",b(n,"class","txt-hint txt-sm txt-right")},m(s,i){r(s,n,i)},d(s){s&&d(n)}}}function Ze(a,n){let s,i,v;function p(){return n[5](n[6])}return{key:a,first:null,c(){s=o("button"),s.textContent=`${n[6].code} `,b(s,"class","tab-item"),Z(s,"active",n[2]===n[6].code),this.first=s},m(c,f){r(c,s,f),i||(v=ct(s,"click",p),i=!0)},p(c,f){n=c,f&20&&Z(s,"active",n[2]===n[6].code)},d(c){c&&d(s),i=!1,v()}}}function et(a,n){let s,i,v,p;return i=new tt({props:{content:n[6].body}}),{key:a,first:null,c(){s=o("div"),W(i.$$.fragment),v=m(),b(s,"class","tab-item"),Z(s,"active",n[2]===n[6].code),this.first=s},m(c,f){r(c,s,f),X(i,s,null),l(s,v),p=!0},p(c,f){n=c,(!p||f&20)&&Z(s,"active",n[2]===n[6].code)},i(c){p||(Q(i.$$.fragment,c),p=!0)},o(c){U(i.$$.fragment,c),p=!1},d(c){c&&d(s),Y(i)}}}function ft(a){var je,Ve;let n,s,i=a[0].name+"",v,p,c,f,w,C,ee,j=a[0].name+"",te,$e,le,F,ne,x,se,$,V,ye,z,T,we,oe,G=a[0].name+"",ae,Ce,ie,Fe,re,B,de,A,ce,I,pe,R,ue,Re,M,O,fe,Oe,me,Pe,h,De,E,Te,Ee,Se,be,xe,_e,Be,Ae,Ie,he,Me,qe,S,ke,q,ge,P,H,y=[],He=new Map,Le,L,k=[],Ne=new Map,D;F=new pt({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

1
ui/dist/assets/index-0a754981.css vendored Normal file

File diff suppressed because one or more lines are too long

13
ui/dist/assets/index-102ce751.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

161
ui/dist/assets/index-6c8f1731.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@ -45,8 +45,8 @@
window.Prism = window.Prism || {}; window.Prism = window.Prism || {};
window.Prism.manual = true; window.Prism.manual = true;
</script> </script>
<script type="module" crossorigin src="./assets/index-7d33ef4c.js"></script> <script type="module" crossorigin src="./assets/index-6c8f1731.js"></script>
<link rel="stylesheet" href="./assets/index-aca38a7a.css"> <link rel="stylesheet" href="./assets/index-0a754981.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

199
ui/package-lock.json generated
View File

@ -18,10 +18,10 @@
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
"@sveltejs/vite-plugin-svelte": "^2.4.1", "@sveltejs/vite-plugin-svelte": "^2.4.1",
"chart.js": "^3.7.1", "chart.js": "^4.0.0",
"chartjs-adapter-luxon": "^1.2.0", "chartjs-adapter-luxon": "^1.2.0",
"luxon": "^2.3.2", "luxon": "^2.3.2",
"pocketbase": "^0.18.0", "pocketbase": "0.20.0-rc2",
"prismjs": "^1.28.0", "prismjs": "^1.28.0",
"sass": "^1.45.0", "sass": "^1.45.0",
"svelte": "^4.0.0", "svelte": "^4.0.0",
@ -44,9 +44,9 @@
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
"version": "6.10.2", "version": "6.11.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.10.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.11.0.tgz",
"integrity": "sha512-3dCL7b0j2GdtZzWN5j7HDpRAJ26ip07R4NGYz7QYthIYMiX8I4E4TNrYcdTayPJGeVQtd/xe7lWU4XL7THFb/w==", "integrity": "sha512-LCPH3W+hl5vcO7OzEQgX6NpKuKVyiKFLGAy7FXROF6nUpsWUdQEgUb3fe/g7B0E1KZCRFfgzdKASt6Wly2UOBg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -193,9 +193,9 @@
"dev": true "dev": true
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.21.4", "version": "6.22.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.4.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.22.0.tgz",
"integrity": "sha512-WKVZ7nvN0lwWPfAf05WxWqTpwjC8YN3q5goj3CsSig7//DD81LULgOx3nBegqpqP0iygBqRmW8z0KSc2QTAdAg==", "integrity": "sha512-6zLj4YIoIpfTGKrDMTbeZRpa8ih4EymMCKmddEDcJWrCdp/N1D46B38YEz4creTb4T177AVS9EyXkLeC/HL2jA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.1.4", "@codemirror/state": "^6.1.4",
@ -603,16 +603,22 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
"dev": true
},
"node_modules/@lezer/common": { "node_modules/@lezer/common": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.1.tgz",
"integrity": "sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw==", "integrity": "sha512-aAPB9YbvZHqAW+bIwiuuTDGB4DG0sYNRObGLxud8cW7osw1ZQxfDuTZ8KQiqfZ0QJGcR34CvpTMDXEyo/+Htgg==",
"dev": true "dev": true
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.4.tgz",
"integrity": "sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==", "integrity": "sha512-CuUwjidrU7FOBokqASRJc72SmJ9g1PsHXDOWMoKg4md6+2u/Zxzwx5YsYrAFxRDsLrjLlsIyEF1rZHK3gFEJbw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
@ -620,18 +626,18 @@
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
"version": "1.1.6", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
"integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@lezer/html": { "node_modules/@lezer/html": {
"version": "1.3.6", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.6.tgz", "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.7.tgz",
"integrity": "sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==", "integrity": "sha512-Wo+rZ5UjLP0VqUTyXjzgmTYRW5bvTJUFn4Uw0K3HCQjX2/+f+zRo9GLN5BCAojwHQISPvaQk8BWSv2SSKx/UcQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
@ -640,9 +646,9 @@
} }
}, },
"node_modules/@lezer/javascript": { "node_modules/@lezer/javascript": {
"version": "1.4.8", "version": "1.4.9",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.8.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.9.tgz",
"integrity": "sha512-QRmw/5xrcyRLyWr3JT3KCzn2XZr5NYNqQMGsqnYy+FghbQn9DZPuj6JDkE6uSXvfMLpdapu8KBIaeoJFaR4QVw==", "integrity": "sha512-7Uv8mBBE6l44spgWEZvEMdDqGV+FIuY7kJ1o5TFm+jxIuxydO3PcKJYiINij09igd1D/9P7l2KDqpkN8c3bM6A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@ -660,18 +666,18 @@
} }
}, },
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.3.13", "version": "1.3.14",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.14.tgz",
"integrity": "sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==", "integrity": "sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@sveltejs/vite-plugin-svelte": { "node_modules/@sveltejs/vite-plugin-svelte": {
"version": "2.4.6", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz",
"integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4",
@ -686,7 +692,7 @@
"node": "^14.18.0 || >= 16" "node": "^14.18.0 || >= 16"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^3.54.0 || ^4.0.0", "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0",
"vite": "^4.0.0" "vite": "^4.0.0"
} }
}, },
@ -708,9 +714,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.3", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true "dev": true
}, },
"node_modules/acorn": { "node_modules/acorn": {
@ -778,10 +784,16 @@
} }
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "3.9.1", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
"dev": true "dev": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=7"
}
}, },
"node_modules/chartjs-adapter-luxon": { "node_modules/chartjs-adapter-luxon": {
"version": "1.3.1", "version": "1.3.1",
@ -1083,9 +1095,9 @@
"dev": true "dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.6", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1139,9 +1151,9 @@
} }
}, },
"node_modules/pocketbase": { "node_modules/pocketbase": {
"version": "0.18.3", "version": "0.20.0-rc2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.18.3.tgz", "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.20.0-rc2.tgz",
"integrity": "sha512-zE4+oue0p0Ntwoil17b2geBpPwV6B6bYdsbXmNWmod6XOqBK2+jLDT5/SlAP3une4bo3U5ZAW2GvigEBAV3bBg==", "integrity": "sha512-Kh/xZlz1W4VikFwvb80hF0SKyZyeOcoQBRGfd6eUHi4QcELXUbPZvX3K3TOFsJCER6sEesOh8eq4W10hwaKHAg==",
"dev": true "dev": true
}, },
"node_modules/postcss": { "node_modules/postcss": {
@ -1251,9 +1263,9 @@
"dev": true "dev": true
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "4.2.2", "version": "4.2.7",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.2.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.7.tgz",
"integrity": "sha512-My2tytF2e2NnHSpn2M7/3VdXT4JdTglYVUuSuK/mXL2XtulPYbeBfl8Dm1QiaKRn0zoULRnL+EtfZHHP0k4H3A==", "integrity": "sha512-UExR1KS7raTdycsUrKLtStayu4hpdV3VZQgM0akX8XbXgLBlosdE/Sf3crOgyh9xIjqSYB3UEBuUlIQKRQX2hg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.1", "@ampproject/remapping": "^2.2.1",
@ -1410,9 +1422,9 @@
} }
}, },
"@codemirror/autocomplete": { "@codemirror/autocomplete": {
"version": "6.10.2", "version": "6.11.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.10.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.11.0.tgz",
"integrity": "sha512-3dCL7b0j2GdtZzWN5j7HDpRAJ26ip07R4NGYz7QYthIYMiX8I4E4TNrYcdTayPJGeVQtd/xe7lWU4XL7THFb/w==", "integrity": "sha512-LCPH3W+hl5vcO7OzEQgX6NpKuKVyiKFLGAy7FXROF6nUpsWUdQEgUb3fe/g7B0E1KZCRFfgzdKASt6Wly2UOBg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -1553,9 +1565,9 @@
"dev": true "dev": true
}, },
"@codemirror/view": { "@codemirror/view": {
"version": "6.21.4", "version": "6.22.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.4.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.22.0.tgz",
"integrity": "sha512-WKVZ7nvN0lwWPfAf05WxWqTpwjC8YN3q5goj3CsSig7//DD81LULgOx3nBegqpqP0iygBqRmW8z0KSc2QTAdAg==", "integrity": "sha512-6zLj4YIoIpfTGKrDMTbeZRpa8ih4EymMCKmddEDcJWrCdp/N1D46B38YEz4creTb4T177AVS9EyXkLeC/HL2jA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/state": "^6.1.4", "@codemirror/state": "^6.1.4",
@ -1756,16 +1768,22 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
"dev": true
},
"@lezer/common": { "@lezer/common": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.1.tgz",
"integrity": "sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw==", "integrity": "sha512-aAPB9YbvZHqAW+bIwiuuTDGB4DG0sYNRObGLxud8cW7osw1ZQxfDuTZ8KQiqfZ0QJGcR34CvpTMDXEyo/+Htgg==",
"dev": true "dev": true
}, },
"@lezer/css": { "@lezer/css": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.4.tgz",
"integrity": "sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==", "integrity": "sha512-CuUwjidrU7FOBokqASRJc72SmJ9g1PsHXDOWMoKg4md6+2u/Zxzwx5YsYrAFxRDsLrjLlsIyEF1rZHK3gFEJbw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
@ -1773,18 +1791,18 @@
} }
}, },
"@lezer/highlight": { "@lezer/highlight": {
"version": "1.1.6", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
"integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"@lezer/html": { "@lezer/html": {
"version": "1.3.6", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.6.tgz", "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.7.tgz",
"integrity": "sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==", "integrity": "sha512-Wo+rZ5UjLP0VqUTyXjzgmTYRW5bvTJUFn4Uw0K3HCQjX2/+f+zRo9GLN5BCAojwHQISPvaQk8BWSv2SSKx/UcQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
@ -1793,9 +1811,9 @@
} }
}, },
"@lezer/javascript": { "@lezer/javascript": {
"version": "1.4.8", "version": "1.4.9",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.8.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.9.tgz",
"integrity": "sha512-QRmw/5xrcyRLyWr3JT3KCzn2XZr5NYNqQMGsqnYy+FghbQn9DZPuj6JDkE6uSXvfMLpdapu8KBIaeoJFaR4QVw==", "integrity": "sha512-7Uv8mBBE6l44spgWEZvEMdDqGV+FIuY7kJ1o5TFm+jxIuxydO3PcKJYiINij09igd1D/9P7l2KDqpkN8c3bM6A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@ -1813,18 +1831,18 @@
} }
}, },
"@lezer/lr": { "@lezer/lr": {
"version": "1.3.13", "version": "1.3.14",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.14.tgz",
"integrity": "sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==", "integrity": "sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"@sveltejs/vite-plugin-svelte": { "@sveltejs/vite-plugin-svelte": {
"version": "2.4.6", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz",
"integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4",
@ -1846,9 +1864,9 @@
} }
}, },
"@types/estree": { "@types/estree": {
"version": "1.0.3", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true "dev": true
}, },
"acorn": { "acorn": {
@ -1901,10 +1919,13 @@
} }
}, },
"chart.js": { "chart.js": {
"version": "3.9.1", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
"dev": true "dev": true,
"requires": {
"@kurkle/color": "^0.3.0"
}
}, },
"chartjs-adapter-luxon": { "chartjs-adapter-luxon": {
"version": "1.3.1", "version": "1.3.1",
@ -2134,9 +2155,9 @@
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
"version": "3.3.6", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true "dev": true
}, },
"normalize-path": { "normalize-path": {
@ -2169,9 +2190,9 @@
"dev": true "dev": true
}, },
"pocketbase": { "pocketbase": {
"version": "0.18.3", "version": "0.20.0-rc2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.18.3.tgz", "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.20.0-rc2.tgz",
"integrity": "sha512-zE4+oue0p0Ntwoil17b2geBpPwV6B6bYdsbXmNWmod6XOqBK2+jLDT5/SlAP3une4bo3U5ZAW2GvigEBAV3bBg==", "integrity": "sha512-Kh/xZlz1W4VikFwvb80hF0SKyZyeOcoQBRGfd6eUHi4QcELXUbPZvX3K3TOFsJCER6sEesOh8eq4W10hwaKHAg==",
"dev": true "dev": true
}, },
"postcss": { "postcss": {
@ -2239,9 +2260,9 @@
"dev": true "dev": true
}, },
"svelte": { "svelte": {
"version": "4.2.2", "version": "4.2.7",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.2.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.7.tgz",
"integrity": "sha512-My2tytF2e2NnHSpn2M7/3VdXT4JdTglYVUuSuK/mXL2XtulPYbeBfl8Dm1QiaKRn0zoULRnL+EtfZHHP0k4H3A==", "integrity": "sha512-UExR1KS7raTdycsUrKLtStayu4hpdV3VZQgM0akX8XbXgLBlosdE/Sf3crOgyh9xIjqSYB3UEBuUlIQKRQX2hg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ampproject/remapping": "^2.2.1", "@ampproject/remapping": "^2.2.1",

View File

@ -25,10 +25,10 @@
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
"@sveltejs/vite-plugin-svelte": "^2.4.1", "@sveltejs/vite-plugin-svelte": "^2.4.1",
"chart.js": "^3.7.1", "chart.js": "^4.0.0",
"chartjs-adapter-luxon": "^1.2.0", "chartjs-adapter-luxon": "^1.2.0",
"luxon": "^2.3.2", "luxon": "^2.3.2",
"pocketbase": "^0.18.0", "pocketbase": "0.20.0-rc2",
"prismjs": "^1.28.0", "prismjs": "^1.28.0",
"sass": "^1.45.0", "sass": "^1.45.0",
"svelte": "^4.0.0", "svelte": "^4.0.0",

View File

@ -0,0 +1,18 @@
<script>
import tooltip from "@/actions/tooltip";
import CommonHelper from "@/utils/CommonHelper";
export let date;
const tooltipData = {
// generate the tooltip text as getter to speed up the initial load
// in case the component is used with large number of items
get text() {
return CommonHelper.formatToLocalDate(date, "yyyy-MM-dd HH:mm:ss.SSS") + " Local";
},
};
</script>
<span class="txt-nowrap" use:tooltip={tooltipData}>
{date.replace("Z", " UTC")}
</span>

View File

@ -0,0 +1,40 @@
<script>
import { logLevels } from "@/utils/CommonHelper";
export let level;
$: label = logLevels.find((l) => l.level == level)?.label;
</script>
<div class="label log-level-label level-{level}">
<span class="txt">
{label || "N/A"} ({level})
</span>
</div>
<style lang="scss">
.log-level-label {
min-width: 75px;
font-weight: 600;
font-size: var(--xsFontSize);
&:before {
content: "";
width: 5px;
height: 5px;
border-radius: 5px;
background: var(--baseAlt4Color);
}
&.level--8:before {
background: var(--primaryColor);
}
&.level-0:before {
background: var(--infoColor);
}
&.level-4:before {
background: var(--warningColor);
}
&.level-8:before {
background: var(--dangerColor);
}
}
</style>

View File

@ -1,14 +1,22 @@
<script> <script>
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte"; import CodeBlock from "@/components/base/CodeBlock.svelte";
import FormattedDate from "@/components/base/FormattedDate.svelte";
import OverlayPanel from "@/components/base/OverlayPanel.svelte"; import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import CopyIcon from "@/components/base/CopyIcon.svelte";
import LogLevel from "@/components/logs/LogLevel.svelte";
import LogDate from "@/components/logs/LogDate.svelte";
let logPanel; let logPanel;
let item = {}; let log = {};
$: hasData = !CommonHelper.isEmpty(log.data);
export function show(model) { export function show(model) {
item = model; if (CommonHelper.isEmpty(model)) {
return;
}
log = model;
return logPanel?.show(); return logPanel?.show();
} }
@ -16,8 +24,54 @@
export function hide() { export function hide() {
return logPanel?.hide(); return logPanel?.hide();
} }
const priotizedKeys = [
"execTime",
"type",
"auth",
"status",
"method",
"url",
"referer",
"remoteIp",
"userIp",
"error",
"details",
//
];
function extractKeys(data) {
if (!data) {
return [];
}
let keys = [];
for (let key of priotizedKeys) {
if (typeof data[key] !== "undefined") {
keys.push(key);
}
}
// append the rest
const original = Object.keys(data);
for (let key of original) {
if (!keys.includes(key)) {
keys.push(key);
}
}
return keys;
}
function downloadJson() {
CommonHelper.downloadJson(log, "log_" + log.created.replaceAll(/[-:\. ]/gi, "") + ".json");
}
</script> </script>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<OverlayPanel bind:this={logPanel} class="overlay-panel-lg log-panel" on:hide on:show> <OverlayPanel bind:this={logPanel} class="overlay-panel-lg log-panel" on:hide on:show>
<svelte:fragment slot="header"> <svelte:fragment slot="header">
<h4>Request log</h4> <h4>Request log</h4>
@ -26,69 +80,53 @@
<table class="table-border"> <table class="table-border">
<tbody> <tbody>
<tr> <tr>
<td class="min-width txt-hint txt-bold">ID</td> <td class="min-width txt-hint txt-bold">id</td>
<td>{item.id}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Status</td>
<td> <td>
<span class="label" class:label-danger={item.status >= 400}> <div class="label">
{item.status} <CopyIcon value={log.id} />
</span> <div class="txt">{log.id}</div>
</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Method</td>
<td>{item.method?.toUpperCase()}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Auth</td>
<td>{item.auth}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">URL</td>
<td>{item.url}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Referer</td>
<td>
{#if item.referer}
<a href={item.referer} target="_blank" rel="noopener noreferrer">
{item.referer}
</a>
{:else}
<span class="txt-hint">N/A</span>
{/if}
</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Remote IP</td>
<td>{item.remoteIp}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">User IP</td>
<td>{item.userIp}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">UserAgent</td>
<td>{item.userAgent}</td>
</tr>
<tr>
<td class="min-width txt-hint txt-bold">Meta</td>
<td>
{#if !CommonHelper.isEmpty(item.meta)}
<div class="block">
<CodeBlock content={JSON.stringify(item.meta, null, 2)} />
</div> </div>
{:else}
<span class="txt-hint">N/A</span>
{/if}
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="min-width txt-hint txt-bold">Created</td> <td class="min-width txt-hint txt-bold">level</td>
<td><FormattedDate date={item.created} /></td> <td><LogLevel level={log.level} /></td>
</tr> </tr>
<tr>
<td class="min-width txt-hint txt-bold">created</td>
<td><LogDate date={log.created} /></td>
</tr>
{#if log.data?.type != "request"}
<tr>
<td class="min-width txt-hint txt-bold">message</td>
<td>
{#if log.message}
<span class="txt">{log.message}</span>
{:else}
<span class="txt txt-hint">N/A</span>
{/if}
</td>
</tr>
{/if}
{#each extractKeys(log.data) as key}
{@const value = log.data[key]}
<tr>
<td class="min-width txt-hint txt-bold" class:v-align-top={hasData}>
data.{key}
</td>
<td>
{#if value !== null && typeof value == "object"}
<CodeBlock content={JSON.stringify(value, null, 2)} />
{:else if CommonHelper.isEmpty(value)}
<span class="txt txt-hint">N/A</span>
{:else}
<span class="txt">
{value}{key == "execTime" ? "ms" : ""}
</span>
{/if}
</td>
</tr>
{/each}
</tbody> </tbody>
</table> </table>
@ -96,5 +134,10 @@
<button type="button" class="btn btn-transparent" on:click={() => hide()}> <button type="button" class="btn btn-transparent" on:click={() => hide()}>
<span class="txt">Close</span> <span class="txt">Close</span>
</button> </button>
<button type="button" class="btn btn-primary" on:click={() => downloadJson()}>
<i class="ri-download-line" />
<span class="txt">Download as JSON</span>
</button>
</svelte:fragment> </svelte:fragment>
</OverlayPanel> </OverlayPanel>

View File

@ -2,11 +2,12 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { scale } from "svelte/transition"; import { scale } from "svelte/transition";
import ApiClient from "@/utils/ApiClient"; import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import { import {
Chart, Chart,
LineElement, BarController,
PointElement, BarElement,
LineController, CategoryScale,
LinearScale, LinearScale,
TimeScale, TimeScale,
Filler, Filler,
@ -20,7 +21,7 @@
let chartCanvas; let chartCanvas;
let chartInst; let chartInst;
let chartData = []; let chartData = [];
let totalRequests = 0; let totalLogs = 0;
let isLoading = false; let isLoading = false;
$: if (typeof filter !== "undefined" || typeof presets !== "undefined") { $: if (typeof filter !== "undefined" || typeof presets !== "undefined") {
@ -36,24 +37,19 @@
isLoading = true; isLoading = true;
return ApiClient.logs return ApiClient.logs
.getRequestsStats({ .getStats({
filter: [presets, filter].filter(Boolean).join("&&"), filter: [presets, CommonHelper.normalizeLogsFilter(filter)].filter(Boolean).join("&&"),
}) })
.then((result) => { .then((result) => {
resetData(); resetData();
for (let item of result) { for (let item of result) {
chartData.push({ chartData.push({
x: new Date(item.date), x: new Date(item.date),
y: item.total, y: item.total,
}); });
totalRequests += item.total; totalLogs += item.total;
} }
// add current time marker to the chart
chartData.push({
x: new Date(),
y: undefined,
});
}) })
.catch((err) => { .catch((err) => {
if (!err?.isAbort) { if (!err?.isAbort) {
@ -68,31 +64,31 @@
} }
function resetData() { function resetData() {
totalRequests = 0;
chartData = []; chartData = [];
totalLogs = 0;
} }
onMount(() => { onMount(() => {
Chart.register(LineElement, PointElement, LineController, LinearScale, TimeScale, Filler, Tooltip); Chart.register(BarController, BarElement, CategoryScale, LinearScale, TimeScale, Filler, Tooltip);
chartInst = new Chart(chartCanvas, { chartInst = new Chart(chartCanvas, {
type: "line", type: "bar",
data: { data: {
datasets: [ datasets: [
{ {
label: "Total requests", label: "Total requests",
data: chartData, data: chartData,
borderColor: "#ef4565", backgroundColor: "#e34562",
pointBackgroundColor: "#ef4565", maxBarThickness: 40,
backgroundColor: "rgb(239,69,101,0.05)", borderRadius: 2,
borderWidth: 2, minBarLength: 7,
pointRadius: 1, hoverBackgroundColor: "#e34562",
pointBorderWidth: 0,
fill: true,
}, },
], ],
}, },
options: { options: {
resizeDelay: 250,
maintainAspectRatio: false,
animation: false, animation: false,
interaction: { interaction: {
intersect: false, intersect: false,
@ -103,25 +99,28 @@
beginAtZero: true, beginAtZero: true,
grid: { grid: {
color: "#edf0f3", color: "#edf0f3",
borderColor: "#dee3e8", },
border: {
color: "#e4e9ec",
}, },
ticks: { ticks: {
precision: 0, precision: 0,
maxTicksLimit: 6, maxTicksLimit: 4,
autoSkip: true, autoSkip: true,
color: "#666f75", color: "#666f75",
}, },
}, },
x: { x: {
// offset: false,
type: "time", type: "time",
time: { time: {
unit: "hour", unit: "hour",
tooltipFormat: "DD h a", tooltipFormat: "DD h a",
}, },
grid: { grid: {
borderColor: "#dee3e8", color: (c) => (c.tick?.major ? "#edf0f3" : ""),
color: (c) => (c.tick.major ? "#edf0f3" : ""),
}, },
color: "#e4e9ec",
ticks: { ticks: {
maxTicksLimit: 15, maxTicksLimit: 15,
autoSkip: true, autoSkip: true,
@ -129,7 +128,7 @@
major: { major: {
enabled: true, enabled: true,
}, },
color: (c) => (c.tick.major ? "#16161a" : "#666f75"), color: (c) => (c.tick?.major ? "#16161a" : "#666f75"),
}, },
}, },
}, },
@ -146,19 +145,14 @@
</script> </script>
<div class="chart-wrapper" class:loading={isLoading}> <div class="chart-wrapper" class:loading={isLoading}>
<div class="total-logs entrance-right" class:hidden={isLoading}>
Found {totalLogs}
{totalLogs == 1 ? "log" : "logs"}
</div>
{#if isLoading} {#if isLoading}
<div class="chart-loader loader" transition:scale={{ duration: 150 }} /> <div class="chart-loader loader" transition:scale={{ duration: 150 }} />
{/if} {/if}
<canvas bind:this={chartCanvas} class="chart-canvas" style="height: 250px; width: 100%;" /> <canvas bind:this={chartCanvas} class="chart-canvas" />
</div>
<div class="txt-hint m-t-xs txt-right">
{#if isLoading}
Loading...
{:else}
{totalRequests}
{totalRequests === 1 ? "log" : "logs"}
{/if}
</div> </div>
<style> <style>
@ -166,6 +160,7 @@
position: relative; position: relative;
display: block; display: block;
width: 100%; width: 100%;
height: 170px;
} }
.chart-wrapper.loading .chart-canvas { .chart-wrapper.loading .chart-canvas {
pointer-events: none; pointer-events: none;
@ -178,4 +173,11 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.total-logs {
position: absolute;
right: 0;
top: -50px;
font-size: var(--smFontSize);
color: var(--txtHintColor);
}
</style> </style>

Some files were not shown because too many files have changed in this diff Show More