Compare commits

...
16 Commits
79 changed files with 997 additions and 1201 deletions
+1 -1
View File
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://www.paypal.com/donate?hosted_button_id=S98EMBN3G3HZY']
custom: ['https://www.paypal.com/donate/?hosted_button_id=4DVXNL4B8WT98']
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '>=1.18.0'
go-version: '1.19.0-beta.1'
# This step usually is not needed because the /ui/dist is pregenerated locally
# but its here to ensure that each release embeds the latest admin ui artifacts.
+1
View File
@@ -1,4 +1,5 @@
/.vscode/
.idea
.DS_Store
+9
View File
@@ -0,0 +1,9 @@
lint:
golangci-lint run -c ./golangci.yml ./...
test:
go test -v --cover ./...
test-report:
go test -v --cover -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
+2 -1
View File
@@ -13,7 +13,7 @@
[PocketBase](https://pocketbase.io) is an open source Go backend, consisting of:
- embedded database (_SQLite_) with **realtime subscriptions**
- backed-in **files and users management**
- built-in **files and users management**
- convenient **Admin dashboard UI**
- and simple **REST-ish API**
@@ -43,6 +43,7 @@ your own custom app specific business logic and still have a single portable exe
# go 1.18+
go get github.com/pocketbase/pocketbase
```
> For Windows, you may have to use go 1.19+ due to an incorrect js mime type in the Windows Registry (see [issue#6](https://github.com/pocketbase/pocketbase/issues/6)).
### Example
+5 -5
View File
@@ -24,7 +24,7 @@ func BindAdminApi(app core.App, rg *echo.Group) {
subGroup.POST("/confirm-password-reset", api.confirmPasswordReset)
subGroup.POST("/refresh", api.refresh, RequireAdminAuth())
subGroup.GET("", api.list, RequireAdminAuth())
subGroup.POST("", api.create, RequireAdminAuth())
subGroup.POST("", api.create, RequireAdminAuthOnlyIfAny(app))
subGroup.GET("/:id", api.view, RequireAdminAuth())
subGroup.PATCH("/:id", api.update, RequireAdminAuth())
subGroup.DELETE("/:id", api.delete, RequireAdminAuth())
@@ -66,7 +66,7 @@ func (api *adminApi) refresh(c echo.Context) error {
func (api *adminApi) emailAuth(c echo.Context) error {
form := forms.NewAdminLogin(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
admin, submitErr := form.Submit()
@@ -80,11 +80,11 @@ func (api *adminApi) emailAuth(c echo.Context) error {
func (api *adminApi) requestPasswordReset(c echo.Context) error {
form := forms.NewAdminPasswordResetRequest(api.app)
if err := c.Bind(form); err != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", err)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", err)
}
if err := form.Validate(); err != nil {
return rest.NewBadRequestError("An error occured while validating the form.", err)
return rest.NewBadRequestError("An error occurred while validating the form.", err)
}
// run in background because we don't need to show the result
@@ -101,7 +101,7 @@ func (api *adminApi) requestPasswordReset(c echo.Context) error {
func (api *adminApi) confirmPasswordReset(c echo.Context) error {
form := forms.NewAdminPasswordResetConfirm(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
admin, submitErr := form.Submit()
+26 -1
View File
@@ -456,12 +456,37 @@ func TestAdminDelete(t *testing.T) {
func TestAdminCreate(t *testing.T) {
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
Name: "unauthorized (while having at least 1 existing admin)",
Method: http.MethodPost,
Url: "/api/admins",
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "unauthorized (while having 0 existing admins)",
Method: http.MethodPost,
Url: "/api/admins",
Body: strings.NewReader(`{"email":"testnew@example.com","password":"1234567890","passwordConfirm":"1234567890","avatar":3}`),
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
// delete all admins
_, err := app.Dao().DB().NewQuery("DELETE FROM {{_admins}}").Execute()
if err != nil {
t.Fatal(err)
}
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"id":`,
`"email":"testnew@example.com"`,
`"avatar":3`,
},
ExpectedEvents: map[string]int{
"OnModelBeforeCreate": 1,
"OnModelAfterCreate": 1,
"OnAdminBeforeCreateRequest": 1,
"OnAdminAfterCreateRequest": 1,
},
},
{
Name: "authorized as user",
Method: http.MethodPost,
+80 -9
View File
@@ -15,6 +15,7 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/rest"
"github.com/pocketbase/pocketbase/ui"
"github.com/spf13/cast"
)
// InitApi creates a configured echo instance with registered
@@ -38,13 +39,13 @@ func InitApi(app core.App) (*echo.Echo, error) {
var apiErr *rest.ApiError
switch v := err.(type) {
case (*echo.HTTPError):
case *echo.HTTPError:
if v.Internal != nil && app.IsDebug() {
log.Println(v.Internal)
}
msg := fmt.Sprintf("%v", v.Message)
apiErr = rest.NewApiError(v.Code, msg, v)
case (*rest.ApiError):
case *rest.ApiError:
if app.IsDebug() && v.RawData() != nil {
log.Println(v.RawData())
}
@@ -71,13 +72,8 @@ func InitApi(app core.App) (*echo.Echo, error) {
}
}
// serves /ui/dist/index.html file
// (explicit route is used to avoid conflicts with `RemoveTrailingSlash` middleware)
e.FileFS("/_", "index.html", ui.DistIndexHTML, middleware.Gzip())
// serves static files from the /ui/dist directory
// (similar to echo.StaticFS but with gzip middleware enabled)
e.GET("/_/*", StaticDirectoryHandler(ui.DistDirFS, false), middleware.Gzip())
// admin ui routes
bindStaticAdminUI(app, e)
// default routes
api := e.Group("/api")
@@ -129,3 +125,78 @@ func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) echo.H
return c.FileFS(name, fileSystem)
}
}
// bindStaticAdminUI registers the endpoints that serves the static admin UI.
func bindStaticAdminUI(app core.App, e *echo.Echo) error {
// serves /ui/dist/index.html file
// (explicit route is used to avoid conflicts with `RemoveTrailingSlash` middleware)
e.FileFS(
"/_",
"index.html",
ui.DistIndexHTML,
middleware.Gzip(),
installerRedirect(app),
)
// serves static files from the /ui/dist directory
// (similar to echo.StaticFS but with gzip middleware enabled)
e.GET(
"/_/*",
StaticDirectoryHandler(ui.DistDirFS, false),
middleware.Gzip(),
)
return nil
}
const totalAdminsCacheKey = "totalAdmins"
func updateTotalAdminsCache(app core.App) error {
total, err := app.Dao().TotalAdmins()
if err != nil {
return err
}
app.Cache().Set(totalAdminsCacheKey, total)
return nil
}
// installerRedirect redirects the user to the installer admin UI page
// when the application needs some preliminary configurations to be done.
func installerRedirect(app core.App) echo.MiddlewareFunc {
// keep totalAdminsCacheKey value up-to-date
app.OnAdminAfterCreateRequest().Add(func(data *core.AdminCreateEvent) error {
return updateTotalAdminsCache(app)
})
app.OnAdminAfterDeleteRequest().Add(func(data *core.AdminDeleteEvent) error {
return updateTotalAdminsCache(app)
})
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// load into cache (if not already)
if !app.Cache().Has(totalAdminsCacheKey) {
if err := updateTotalAdminsCache(app); err != nil {
return err
}
}
totalAdmins := cast.ToInt(app.Cache().Get(totalAdminsCacheKey))
_, hasInstallerParam := c.Request().URL.Query()["installer"]
if totalAdmins == 0 && !hasInstallerParam {
// redirect to the installer page
return c.Redirect(http.StatusTemporaryRedirect, "/_/?installer#")
}
if totalAdmins != 0 && hasInstallerParam {
// redirect to the home page
return c.Redirect(http.StatusTemporaryRedirect, "/_/#/")
}
return next(c)
}
}
}
+2 -1
View File
@@ -1,13 +1,14 @@
package apis_test
import (
"github.com/pocketbase/pocketbase/tests"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"testing"
"github.com/pocketbase/pocketbase/tests"
)
func TestFileDownload(t *testing.T) {
+1 -1
View File
@@ -3,8 +3,8 @@ package apis
import (
"net/http"
"github.com/pocketbase/dbx"
"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/rest"
+25 -3
View File
@@ -77,6 +77,28 @@ func RequireAdminAuth() echo.MiddlewareFunc {
}
}
// RequireAdminAuthIfAny middleware requires a request to have
// a valid admin Authorization header set (aka. `Authorization: Admin ...`)
// ONLY if the application has at least 1 existing Admin model.
func RequireAdminAuthOnlyIfAny(app core.App) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
totalAdmins, err := app.Dao().TotalAdmins()
if err != nil {
return rest.NewBadRequestError("Failed to fetch admins info.", err)
}
admin, _ := c.Get(ContextAdminKey).(*models.Admin)
if admin != nil || totalAdmins == 0 {
return next(c)
}
return rest.NewUnauthorizedError("The request requires admin authorization token to be set.", nil)
}
}
}
// RequireAdminOrUserAuth middleware requires a request to have
// a valid admin or user Authorization header set
// (aka. `Authorization: Admin ...` or `Authorization: User ...`).
@@ -204,11 +226,11 @@ func ActivityLogger(app core.App) echo.MiddlewareFunc {
if err != nil {
switch v := err.(type) {
case (*echo.HTTPError):
case *echo.HTTPError:
status = v.Code
meta["errorMessage"] = v.Message
meta["errorDetails"] = fmt.Sprint(v.Internal)
case (*rest.ApiError):
case *rest.ApiError:
status = v.Code
meta["errorMessage"] = v.Message
meta["errorDetails"] = fmt.Sprint(v.RawData())
@@ -259,7 +281,7 @@ func ActivityLogger(app core.App) echo.MiddlewareFunc {
// ---
now := time.Now()
lastLogsDeletedAt := cast.ToTime(app.Cache().Get("lastLogsDeletedAt"))
daysDiff := (now.Sub(lastLogsDeletedAt).Hours() * 24)
daysDiff := now.Sub(lastLogsDeletedAt).Hours() * 24
if daysDiff > float64(app.Settings().Logs.MaxDays) {
deleteErr := app.LogsDao().DeleteOldRequests(now.AddDate(0, 0, -1*app.Settings().Logs.MaxDays))
+119
View File
@@ -291,6 +291,125 @@ func TestRequireAdminAuth(t *testing.T) {
}
}
func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
scenarios := []tests.ApiScenario{
{
Name: "guest (while having at least 1 existing admin)",
Method: http.MethodGet,
Url: "/my/test",
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
e.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/test",
Handler: func(c echo.Context) error {
return c.String(200, "test123")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuthOnlyIfAny(app),
},
})
},
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "guest (while having 0 existing admins)",
Method: http.MethodGet,
Url: "/my/test",
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
// delete all admins
_, err := app.Dao().DB().NewQuery("DELETE FROM {{_admins}}").Execute()
if err != nil {
t.Fatal(err)
}
e.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/test",
Handler: func(c echo.Context) error {
return c.String(200, "test123")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuthOnlyIfAny(app),
},
})
},
ExpectedStatus: 200,
ExpectedContent: []string{"test123"},
},
{
Name: "expired/invalid token",
Method: http.MethodGet,
Url: "/my/test",
RequestHeaders: map[string]string{
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MTAxMzIwMH0.Gp_1b5WVhqjj2o3nJhNUlJmpdiwFLXN72LbMP-26gjA",
},
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
e.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/test",
Handler: func(c echo.Context) error {
return c.String(200, "test123")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuthOnlyIfAny(app),
},
})
},
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "valid user token",
Method: http.MethodGet,
Url: "/my/test",
RequestHeaders: map[string]string{
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
},
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
e.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/test",
Handler: func(c echo.Context) error {
return c.String(200, "test123")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuthOnlyIfAny(app),
},
})
},
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "valid admin token",
Method: http.MethodGet,
Url: "/my/test",
RequestHeaders: map[string]string{
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
},
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
e.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/test",
Handler: func(c echo.Context) error {
return c.String(200, "test123")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuthOnlyIfAny(app),
},
})
},
ExpectedStatus: 200,
ExpectedContent: []string{"test123"},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestRequireAdminOrUserAuth(t *testing.T) {
scenarios := []tests.ApiScenario{
{
+9 -7
View File
@@ -9,8 +9,8 @@ import (
"net/http"
"time"
"github.com/pocketbase/dbx"
"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models"
@@ -163,11 +163,12 @@ func (api *realtimeApi) bindEvents() {
modelTable := data.Model.TableName()
var contextKey string
if modelTable == userTable {
switch modelTable {
case userTable:
contextKey = ContextUserKey
} else if modelTable == adminTable {
case adminTable:
contextKey = ContextAdminKey
} else {
default:
return nil
}
@@ -186,11 +187,12 @@ func (api *realtimeApi) bindEvents() {
modelTable := data.Model.TableName()
var contextKey string
if modelTable == userTable {
switch modelTable {
case userTable:
contextKey = ContextUserKey
} else if modelTable == adminTable {
case adminTable:
contextKey = ContextAdminKey
} else {
default:
return nil
}
+1 -1
View File
@@ -6,8 +6,8 @@ import (
"net/http"
"strings"
"github.com/pocketbase/dbx"
"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/forms"
+2 -2
View File
@@ -41,7 +41,7 @@ func (api *settingsApi) list(c echo.Context) error {
func (api *settingsApi) set(c echo.Context) error {
form := forms.NewSettingsUpsert(api.app)
if err := c.Bind(form); err != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", err)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", err)
}
event := &core.SettingsUpdateEvent{
@@ -52,7 +52,7 @@ func (api *settingsApi) set(c echo.Context) error {
handlerErr := api.app.OnSettingsBeforeUpdateRequest().Trigger(event, func(e *core.SettingsUpdateEvent) error {
if err := form.Submit(); err != nil {
return rest.NewBadRequestError("An error occured while submitting the form.", err)
return rest.NewBadRequestError("An error occurred while submitting the form.", err)
}
redactedSettings, err := api.app.Settings().RedactClone()
+12 -12
View File
@@ -152,12 +152,12 @@ func (api *userApi) authMethods(c echo.Context) error {
func (api *userApi) oauth2Auth(c echo.Context) error {
form := forms.NewUserOauth2Login(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
user, authData, submitErr := form.Submit()
if submitErr != nil {
return rest.NewBadRequestError("Failed to authenticated.", submitErr)
return rest.NewBadRequestError("Failed to authenticate.", submitErr)
}
return api.authResponse(c, user, authData)
@@ -170,7 +170,7 @@ func (api *userApi) emailAuth(c echo.Context) error {
form := forms.NewUserEmailLogin(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
user, submitErr := form.Submit()
@@ -184,11 +184,11 @@ func (api *userApi) emailAuth(c echo.Context) error {
func (api *userApi) requestPasswordReset(c echo.Context) error {
form := forms.NewUserPasswordResetRequest(api.app)
if err := c.Bind(form); err != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", err)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", err)
}
if err := form.Validate(); err != nil {
return rest.NewBadRequestError("An error occured while validating the form.", err)
return rest.NewBadRequestError("An error occurred while validating the form.", err)
}
// run in background because we don't need to show
@@ -205,7 +205,7 @@ func (api *userApi) requestPasswordReset(c echo.Context) error {
func (api *userApi) confirmPasswordReset(c echo.Context) error {
form := forms.NewUserPasswordResetConfirm(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
user, submitErr := form.Submit()
@@ -224,7 +224,7 @@ func (api *userApi) requestEmailChange(c echo.Context) error {
form := forms.NewUserEmailChangeRequest(api.app, loggedUser)
if err := c.Bind(form); err != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", err)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", err)
}
if err := form.Submit(); err != nil {
@@ -237,7 +237,7 @@ func (api *userApi) requestEmailChange(c echo.Context) error {
func (api *userApi) confirmEmailChange(c echo.Context) error {
form := forms.NewUserEmailChangeConfirm(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
user, submitErr := form.Submit()
@@ -251,11 +251,11 @@ func (api *userApi) confirmEmailChange(c echo.Context) error {
func (api *userApi) requestVerification(c echo.Context) error {
form := forms.NewUserVerificationRequest(api.app)
if err := c.Bind(form); err != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", err)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", err)
}
if err := form.Validate(); err != nil {
return rest.NewBadRequestError("An error occured while validating the form.", err)
return rest.NewBadRequestError("An error occurred while validating the form.", err)
}
// run in background because we don't need to show
@@ -272,12 +272,12 @@ func (api *userApi) requestVerification(c echo.Context) error {
func (api *userApi) confirmVerification(c echo.Context) error {
form := forms.NewUserVerificationConfirm(api.app)
if readErr := c.Bind(form); readErr != nil {
return rest.NewBadRequestError("An error occured while reading the submitted data.", readErr)
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
user, submitErr := form.Submit()
if submitErr != nil {
return rest.NewBadRequestError("An error occured while submitting the form.", submitErr)
return rest.NewBadRequestError("An error occurred while submitting the form.", submitErr)
}
return api.authResponse(c, user, nil)
+10 -83
View File
@@ -2,7 +2,6 @@ package cmd
import (
"crypto/tls"
"errors"
"fmt"
"log"
"net"
@@ -10,14 +9,10 @@ import (
"path/filepath"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/labstack/echo/v5/middleware"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/migrate"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
@@ -35,18 +30,6 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
Use: "serve",
Short: "Starts the web server (default to localhost:8090)",
Run: func(command *cobra.Command, args []string) {
router, err := apis.InitApi(app)
if err != nil {
panic(err)
}
// configure cors
router.Use(middleware.CORSWithConfig(middleware.CORSConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: allowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
})))
// ensure that the latest migrations are applied before starting the server
if err := runMigrations(app); err != nil {
panic(err)
@@ -61,19 +44,18 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
color.Yellow("=====================================")
}
// if no admins are found, create the first one
totalAdmins, err := app.Dao().TotalAdmins()
router, err := apis.InitApi(app)
if err != nil {
log.Fatalln(err)
return
}
if totalAdmins == 0 {
if err := promptCreateAdmin(app); err != nil {
log.Fatalln(err)
return
}
panic(err)
}
// configure cors
router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: allowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))
// start http server
// ---
mainAddr := httpAddr
@@ -149,7 +131,7 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
&httpsAddr,
"https",
"",
"api HTTPS server address (auto TLS via Let's Encrypt)\nthe incomming --http address traffic also will be redirected to this address",
"api HTTPS server address (auto TLS via Let's Encrypt)\nthe incoming --http address traffic also will be redirected to this address",
)
return command
@@ -171,58 +153,3 @@ func runMigrations(app core.App) error {
return nil
}
func promptCreateAdmin(app core.App) error {
color.White("-------------------------------------")
color.Cyan("Lets create your first admin account:")
color.White("-------------------------------------")
prompts := []*survey.Question{
{
Name: "Email",
Prompt: &survey.Input{Message: "Email:"},
Validate: func(val any) error {
if err := survey.Required(val); err != nil {
return err
}
if err := is.Email.Validate(val); err != nil {
return err
}
return nil
},
},
{
Name: "Password",
Prompt: &survey.Password{Message: "Pass (min 10 chars):"},
Validate: func(val any) error {
if str, ok := val.(string); !ok || len(str) < 10 {
return errors.New("The password must be at least 10 characters.")
}
return nil
},
},
}
result := struct {
Email string
Password string
}{}
if err := survey.Ask(prompts, &result); err != nil {
return err
}
form := forms.NewAdminUpsert(app, &models.Admin{})
form.Email = result.Email
form.Password = result.Password
form.PasswordConfirm = result.Password
if err := form.Submit(); err != nil {
return err
}
color.Green("Successfully created admin %s!", result.Email)
fmt.Println("")
return nil
}
+8 -8
View File
@@ -91,7 +91,7 @@ type App interface {
// entry in the DB, allowing you to modify or validate the stored data.
OnModelBeforeCreate() *hook.Hook[*ModelEvent]
// OnModelAfterCreate hook is triggered after successfuly
// OnModelAfterCreate hook is triggered after successfully
// inserting a new entry in the DB.
OnModelAfterCreate() *hook.Hook[*ModelEvent]
@@ -99,7 +99,7 @@ type App interface {
// entry in the DB, allowing you to modify or validate the stored data.
OnModelBeforeUpdate() *hook.Hook[*ModelEvent]
// OnModelAfterUpdate hook is triggered after successfuly updating
// OnModelAfterUpdate hook is triggered after successfully updating
// existing entry in the DB.
OnModelAfterUpdate() *hook.Hook[*ModelEvent]
@@ -107,7 +107,7 @@ type App interface {
// existing entry from the DB.
OnModelBeforeDelete() *hook.Hook[*ModelEvent]
// OnModelAfterDelete is triggered after successfuly deleting an
// OnModelAfterDelete is triggered after successfully deleting an
// existing entry from the DB.
OnModelAfterDelete() *hook.Hook[*ModelEvent]
@@ -123,7 +123,7 @@ type App interface {
OnMailerBeforeAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
// OnMailerAfterAdminResetPasswordSend hook is triggered after
// admin password reset email was successfuly sent.
// admin password reset email was successfully sent.
OnMailerAfterAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
// OnMailerBeforeUserResetPasswordSend hook is triggered right before
@@ -134,7 +134,7 @@ type App interface {
OnMailerBeforeUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
// OnMailerAfterUserResetPasswordSend hook is triggered after
// a user password reset email was successfuly sent.
// a user password reset email was successfully sent.
OnMailerAfterUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
// OnMailerBeforeUserVerificationSend hook is triggered right before
@@ -145,7 +145,7 @@ type App interface {
OnMailerBeforeUserVerificationSend() *hook.Hook[*MailerUserEvent]
// OnMailerAfterUserVerificationSend hook is triggered after a user
// verification email was successfuly sent.
// verification email was successfully sent.
OnMailerAfterUserVerificationSend() *hook.Hook[*MailerUserEvent]
// OnMailerBeforeUserChangeEmailSend hook is triggered right before
@@ -156,7 +156,7 @@ type App interface {
OnMailerBeforeUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
// OnMailerAfterUserChangeEmailSend hook is triggered after a user
// change address email was successfuly sent.
// change address email was successfully sent.
OnMailerAfterUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
// ---------------------------------------------------------------
@@ -180,7 +180,7 @@ type App interface {
// Settings API event hooks
// ---------------------------------------------------------------
// OnSettingsListRequest hook is triggered on each successfull
// OnSettingsListRequest hook is triggered on each successful
// API Settings list request.
//
// Could be used to validate or modify the response before
+1 -1
View File
@@ -5,8 +5,8 @@ package core
import (
"fmt"
"github.com/pocketbase/dbx"
_ "github.com/mattn/go-sqlite3"
"github.com/pocketbase/dbx"
)
func connectDB(dbPath string) (*dbx.DB, error) {
+6 -6
View File
@@ -148,12 +148,12 @@ func (s *Settings) Merge(other *Settings) error {
}
// Clone creates a new deep copy of the current settings.
func (c *Settings) Clone() (*Settings, error) {
new := &Settings{}
if err := new.Merge(c); err != nil {
func (s *Settings) Clone() (*Settings, error) {
settings := &Settings{}
if err := settings.Merge(s); err != nil {
return nil, err
}
return new, nil
return settings, nil
}
// RedactClone creates a new deep copy of the current settings,
@@ -360,11 +360,11 @@ func (c AuthProviderConfig) SetupProvider(provider auth.Provider) error {
}
if c.ClientId != "" {
provider.SetClientId(string(c.ClientId))
provider.SetClientId(c.ClientId)
}
if c.ClientSecret != "" {
provider.SetClientSecret(string(c.ClientSecret))
provider.SetClientSecret(c.ClientSecret)
}
if c.AuthUrl != "" {
+1 -1
View File
@@ -45,7 +45,7 @@ func (dao *Dao) FindAdminByEmail(email string) (*models.Admin, error) {
return model, nil
}
// FindAdminByEmail finds the admin associated with the provided JWT token.
// FindAdminByToken finds the admin associated with the provided JWT token.
//
// Returns an error if the JWT token is invalid or expired.
func (dao *Dao) FindAdminByToken(token string, baseTokenKey string) (*models.Admin, error) {
-1
View File
@@ -166,7 +166,6 @@ func TestDeleteCollection(t *testing.T) {
t.Errorf("(%d) Expected hasErr %v, got %v", i, scenario.expectError, hasErr)
}
}
}
func TestSaveCollectionCreate(t *testing.T) {
+1 -1
View File
@@ -309,7 +309,7 @@ func (dao *Dao) SyncRecordTableSchema(newCollection *models.Collection, oldColle
newSchema := newCollection.Schema
// check for renamed table
if strings.ToLower(oldTableName) != strings.ToLower(newTableName) {
if !strings.EqualFold(oldTableName, newTableName) {
_, err := dao.DB().RenameTable(oldTableName, newTableName).Execute()
if err != nil {
return err
+3
View File
@@ -396,6 +396,9 @@ func TestSyncRecordTableSchema(t *testing.T) {
t.Fatal(err)
}
updatedCollection, err := app.Dao().FindCollectionByNameOrId("demo")
if err != nil {
t.Fatal(err)
}
updatedCollection.Name = "demo_renamed"
updatedCollection.Schema.RemoveField(updatedCollection.Schema.GetFieldByName("file").Id)
updatedCollection.Schema.AddField(
+1 -1
View File
@@ -107,7 +107,7 @@ func TestAdminPasswordResetConfirmSubmit(t *testing.T) {
}
claims, _ := security.ParseUnverifiedJWT(s.token)
tokenAdminId, _ := claims["id"]
tokenAdminId := claims["id"]
if admin.Id != tokenAdminId {
t.Errorf("(%d) Expected admin with id %s to be returned, got %v", i, tokenAdminId, admin)
+1 -1
View File
@@ -95,7 +95,7 @@ func (form *CollectionUpsert) checkUniqueName(value any) error {
return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
}
if (form.isCreate || strings.ToLower(v) != strings.ToLower(form.collection.Name)) && form.app.Dao().HasTable(v) {
if (form.isCreate || !strings.EqualFold(v, form.collection.Name)) && form.app.Dao().HasTable(v) {
return validation.NewError("validation_collection_name_table_exists", "The collection name must be also unique table name.")
}
+1 -2
View File
@@ -44,7 +44,6 @@ func NewRecordUpsert(app core.App, record *models.Record) *RecordUpsert {
form.Data = map[string]any{}
for _, field := range record.Collection().Schema.Fields() {
form.Data[field.Name] = record.GetDataValue(field.Name)
}
return form
@@ -147,7 +146,7 @@ func (form *RecordUpsert) LoadData(r *http.Request) error {
for _, field := range form.record.Collection().Schema.Fields() {
key := field.Name
value, _ := extendedData[key]
value := extendedData[key]
value = field.PrepareValue(value)
if field.Type == schema.FieldTypeFile {
+1 -1
View File
@@ -29,7 +29,7 @@ func TestNewRecordUpsert(t *testing.T) {
form := forms.NewRecordUpsert(app, record)
val, _ := form.Data["title"]
val := form.Data["title"]
if val != "test_value" {
t.Errorf("Expected record data to be load, got %v", form.Data)
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/pocketbase/pocketbase/models"
)
// UserEmailChangeConfirm defines a user email change request form.
// UserEmailChangeRequest defines a user email change request form.
type UserEmailChangeRequest struct {
app core.App
user *models.User
+1 -1
View File
@@ -68,7 +68,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
return nil, nil, err
}
config, _ := form.app.Settings().NamedAuthProviderConfigs()[form.Provider]
config := form.app.Settings().NamedAuthProviderConfigs()[form.Provider]
config.SetupProvider(provider)
provider.SetRedirectUrl(form.RedirectUrl)
+1 -1
View File
@@ -148,7 +148,7 @@ func TestUserPasswordResetConfirmSubmit(t *testing.T) {
}
claims, _ := security.ParseUnverifiedJWT(form.Token)
tokenUserId, _ := claims["id"]
tokenUserId := claims["id"]
if user.Id != tokenUserId {
t.Errorf("(%d) Expected user with id %s, got %v", i, tokenUserId, user)
+1 -1
View File
@@ -127,7 +127,7 @@ func TestUserVerificationConfirmSubmit(t *testing.T) {
}
claims, _ := security.ParseUnverifiedJWT(form.Token)
tokenUserId, _ := claims["id"]
tokenUserId := claims["id"]
if user.Id != tokenUserId {
t.Errorf("(%d) Expected user.Id %q, got %q", i, tokenUserId, user.Id)
+1 -1
View File
@@ -6,9 +6,9 @@ import (
"regexp"
"strings"
"github.com/pocketbase/dbx"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
+1 -1
View File
@@ -1189,7 +1189,7 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: 3,
CollectionId: "", // missing or nonexisting colleciton id
CollectionId: "", // missing or non-existing collection id
},
},
)
+30
View File
@@ -0,0 +1,30 @@
run:
go: 1.18
concurrency: 4
timeout: 10m
linters:
disable-all: true
enable:
- asciicheck
- deadcode
- depguard
- exportloopref
- gocritic
- gofmt
- goimports
- gomodguard
- goprintffuncname
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- nolintlint
- prealloc
- staticcheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
+2 -2
View File
@@ -53,7 +53,7 @@ func (m *BaseModel) GetCreated() types.DateTime {
return m.Created
}
// GetCreated returns the model's Updated datetime.
// GetUpdated returns the model's Updated datetime.
func (m *BaseModel) GetUpdated() types.DateTime {
return m.Updated
}
@@ -71,7 +71,7 @@ func (m *BaseModel) RefreshCreated() {
m.Created = types.NowDateTime()
}
// RefreshCreated updates the model's Created field with the current datetime.
// RefreshUpdated updates the model's Created field with the current datetime.
func (m *BaseModel) RefreshUpdated() {
m.Updated = types.NowDateTime()
}
+2 -2
View File
@@ -77,12 +77,12 @@ func NewRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringM
return result
}
// Returns the table name associated to the current Record model.
// TableName returns the table name associated to the current Record model.
func (m *Record) TableName() string {
return m.collection.Name
}
// Returns the Collection model associated to the current Record model.
// Collection returns the Collection model associated to the current Record model.
func (m *Record) Collection() *Collection {
return m.collection
}
+1 -1
View File
@@ -134,7 +134,7 @@ func (s *Schema) AddField(newField *SchemaField) {
// checks for invalid renamed fields and field name duplications.
func (s Schema) Validate() error {
return validation.Validate(&s.fields, validation.Required, validation.By(func(value any) error {
fields := s.fields // use directly the schema value to avoid unnecesary interface casting
fields := s.fields // use directly the schema value to avoid unnecessary interface casting
if len(fields) == 0 {
return validation.NewError("validation_invalid_schema", "Invalid schema format.")
+3 -4
View File
@@ -493,9 +493,9 @@ func TestSchemaFieldPrepareValue(t *testing.T) {
value any
expectJson string
}{
{schema.SchemaField{Type: "unkown"}, "test", `"test"`},
{schema.SchemaField{Type: "unkown"}, 123, "123"},
{schema.SchemaField{Type: "unkown"}, []int{1, 2, 1}, "[1,2,1]"},
{schema.SchemaField{Type: "unknown"}, "test", `"test"`},
{schema.SchemaField{Type: "unknown"}, 123, "123"},
{schema.SchemaField{Type: "unknown"}, []int{1, 2, 1}, "[1,2,1]"},
// text
{schema.SchemaField{Type: schema.FieldTypeText}, nil, `null`},
@@ -1281,7 +1281,6 @@ func TestFileOptionsValidate(t *testing.T) {
}
func TestRelationOptionsValidate(t *testing.T) {
scenarios := []fieldOptionsScenario{
{
"empty",
+2 -2
View File
@@ -51,9 +51,9 @@ type PocketBase struct {
// New creates a new PocketBase instance.
//
// Note that the application will not be initialized/bootstrapped yet,
// aka. DB connections, migrations, app settings, etc. will not be accessable.
// aka. DB connections, migrations, app settings, etc. will not be accessible.
// Everything will be initialized when Start() is executed.
// If you want initialize the application before calling Start(),
// If you want to initialize the application before calling Start(),
// then you'll have to manually call Bootstrap().
func New() *PocketBase {
// try to find the base executable directory and how it was run
+1 -1
View File
@@ -109,7 +109,7 @@ func (scenario *ApiScenario) Test(t *testing.T) {
}
for event, expectedCalls := range scenario.ExpectedEvents {
actualCalls, _ := testApp.EventCalls[event]
actualCalls := testApp.EventCalls[event]
if actualCalls != expectedCalls {
t.Errorf("[%s] Expected event %s to be called %d, got %d", prefix, event, expectedCalls, actualCalls)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Pacakge tests provides common helpers and mocks used in PocketBase application tests.
// Package tests provides common helpers and mocks used in PocketBase application tests.
package tests
import (
+1 -1
View File
@@ -29,7 +29,7 @@ type Provider interface {
// SetClientId sets the provider client's ID.
SetClientId(clientId string)
// ClientId returns the provider client's app secret.
// ClientSecret returns the provider client's app secret.
ClientSecret() string
// SetClientSecret sets the provider client's app secret.
+1 -1
View File
@@ -127,7 +127,7 @@ func (s *System) DeletePrefix(prefix string) []error {
Prefix: prefix,
}
// delete all files witht the prefix
// delete all files with the prefix
// ---
iter := s.bucket.List(&opts)
for {
+1 -1
View File
@@ -82,7 +82,7 @@ func TestSanitize(t *testing.T) {
{" ", ` `, "", false},
{"", `[A-Z]`, "", false},
{"abcABC", `[A-Z]`, "abc", false},
{"abcABC", `[A-Z`, "", true}, // invlid pattern
{"abcABC", `[A-Z`, "", true}, // invalid pattern
}
for i, scenario := range scenarios {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var cachedPatterns = map[string]*regexp.Regexp{}
// ExustInSlice checks whether a comparable element exists in a slice of the same type.
// ExistInSlice checks whether a comparable element exists in a slice of the same type.
func ExistInSlice[T comparable](item T, list []T) bool {
if len(list) == 0 {
return false
-2
View File
@@ -37,8 +37,6 @@ func NewSmtpClient(
// SmtpClient defines a SMTP mail client structure that implements
// `mailer.Mailer` interface.
type SmtpClient struct {
mail *mailyak.MailYak
host string
port int
username string
+1 -1
View File
@@ -210,7 +210,7 @@ func (s *Provider) Exec(items any) (*Result, error) {
s.perPage = MaxPerPage
}
// normalize page accoring to the total count
// normalize page according to the total count
if s.page <= 0 || totalCount == 0 {
s.page = 1
} else if totalPages := int(math.Ceil(float64(totalCount) / float64(s.perPage))); s.page > totalPages {
+2 -1
View File
@@ -9,6 +9,7 @@ import (
func TestRandomString(t *testing.T) {
generated := []string{}
reg := regexp.MustCompile(`[a-zA-Z0-9]+`)
for i := 0; i < 30; i++ {
length := 5 + i
@@ -18,7 +19,7 @@ func TestRandomString(t *testing.T) {
t.Errorf("(%d) Expected the length of the string to be %d, got %d", i, length, len(result))
}
if match, _ := regexp.MatchString("[a-zA-Z0-9]+", result); !match {
if match := reg.MatchString(result); !match {
t.Errorf("(%d) The generated strings should have only [a-zA-Z0-9]+ characters, got %q", i, result)
}
+1 -1
View File
@@ -57,7 +57,7 @@ func (s *Store[T]) Set(key string, value T) {
// SetIfLessThanLimit sets (or overwrite if already exist) a new value for key.
//
// This is method is similar to Set() but **it will skip adding new elements**
// This method is similar to Set() but **it will skip adding new elements**
// to the store if the store length has reached the specified limit.
// `false` is returned if maxAllowedElements limit is reached.
func (s *Store[T]) SetIfLessThanLimit(key string, value T, maxAllowedElements int) bool {
+2 -1
View File
@@ -1,10 +1,11 @@
package types_test
import (
"github.com/pocketbase/pocketbase/tools/types"
"strings"
"testing"
"time"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestNowDateTime(t *testing.T) {
+1
View File
@@ -2,3 +2,4 @@
PB_BACKEND_URL = /
PB_PROFILE_COLLECTION = profiles
PB_RULES_SYNTAX_DOCS = https://pocketbase.io/docs/manage-collections#rules-filters-syntax
PB_INSTALLER_PARAM = installer
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{S as t,i as n,s as o,K as a}from"./index.944ee0db.js";function l(e){return a("/collections"),[]}class c extends t{constructor(s){super(),n(this,s,l,null,o,{})}}export{c as default};
@@ -1,2 +0,0 @@
import{S as I,i as K,s as L,F as W,f as F,m as H,n as B,o as J,q as N,H as M,D as q,e as c,g as u,h as b,j as _,E as O,p as w,z,d as k,A as D,B as T,C as Q,k as U,v as V,I as X,y as E,J as Y,K as Z,G as S}from"./index.944ee0db.js";function G(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&O(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=T(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=T(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,C,v,P,A,h,m=f[3]&&G(f);return r=new z({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new z({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password
`),m&&m.c(),t=k(),F(r.$$.fragment),p=k(),F(d.$$.fragment),n=k(),i=c("button"),g=c("span"),g.textContent="Set new password",R=k(),C=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],D(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(C,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),H(r,e,null),_(e,p),H(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,C,$),_(C,v),P=!0,A||(h=[T(e,"submit",Q(f[4])),U(V.call(null,v))],A=!0)},p(a,$){a[3]?m?m.p(a,$):(m=G(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const j={};$&769&&(j.$$scope={dirty:$,ctx:a}),r.$set(j);const y={};$&770&&(y.$$scope={dirty:$,ctx:a}),d.$set(y),(!P||$&4)&&(i.disabled=a[2]),$&4&&D(i,"btn-loading",a[2])},i(a){P||(B(r.$$.fragment,a),B(d.$$.fragment,a),P=!0)},o(a){J(r.$$.fragment,a),J(d.$$.fragment,a),P=!1},d(a){a&&w(e),m&&m.d(),N(r),N(d),a&&w(R),a&&w(C),A=!1,X(h)}}}function se(f){let e,o;return e=new W({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){F(e.$$.fragment)},m(s,l){H(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(B(e.$$.fragment,s),o=!0)},o(s){J(e.$$.fragment,s),o=!1},d(s){N(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await E.Admins.confirmPasswordReset(l==null?void 0:l.token,t,r),Y("Successfully set a new admin password."),Z("/")}catch(g){E.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends I{constructor(e){super(),K(this,e,le,se,L,{params:5})}}export{ae as default};
@@ -0,0 +1,2 @@
import{S as E,i as G,s as I,F as K,c as F,m as B,t as H,a as N,d as T,C as M,w as q,e as c,f as u,g as b,h as _,x as O,o as w,q as J,b as k,r as L,u as h,v as Q,j as U,l as V,A as X,p as W,B as Y,D as Z,z as S}from"./index.c63abb6b.js";function y(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&O(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=h(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=h(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,C,v,P,A,j,m=f[3]&&y(f);return r=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password
`),m&&m.c(),t=k(),F(r.$$.fragment),p=k(),F(d.$$.fragment),n=k(),i=c("button"),g=c("span"),g.textContent="Set new password",R=k(),C=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],L(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(C,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),B(r,e,null),_(e,p),B(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,C,$),_(C,v),P=!0,A||(j=[h(e,"submit",Q(f[4])),U(V.call(null,v))],A=!0)},p(a,$){a[3]?m?m.p(a,$):(m=y(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const z={};$&769&&(z.$$scope={dirty:$,ctx:a}),r.$set(z);const D={};$&770&&(D.$$scope={dirty:$,ctx:a}),d.$set(D),(!P||$&4)&&(i.disabled=a[2]),$&4&&L(i,"btn-loading",a[2])},i(a){P||(H(r.$$.fragment,a),H(d.$$.fragment,a),P=!0)},o(a){N(r.$$.fragment,a),N(d.$$.fragment,a),P=!1},d(a){a&&w(e),m&&m.d(),T(r),T(d),a&&w(R),a&&w(C),A=!1,X(j)}}}function se(f){let e,o;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){F(e.$$.fragment)},m(s,l){B(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(H(e.$$.fragment,s),o=!0)},o(s){N(e.$$.fragment,s),o=!1},d(s){T(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await W.Admins.confirmPasswordReset(l==null?void 0:l.token,t,r),Y("Successfully set a new admin password."),Z("/")}catch(g){W.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default};
@@ -1,2 +1,2 @@
import{S,i as B,s as M,F as T,f as A,m as E,n as w,o as y,q as H,d as g,e as _,g as p,h as k,j as d,k as j,v as z,w as D,x as G,p as v,y as C,z as N,A as F,B as L,C as I,D as h,E as J,u as P,G as R}from"./index.944ee0db.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new N({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{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 we\u2019ll send you a recovery link:</p>`,n=g(),A(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),E(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=L(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),$&2&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),H(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=L(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),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(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=j(z.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(D(),y(a[$],1,1,()=>{a[$]=null}),G(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new T({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){A(e.$$.fragment)},m(n,l){E(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){H(e,n)}}}function W(c,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.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends S{constructor(e){super(),B(this,e,W,V,M,{})}}export{Y as default};
import{S as E,i as M,s as T,F as j,c as H,m as L,t as w,a as y,d as S,b as g,e as _,f as p,g as k,h as d,j as z,l as B,k as N,n as D,o as v,p as C,q as G,r as F,u as A,v as I,w as h,x as J,y as P,z as R}from"./index.c63abb6b.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{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 we\u2019ll send you a recovery link:</p>`,n=g(),H(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),L(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=A(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),$&2&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),S(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=A(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),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(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=z(B.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new j({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){H(e.$$.fragment)},m(n,l){L(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){S(e,n)}}}function W(c,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.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends E{constructor(e){super(),M(this,e,W,V,T,{})}}export{Y as default};
@@ -1,4 +1,4 @@
import{S as z,i as A,s as B,F as D,f as S,m as U,n as $,o as v,q as j,H as G,L as J,h as _,w as M,x as N,p as b,y as P,D as y,e as m,g as d,j as g,E as R,z as W,d as C,A as F,B as E,C as Y,u as h,G as L}from"./index.944ee0db.js";function I(r){let e,s,t,l,n,o,c,a,i,u,k,q,p=r[3]&&T(r);return o=new W({props:{class:"form-field required",name:"password",$$slots:{default:[O,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),s=m("div"),t=m("h4"),l=y(`Type your password to confirm changing your email address
`),p&&p.c(),n=C(),S(o.$$.fragment),c=C(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","m-b-xs"),d(s,"class","content txt-center m-b-sm"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],F(a,"btn-loading",r[1])},m(f,w){_(f,e,w),g(e,s),g(s,t),g(t,l),p&&p.m(t,null),g(e,n),U(o,e,null),g(e,c),g(e,a),g(a,i),u=!0,k||(q=E(e,"submit",Y(r[4])),k=!0)},p(f,w){f[3]?p?p.p(f,w):(p=T(f),p.c(),p.m(t,null)):p&&(p.d(1),p=null);const H={};w&769&&(H.$$scope={dirty:w,ctx:f}),o.$set(H),(!u||w&2)&&(a.disabled=f[1]),w&2&&F(a,"btn-loading",f[1])},i(f){u||($(o.$$.fragment,f),u=!0)},o(f){v(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),j(o),k=!1,q()}}}function K(r){let e,s,t,l,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
import{S as J,i as M,s as N,F as R,c as S,m as U,t as $,a as v,d as z,C as W,E as Y,g as _,k as j,n as A,o as b,p as F,w as y,e as m,f as d,h as k,x as B,q as D,b as C,r as H,u as E,v as G,y as h,z as T}from"./index.c63abb6b.js";function I(r){let e,s,t,l,n,o,c,a,i,u,g,q,p=r[3]&&L(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[O,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),s=m("div"),t=m("h4"),l=y(`Type your password to confirm changing your email address
`),p&&p.c(),n=C(),S(o.$$.fragment),c=C(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","m-b-xs"),d(s,"class","content txt-center m-b-sm"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],H(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,s),k(s,t),k(t,l),p&&p.m(t,null),k(e,n),U(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||(q=E(e,"submit",G(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=L(f),p.c(),p.m(t,null)):p&&(p.d(1),p=null);const P={};w&769&&(P.$$scope={dirty:w,ctx:f}),o.$set(P),(!u||w&2)&&(a.disabled=f[1]),w&2&&H(a,"btn-loading",f[1])},i(f){u||($(o.$$.fragment,f),u=!0)},o(f){v(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),z(o),g=!1,q()}}}function K(r){let e,s,t,l,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Email address changed</p>
<p>You can now sign in with your new email address.</p></div>`,s=C(),t=m("button"),t.textContent="Close",d(e,"class","alert alert-success"),d(t,"type","button"),d(t,"class","btn btn-secondary btn-block")},m(o,c){_(o,e,c),_(o,s,c),_(o,t,c),l||(n=E(t,"click",r[6]),l=!0)},p:h,i:h,o:h,d(o){o&&b(e),o&&b(s),o&&b(t),l=!1,n()}}}function T(r){let e,s,t;return{c(){e=y("to "),s=m("strong"),t=y(r[3]),d(s,"class","txt-nowrap")},m(l,n){_(l,e,n),_(l,s,n),g(s,t)},p(l,n){n&8&&R(t,l[3])},d(l){l&&b(e),l&&b(s)}}}function O(r){let e,s,t,l,n,o,c,a;return{c(){e=m("label"),s=y("Password"),l=C(),n=m("input"),d(e,"for",t=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),g(e,s),_(i,l,u),_(i,n,u),L(n,r[0]),n.focus(),c||(a=E(n,"input",r[7]),c=!0)},p(i,u){u&256&&t!==(t=i[8])&&d(e,"for",t),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&L(n,i[0])},d(i){i&&b(e),i&&b(l),i&&b(n),c=!1,a()}}}function Q(r){let e,s,t,l;const n=[K,I],o=[];function c(a,i){return a[2]?0:1}return e=c(r),s=o[e]=n[e](r),{c(){s.c(),t=J()},m(a,i){o[e].m(a,i),_(a,t,i),l=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(M(),v(o[u],1,1,()=>{o[u]=null}),N(),s=o[e],s?s.p(a,i):(s=o[e]=n[e](a),s.c()),$(s,1),s.m(t.parentNode,t))},i(a){l||($(s),l=!0)},o(a){v(s),l=!1},d(a){o[e].d(a),a&&b(t)}}}function V(r){let e,s;return e=new D({props:{nobranding:!0,$$slots:{default:[Q]},$$scope:{ctx:r}}}),{c(){S(e.$$.fragment)},m(t,l){U(e,t,l),s=!0},p(t,[l]){const n={};l&527&&(n.$$scope={dirty:l,ctx:t}),e.$set(n)},i(t){s||($(e.$$.fragment,t),s=!0)},o(t){v(e.$$.fragment,t),s=!1},d(t){j(e,t)}}}function X(r,e,s){let t,{params:l}=e,n="",o=!1,c=!1;async function a(){if(!o){s(1,o=!0);try{await P.Users.confirmEmailChange(l==null?void 0:l.token,n),s(2,c=!0)}catch(k){P.errorResponseHandler(k)}s(1,o=!1)}}const i=()=>window.close();function u(){n=this.value,s(0,n)}return r.$$set=k=>{"params"in k&&s(5,l=k.params)},r.$$.update=()=>{r.$$.dirty&32&&s(3,t=G.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[n,o,c,t,a,l,i,u]}class x extends z{constructor(e){super(),A(this,e,X,V,B,{params:5})}}export{x as default};
<p>You can now sign in with your new email address.</p></div>`,s=C(),t=m("button"),t.textContent="Close",d(e,"class","alert alert-success"),d(t,"type","button"),d(t,"class","btn btn-secondary btn-block")},m(o,c){_(o,e,c),_(o,s,c),_(o,t,c),l||(n=E(t,"click",r[6]),l=!0)},p:h,i:h,o:h,d(o){o&&b(e),o&&b(s),o&&b(t),l=!1,n()}}}function L(r){let e,s,t;return{c(){e=y("to "),s=m("strong"),t=y(r[3]),d(s,"class","txt-nowrap")},m(l,n){_(l,e,n),_(l,s,n),k(s,t)},p(l,n){n&8&&B(t,l[3])},d(l){l&&b(e),l&&b(s)}}}function O(r){let e,s,t,l,n,o,c,a;return{c(){e=m("label"),s=y("Password"),l=C(),n=m("input"),d(e,"for",t=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),k(e,s),_(i,l,u),_(i,n,u),T(n,r[0]),n.focus(),c||(a=E(n,"input",r[7]),c=!0)},p(i,u){u&256&&t!==(t=i[8])&&d(e,"for",t),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&T(n,i[0])},d(i){i&&b(e),i&&b(l),i&&b(n),c=!1,a()}}}function Q(r){let e,s,t,l;const n=[K,I],o=[];function c(a,i){return a[2]?0:1}return e=c(r),s=o[e]=n[e](r),{c(){s.c(),t=Y()},m(a,i){o[e].m(a,i),_(a,t,i),l=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(j(),v(o[u],1,1,()=>{o[u]=null}),A(),s=o[e],s?s.p(a,i):(s=o[e]=n[e](a),s.c()),$(s,1),s.m(t.parentNode,t))},i(a){l||($(s),l=!0)},o(a){v(s),l=!1},d(a){o[e].d(a),a&&b(t)}}}function V(r){let e,s;return e=new R({props:{nobranding:!0,$$slots:{default:[Q]},$$scope:{ctx:r}}}),{c(){S(e.$$.fragment)},m(t,l){U(e,t,l),s=!0},p(t,[l]){const n={};l&527&&(n.$$scope={dirty:l,ctx:t}),e.$set(n)},i(t){s||($(e.$$.fragment,t),s=!0)},o(t){v(e.$$.fragment,t),s=!1},d(t){z(e,t)}}}function X(r,e,s){let t,{params:l}=e,n="",o=!1,c=!1;async function a(){if(!o){s(1,o=!0);try{await F.Users.confirmEmailChange(l==null?void 0:l.token,n),s(2,c=!0)}catch(g){F.errorResponseHandler(g)}s(1,o=!1)}}const i=()=>window.close();function u(){n=this.value,s(0,n)}return r.$$set=g=>{"params"in g&&s(5,l=g.params)},r.$$.update=()=>{r.$$.dirty&32&&s(3,t=W.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[n,o,c,t,a,l,i,u]}class x extends J{constructor(e){super(),M(this,e,X,V,N,{params:5})}}export{x as default};
@@ -1,4 +0,0 @@
import{S as D,i as E,s as G,F as J,f as F,m as L,n as P,o as q,q as N,H as M,L as W,h as _,w as Y,x as I,p as m,y as j,D as y,e as b,j as w,E as K,z,d as C,g as c,A,B as R,C as O,u as h,G as H}from"./index.944ee0db.js";function Q(r){let e,l,t,n,s,o,p,a,i,u,v,g,k,S,d=r[4]&&B(r);return o=new z({props:{class:"form-field required",name:"password",$$slots:{default:[X,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),a=new z({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[Z,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),{c(){e=b("form"),l=b("div"),t=b("h4"),n=y(`Reset your user password
`),d&&d.c(),s=C(),F(o.$$.fragment),p=C(),F(a.$$.fragment),i=C(),u=b("button"),v=b("span"),v.textContent="Set new password",c(t,"class","m-b-xs"),c(l,"class","content txt-center m-b-sm"),c(v,"class","txt"),c(u,"type","submit"),c(u,"class","btn btn-lg btn-block"),u.disabled=r[2],A(u,"btn-loading",r[2])},m(f,$){_(f,e,$),w(e,l),w(l,t),w(t,n),d&&d.m(t,null),w(e,s),L(o,e,null),w(e,p),L(a,e,null),w(e,i),w(e,u),w(u,v),g=!0,k||(S=R(e,"submit",O(r[5])),k=!0)},p(f,$){f[4]?d?d.p(f,$):(d=B(f),d.c(),d.m(t,null)):d&&(d.d(1),d=null);const T={};$&3073&&(T.$$scope={dirty:$,ctx:f}),o.$set(T);const U={};$&3074&&(U.$$scope={dirty:$,ctx:f}),a.$set(U),(!g||$&4)&&(u.disabled=f[2]),$&4&&A(u,"btn-loading",f[2])},i(f){g||(P(o.$$.fragment,f),P(a.$$.fragment,f),g=!0)},o(f){q(o.$$.fragment,f),q(a.$$.fragment,f),g=!1},d(f){f&&m(e),d&&d.d(),N(o),N(a),k=!1,S()}}}function V(r){let e,l,t,n,s;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Password changed</p>
<p>You can now sign in with your new password.</p></div>`,l=C(),t=b("button"),t.textContent="Close",c(e,"class","alert alert-success"),c(t,"type","button"),c(t,"class","btn btn-secondary btn-block")},m(o,p){_(o,e,p),_(o,l,p),_(o,t,p),n||(s=R(t,"click",r[7]),n=!0)},p:h,i:h,o:h,d(o){o&&m(e),o&&m(l),o&&m(t),n=!1,s()}}}function B(r){let e,l,t;return{c(){e=y("for "),l=b("strong"),t=y(r[4])},m(n,s){_(n,e,s),_(n,l,s),w(l,t)},p(n,s){s&16&&K(t,n[4])},d(n){n&&m(e),n&&m(l)}}}function X(r){let e,l,t,n,s,o,p,a;return{c(){e=b("label"),l=y("New password"),n=C(),s=b("input"),c(e,"for",t=r[10]),c(s,"type","password"),c(s,"id",o=r[10]),s.required=!0,s.autofocus=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,s,u),H(s,r[0]),s.focus(),p||(a=R(s,"input",r[8]),p=!0)},p(i,u){u&1024&&t!==(t=i[10])&&c(e,"for",t),u&1024&&o!==(o=i[10])&&c(s,"id",o),u&1&&s.value!==i[0]&&H(s,i[0])},d(i){i&&m(e),i&&m(n),i&&m(s),p=!1,a()}}}function Z(r){let e,l,t,n,s,o,p,a;return{c(){e=b("label"),l=y("New password confirm"),n=C(),s=b("input"),c(e,"for",t=r[10]),c(s,"type","password"),c(s,"id",o=r[10]),s.required=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,s,u),H(s,r[1]),p||(a=R(s,"input",r[9]),p=!0)},p(i,u){u&1024&&t!==(t=i[10])&&c(e,"for",t),u&1024&&o!==(o=i[10])&&c(s,"id",o),u&2&&s.value!==i[1]&&H(s,i[1])},d(i){i&&m(e),i&&m(n),i&&m(s),p=!1,a()}}}function x(r){let e,l,t,n;const s=[V,Q],o=[];function p(a,i){return a[3]?0:1}return e=p(r),l=o[e]=s[e](r),{c(){l.c(),t=W()},m(a,i){o[e].m(a,i),_(a,t,i),n=!0},p(a,i){let u=e;e=p(a),e===u?o[e].p(a,i):(Y(),q(o[u],1,1,()=>{o[u]=null}),I(),l=o[e],l?l.p(a,i):(l=o[e]=s[e](a),l.c()),P(l,1),l.m(t.parentNode,t))},i(a){n||(P(l),n=!0)},o(a){q(l),n=!1},d(a){o[e].d(a),a&&m(t)}}}function ee(r){let e,l;return e=new J({props:{nobranding:!0,$$slots:{default:[x]},$$scope:{ctx:r}}}),{c(){F(e.$$.fragment)},m(t,n){L(e,t,n),l=!0},p(t,[n]){const s={};n&2079&&(s.$$scope={dirty:n,ctx:t}),e.$set(s)},i(t){l||(P(e.$$.fragment,t),l=!0)},o(t){q(e.$$.fragment,t),l=!1},d(t){N(e,t)}}}function te(r,e,l){let t,{params:n}=e,s="",o="",p=!1,a=!1;async function i(){if(!p){l(2,p=!0);try{await j.Users.confirmPasswordReset(n==null?void 0:n.token,s,o),l(3,a=!0)}catch(k){j.errorResponseHandler(k)}l(2,p=!1)}}const u=()=>window.close();function v(){s=this.value,l(0,s)}function g(){o=this.value,l(1,o)}return r.$$set=k=>{"params"in k&&l(6,n=k.params)},r.$$.update=()=>{r.$$.dirty&64&&l(4,t=M.getJWTPayload(n==null?void 0:n.token).email||"")},[s,o,p,a,t,i,n,u,v,g]}class le extends D{constructor(e){super(),E(this,e,te,ee,G,{params:6})}}export{le as default};
@@ -0,0 +1,4 @@
import{S as W,i as Y,s as j,F as A,c as H,m as N,t as P,a as q,d as S,C as B,E as D,g as _,k as G,n as I,o as m,p as z,w as y,e as b,h as w,x as K,q as E,b as C,f as c,r as J,u as h,v as O,y as F,z as R}from"./index.c63abb6b.js";function Q(i){let e,l,t,n,s,o,p,a,r,u,v,g,k,L,d=i[4]&&M(i);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:i}}}),a=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[Z,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:i}}}),{c(){e=b("form"),l=b("div"),t=b("h4"),n=y(`Reset your user password
`),d&&d.c(),s=C(),H(o.$$.fragment),p=C(),H(a.$$.fragment),r=C(),u=b("button"),v=b("span"),v.textContent="Set new password",c(t,"class","m-b-xs"),c(l,"class","content txt-center m-b-sm"),c(v,"class","txt"),c(u,"type","submit"),c(u,"class","btn btn-lg btn-block"),u.disabled=i[2],J(u,"btn-loading",i[2])},m(f,$){_(f,e,$),w(e,l),w(l,t),w(t,n),d&&d.m(t,null),w(e,s),N(o,e,null),w(e,p),N(a,e,null),w(e,r),w(e,u),w(u,v),g=!0,k||(L=h(e,"submit",O(i[5])),k=!0)},p(f,$){f[4]?d?d.p(f,$):(d=M(f),d.c(),d.m(t,null)):d&&(d.d(1),d=null);const T={};$&3073&&(T.$$scope={dirty:$,ctx:f}),o.$set(T);const U={};$&3074&&(U.$$scope={dirty:$,ctx:f}),a.$set(U),(!g||$&4)&&(u.disabled=f[2]),$&4&&J(u,"btn-loading",f[2])},i(f){g||(P(o.$$.fragment,f),P(a.$$.fragment,f),g=!0)},o(f){q(o.$$.fragment,f),q(a.$$.fragment,f),g=!1},d(f){f&&m(e),d&&d.d(),S(o),S(a),k=!1,L()}}}function V(i){let e,l,t,n,s;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Password changed</p>
<p>You can now sign in with your new password.</p></div>`,l=C(),t=b("button"),t.textContent="Close",c(e,"class","alert alert-success"),c(t,"type","button"),c(t,"class","btn btn-secondary btn-block")},m(o,p){_(o,e,p),_(o,l,p),_(o,t,p),n||(s=h(t,"click",i[7]),n=!0)},p:F,i:F,o:F,d(o){o&&m(e),o&&m(l),o&&m(t),n=!1,s()}}}function M(i){let e,l,t;return{c(){e=y("for "),l=b("strong"),t=y(i[4])},m(n,s){_(n,e,s),_(n,l,s),w(l,t)},p(n,s){s&16&&K(t,n[4])},d(n){n&&m(e),n&&m(l)}}}function X(i){let e,l,t,n,s,o,p,a;return{c(){e=b("label"),l=y("New password"),n=C(),s=b("input"),c(e,"for",t=i[10]),c(s,"type","password"),c(s,"id",o=i[10]),s.required=!0,s.autofocus=!0},m(r,u){_(r,e,u),w(e,l),_(r,n,u),_(r,s,u),R(s,i[0]),s.focus(),p||(a=h(s,"input",i[8]),p=!0)},p(r,u){u&1024&&t!==(t=r[10])&&c(e,"for",t),u&1024&&o!==(o=r[10])&&c(s,"id",o),u&1&&s.value!==r[0]&&R(s,r[0])},d(r){r&&m(e),r&&m(n),r&&m(s),p=!1,a()}}}function Z(i){let e,l,t,n,s,o,p,a;return{c(){e=b("label"),l=y("New password confirm"),n=C(),s=b("input"),c(e,"for",t=i[10]),c(s,"type","password"),c(s,"id",o=i[10]),s.required=!0},m(r,u){_(r,e,u),w(e,l),_(r,n,u),_(r,s,u),R(s,i[1]),p||(a=h(s,"input",i[9]),p=!0)},p(r,u){u&1024&&t!==(t=r[10])&&c(e,"for",t),u&1024&&o!==(o=r[10])&&c(s,"id",o),u&2&&s.value!==r[1]&&R(s,r[1])},d(r){r&&m(e),r&&m(n),r&&m(s),p=!1,a()}}}function x(i){let e,l,t,n;const s=[V,Q],o=[];function p(a,r){return a[3]?0:1}return e=p(i),l=o[e]=s[e](i),{c(){l.c(),t=D()},m(a,r){o[e].m(a,r),_(a,t,r),n=!0},p(a,r){let u=e;e=p(a),e===u?o[e].p(a,r):(G(),q(o[u],1,1,()=>{o[u]=null}),I(),l=o[e],l?l.p(a,r):(l=o[e]=s[e](a),l.c()),P(l,1),l.m(t.parentNode,t))},i(a){n||(P(l),n=!0)},o(a){q(l),n=!1},d(a){o[e].d(a),a&&m(t)}}}function ee(i){let e,l;return e=new A({props:{nobranding:!0,$$slots:{default:[x]},$$scope:{ctx:i}}}),{c(){H(e.$$.fragment)},m(t,n){N(e,t,n),l=!0},p(t,[n]){const s={};n&2079&&(s.$$scope={dirty:n,ctx:t}),e.$set(s)},i(t){l||(P(e.$$.fragment,t),l=!0)},o(t){q(e.$$.fragment,t),l=!1},d(t){S(e,t)}}}function te(i,e,l){let t,{params:n}=e,s="",o="",p=!1,a=!1;async function r(){if(!p){l(2,p=!0);try{await z.Users.confirmPasswordReset(n==null?void 0:n.token,s,o),l(3,a=!0)}catch(k){z.errorResponseHandler(k)}l(2,p=!1)}}const u=()=>window.close();function v(){s=this.value,l(0,s)}function g(){o=this.value,l(1,o)}return i.$$set=k=>{"params"in k&&l(6,n=k.params)},i.$$.update=()=>{i.$$.dirty&64&&l(4,t=B.getJWTPayload(n==null?void 0:n.token).email||"")},[s,o,p,a,t,r,n,u,v,g]}class le extends W{constructor(e){super(),Y(this,e,te,ee,j,{params:6})}}export{le as default};
@@ -1,3 +1,3 @@
import{S as k,i as v,s as y,F as w,f as x,m as C,n as g,o as L,q as $,y as H,L as M,h as r,p as a,e as u,d as m,g as f,B as _,u as p}from"./index.944ee0db.js";function P(o){let t,s,e,n,i;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-error-warning-line"></i></div>
import{S as k,i as v,s as y,F as w,c as x,m as C,t as g,a as $,d as L,p as H,E as M,g as r,o as a,e as u,b as m,f,u as _,y as p}from"./index.c63abb6b.js";function P(o){let t,s,e,n,i;return{c(){t=u("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=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-danger"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,c){r(l,t,c),r(l,s,c),r(l,e,c),n||(i=_(e,"click",o[4]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function S(o){let t,s,e,n,i;return{c(){t=u("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=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,c){r(l,t,c),r(l,s,c),r(l,e,c),n||(i=_(e,"click",o[3]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function T(o){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function q(o){let t;function s(i,l){return i[1]?T:i[0]?S:P}let e=s(o),n=e(o);return{c(){n.c(),t=M()},m(i,l){n.m(i,l),r(i,t,l)},p(i,l){e===(e=s(i))&&n?n.p(i,l):(n.d(1),n=e(i),n&&(n.c(),n.m(t.parentNode,t)))},d(i){n.d(i),i&&a(t)}}}function F(o){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[q]},$$scope:{ctx:o}}}),{c(){x(t.$$.fragment)},m(e,n){C(t,e,n),s=!0},p(e,[n]){const i={};n&67&&(i.$$scope={dirty:n,ctx:e}),t.$set(i)},i(e){s||(g(t.$$.fragment,e),s=!0)},o(e){L(t.$$.fragment,e),s=!1},d(e){$(t,e)}}}function U(o,t,s){let{params:e}=t,n=!1,i=!1;l();async function l(){s(1,i=!0);try{await H.Users.confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch(d){console.warn(d),s(0,n=!1)}s(1,i=!1)}const c=()=>window.close(),b=()=>window.close();return o.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,i,e,c,b]}class B extends k{constructor(t){super(),v(this,t,U,F,y,{params:2})}}export{B as default};
<div class="content txt-bold"><p>Successfully verified email address.</p></div>`,s=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,c){r(l,t,c),r(l,s,c),r(l,e,c),n||(i=_(e,"click",o[3]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function T(o){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function F(o){let t;function s(i,l){return i[1]?T:i[0]?S:P}let e=s(o),n=e(o);return{c(){n.c(),t=M()},m(i,l){n.m(i,l),r(i,t,l)},p(i,l){e===(e=s(i))&&n?n.p(i,l):(n.d(1),n=e(i),n&&(n.c(),n.m(t.parentNode,t)))},d(i){n.d(i),i&&a(t)}}}function U(o){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[F]},$$scope:{ctx:o}}}),{c(){x(t.$$.fragment)},m(e,n){C(t,e,n),s=!0},p(e,[n]){const i={};n&67&&(i.$$scope={dirty:n,ctx:e}),t.$set(i)},i(e){s||(g(t.$$.fragment,e),s=!0)},o(e){$(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function V(o,t,s){let{params:e}=t,n=!1,i=!1;l();async function l(){s(1,i=!0);try{await H.Users.confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch(d){console.warn(d),s(0,n=!1)}s(1,i=!1)}const c=()=>window.close(),b=()=>window.close();return o.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,i,e,c,b]}class E extends k{constructor(t){super(),v(this,t,V,U,y,{params:2})}}export{E as default};
-363
View File
File diff suppressed because one or more lines are too long
+364
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -20,8 +20,8 @@
window.Prism = window.Prism || {};
window.Prism.manual = true;
</script>
<script type="module" crossorigin src="/_/assets/index.944ee0db.js"></script>
<link rel="stylesheet" href="/_/assets/index.35a93598.css">
<script type="module" crossorigin src="/_/assets/index.c63abb6b.js"></script>
<link rel="stylesheet" href="/_/assets/index.d97a62dd.css">
</head>
<body>
<div id="app"></div>
+2 -2
View File
@@ -13,8 +13,8 @@ var distDir embed.FS
//go:embed dist/index.html
var indexHTML embed.FS
// DistDirFS contains the embeded dist directory files (without the "dist" prefix)
// DistDirFS contains the embedded dist directory files (without the "dist" prefix)
var DistDirFS = echo.MustSubFS(distDir, "dist")
// DistIndexHTML contains the embeded dist/index.html file (without the "dist" prefix)
// DistIndexHTML contains the embedded dist/index.html file (without the "dist" prefix)
var DistIndexHTML = echo.MustSubFS(indexHTML, "dist")
+37 -37
View File
@@ -17,7 +17,7 @@
"chart.js": "^3.7.1",
"chartjs-adapter-luxon": "^1.1.0",
"luxon": "^2.3.2",
"pocketbase": "^0.1.1",
"pocketbase": "^0.1.2",
"prismjs": "^1.28.0",
"sass": "^1.45.0",
"svelte": "^3.44.0",
@@ -27,9 +27,9 @@
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.3.tgz",
"integrity": "sha512-JTSBDC4tUyR8iRmCwQJaYpTXtOZmRn4gKjw1Fu4xIatFPqTJ7m0QRCdkdbzlvMovzjTiuHp4a8WUEB1c/LtiHg==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.4.tgz",
"integrity": "sha512-uP7UodCRykPNwSAN+wYa/AS9gJI/V47echCAXUYgCgBXy3l19nwO7W/d29COtG/dfAsjBOhMDeh3Ms8Y5VZbrA==",
"dev": true,
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -97,9 +97,9 @@
"dev": true
},
"node_modules/@codemirror/view": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz",
"integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.3.tgz",
"integrity": "sha512-1gDBymhbx2DZzwnR/rNUu1LiQqjxBJtFiB+4uLR6tHQ6vKhTIwUsP5uZUQ7SM7JxVx3UihMynnTqjcsC+mczZg==",
"dev": true,
"dependencies": {
"@codemirror/state": "^6.0.0",
@@ -922,9 +922,9 @@
}
},
"node_modules/pocketbase": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.1.1.tgz",
"integrity": "sha512-OW7h88+TS+TvBSdFTBq67x4TLdIET3KFqstTa+ufHKYv0wU3mdSnQO8yK/I05ExvY2XYWMH8csyB34W7+e9wDA==",
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.1.2.tgz",
"integrity": "sha512-FnxpKttOsS0bfo0z6u20LgEPfvG/ItxbK/bOUnaCOov8rHs5qWP6al3cS0vGkieFOo0jqWwrexo4yzPIxnYsHw==",
"dev": true,
"dependencies": {
"axios": "^0.21.1",
@@ -1018,9 +1018,9 @@
}
},
"node_modules/rollup": {
"version": "2.75.7",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
"integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==",
"version": "2.76.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz",
"integrity": "sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -1097,9 +1097,9 @@
}
},
"node_modules/svelte": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
"integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==",
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"dev": true,
"engines": {
"node": ">= 8"
@@ -1154,9 +1154,9 @@
}
},
"node_modules/vite": {
"version": "2.9.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
"integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
"version": "2.9.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.14.tgz",
"integrity": "sha512-P/UCjSpSMcE54r4mPak55hWAZPlyfS369svib/gpmz8/01L822lMPOJ/RYW6tLCe1RPvMvOsJ17erf55bKp4Hw==",
"dev": true,
"dependencies": {
"esbuild": "^0.14.27",
@@ -1199,9 +1199,9 @@
},
"dependencies": {
"@codemirror/autocomplete": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.3.tgz",
"integrity": "sha512-JTSBDC4tUyR8iRmCwQJaYpTXtOZmRn4gKjw1Fu4xIatFPqTJ7m0QRCdkdbzlvMovzjTiuHp4a8WUEB1c/LtiHg==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.0.4.tgz",
"integrity": "sha512-uP7UodCRykPNwSAN+wYa/AS9gJI/V47echCAXUYgCgBXy3l19nwO7W/d29COtG/dfAsjBOhMDeh3Ms8Y5VZbrA==",
"dev": true,
"requires": {
"@codemirror/language": "^6.0.0",
@@ -1263,9 +1263,9 @@
"dev": true
},
"@codemirror/view": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz",
"integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.3.tgz",
"integrity": "sha512-1gDBymhbx2DZzwnR/rNUu1LiQqjxBJtFiB+4uLR6tHQ6vKhTIwUsP5uZUQ7SM7JxVx3UihMynnTqjcsC+mczZg==",
"dev": true,
"requires": {
"@codemirror/state": "^6.0.0",
@@ -1767,9 +1767,9 @@
"dev": true
},
"pocketbase": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.1.1.tgz",
"integrity": "sha512-OW7h88+TS+TvBSdFTBq67x4TLdIET3KFqstTa+ufHKYv0wU3mdSnQO8yK/I05ExvY2XYWMH8csyB34W7+e9wDA==",
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.1.2.tgz",
"integrity": "sha512-FnxpKttOsS0bfo0z6u20LgEPfvG/ItxbK/bOUnaCOov8rHs5qWP6al3cS0vGkieFOo0jqWwrexo4yzPIxnYsHw==",
"dev": true,
"requires": {
"axios": "^0.21.1",
@@ -1829,9 +1829,9 @@
}
},
"rollup": {
"version": "2.75.7",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
"integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==",
"version": "2.76.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz",
"integrity": "sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -1884,9 +1884,9 @@
"dev": true
},
"svelte": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
"integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==",
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"dev": true
},
"svelte-flatpickr": {
@@ -1924,9 +1924,9 @@
}
},
"vite": {
"version": "2.9.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
"integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
"version": "2.9.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.14.tgz",
"integrity": "sha512-P/UCjSpSMcE54r4mPak55hWAZPlyfS369svib/gpmz8/01L822lMPOJ/RYW6tLCe1RPvMvOsJ17erf55bKp4Hw==",
"dev": true,
"requires": {
"esbuild": "^0.14.27",
+1 -1
View File
@@ -23,7 +23,7 @@
"chart.js": "^3.7.1",
"chartjs-adapter-luxon": "^1.1.0",
"luxon": "^2.3.2",
"pocketbase": "^0.1.1",
"pocketbase": "^0.1.2",
"prismjs": "^1.28.0",
"sass": "^1.45.0",
"svelte": "^3.44.0",
+6 -6
View File
@@ -16,11 +16,15 @@
let oldLocation = undefined;
let showAppSidebar = false;
function handleRouteLoading(e) {
if (e?.detail?.location === oldLocation) {
return; // not an actual change
}
showAppSidebar = !!e?.detail?.userData?.showAppSidebar;
oldLocation = e?.detail?.location;
// resets
@@ -30,11 +34,7 @@
}
function handleRouteFailure() {
if (ApiClient.AuthStore.isValid) {
replace("/");
} else {
ApiClient.logout();
}
replace("/");
}
function logout() {
@@ -43,7 +43,7 @@
</script>
<div class="app-layout">
{#if $admin?.id}
{#if $admin?.id && showAppSidebar}
<aside class="app-sidebar">
<a href="/" class="logo logo-sm" use:link>
<img
-487
View File
@@ -1,487 +0,0 @@
<script>
import tooltip from "@/actions/tooltip";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import { addInfoToast } from "@/stores/toasts";
let popupActive = true;
setTimeout(function () {
addInfoToast("Hello world");
}, 500);
</script>
<div class="form-field">
<label for="">EXAMPLE</label>
<ObjectSelect multiple searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="form-field">
<label for="">EXAMPLE</label>
<ObjectSelect searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="form-field disabled">
<label for="">EXAMPLE</label>
<ObjectSelect disabled searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="alert">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-info">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-danger">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-warning">
<div class="icon">
<i class="ri-error-warning-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-success">
<div class="icon">
<i class="ri-checkbox-circle-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<hr />
<h1>H1 title</h1>
<p>Lorem Ipsum dolor sit amet...</p>
<h2>H2 title</h2>
<p>Lorem Ipsum dolor sit amet...</p>
<h3>H3 title</h3>
<p>Lorem Ipsum dolor sit amet...</p>
<h4>H4 title</h4>
<p>Lorem Ipsum dolor sit amet...</p>
<h5>H5 title</h5>
<p>Lorem Ipsum dolor sit amet...</p>
<h6>H6 title</h6>
<p>Lorem Ipsum dolor sit amet...</p>
<hr />
<div class="grid">
<div class="col-6">COL1</div>
<div class="col-6">COL2</div>
</div>
<p>
Lorem Ipsum is <a href="/">simply dummy</a> text of the printing and typesetting industry. Lorem Ipsum has
been the industry's
<strong>standard</strong> dummy text ever since the 1500s, when an unknown printer took a galley of type
and <em>scrambled</em> it to make a type specimen book. It has survived not only five centuries, but also
the leap into electronic typesetting, remaining<sup>1</sup> essentially<sub>2</sub> unchanged.
</p>
<p>
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and
more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum
</p>
<ul>
<li><small>Option 1</small></li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
<ol>
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ol>
<hr />
<span use:tooltip={"My tooltip"}>Lorem Ipsum</span>
<hr />
<button class="btn">Button default</button>
<button class="btn btn-danger">Button danger</button>
<button class="btn btn-warning">Button warning</button>
<button class="btn btn-success">Button success</button>
<button class="btn btn-info">Button info</button>
<button class="btn btn-hint">Button hint</button>
<hr />
<button class="btn btn-secondary">Button default</button>
<button class="btn btn-secondary btn-danger">Button danger</button>
<button class="btn btn-secondary btn-warning">Button danger</button>
<button class="btn btn-secondary btn-success">Button success</button>
<button class="btn btn-secondary btn-info">Button info</button>
<button class="btn btn-secondary btn-hint">Button hint</button>
<hr />
<button class="btn btn-outline">Button default</button>
<button class="btn btn-outline btn-danger">Button danger</button>
<button class="btn btn-outline btn-warning">Button danger</button>
<button class="btn btn-outline btn-success">Button success</button>
<button class="btn btn-outline btn-info">Button info</button>
<button class="btn btn-outline btn-hint">Button hint</button>
<hr />
<button disabled class="btn">Button default</button>
<button disabled class="btn btn-danger">Button danger</button>
<button disabled class="btn btn-warning">Button warning</button>
<button disabled class="btn btn-success">Button success</button>
<button disabled class="btn btn-info">Button info</button>
<button disabled class="btn btn-hint">Button hint</button>
<button disabled class="btn btn-secondary">Button default</button>
<button disabled class="btn btn-secondary btn-danger">Button danger</button>
<button disabled class="btn btn-secondary btn-warning">Button danger</button>
<button disabled class="btn btn-secondary btn-success">Button success</button>
<button disabled class="btn btn-secondary btn-info">Button info</button>
<button disabled class="btn btn-secondary btn-hint">Button hint</button>
<hr />
<button class="btn">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<button class="btn btn-loading">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-primary btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-loading btn-primary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-loading btn-danger btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<button disabled class="btn btn-loading">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-primary btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-circle">
<i class="ri-mail-line" />
</button>
<button disabled class="btn btn-loading btn-primary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button disabled class="btn btn-loading btn-danger btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<input type="text" />
<hr />
<select>
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
<hr />
<textarea cols="30" rows="10" />
<hr />
<div class="form-field required">
<label for="field_1">Name</label>
<input type="text" id="field_1" placeholder="Name 123" />
</div>
<div class="form-field required">
<label for="field_2">Description</label>
<textarea id="field_2" />
</div>
<div class="form-field">
<label for="field_3">Choose value</label>
<select id="field_3">
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
</div>
<hr />
<div class="form-field">
<input type="text" placeholder="Lorem ipsum dolor sit amet..." />
</div>
<div class="form-field">
<textarea />
</div>
<div class="form-field">
<select>
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
</div>
<hr />
<div class="form-field">
<input type="checkbox" id="field_check" />
<label for="field_check">
I agree with the <a href="/">terms and conditions</a>
</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio1" value="1" />
<label for="field_radio1">Radio 1</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio2" value="2" />
<label for="field_radio2">Radio 2</label>
</div>
<div class="form-field form-field-toggle">
<input type="checkbox" id="field_toggle" />
<label for="field_toggle">Toggle check</label>
</div>
<hr />
<div class="form-field error">
<label for="field_error">Name + error</label>
<input type="text" id="field_error" />
<div class="help-block help-block-error">
<p>Something went wrong</p>
</div>
</div>
<div class="form-field">
<label for="field_hint">Name + hint</label>
<input type="text" id="field_hint" />
<div class="help-block">
<p>Lorem ipsum dolor <a href="/">sit</a> amet</p>
</div>
</div>
<div class="form-field">
<input type="checkbox" id="field_check_hint" />
<label for="field_check_hint">Checkbox hint</label>
<div class="help-block">Lorem ipsum</div>
</div>
<div class="form-field has-error">
<input type="checkbox" id="field_check_error" />
<label for="field_check_error">Checkbox error</label>
<div class="help-block help-error">Lorem ipsum</div>
</div>
<hr />
<div class="form-field disabled">
<input type="checkbox" id="field_check" disabled />
<label for="field_check">
I agree with the <a href="/">terms and conditions</a>
</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio1" value="1" disabled />
<label for="field_radio1">Radio 1</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio2" value="2" disabled />
<label for="field_radio2">Radio 2</label>
</div>
<div class="form-field form-field-toggle disabled">
<input type="checkbox" id="field_toggle" disabled />
<label for="field_toggle" />
</div>
<hr />
<div class="form-field disabled">
<label for="field_addon1">Name</label>
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input disabled type="text" id="field_addon1" />
</div>
<div class="form-field">
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input type="text" />
</div>
<hr />
<div class="form-group">
<div class="form-field">
<label for="field_group1">Name</label>
<div class="form-field-addon">
<div class="btn btn-circle btn-secondary">
<i class="ri-mail-line" />
</div>
</div>
<input type="text" id="field_group1" />
</div>
<div class="form-field">
<label for="field_group2">Password</label>
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input type="password" id="field_group2" />
</div>
</div>
<OverlayPanel bind:active={popupActive} popup={false}>
<h4 slot="header">My title</h4>
<p>Lorem ipsum dolor sit amet...</p>
<svelte:fragment slot="footer">
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-expanded">Save</button>
</svelte:fragment>
</OverlayPanel>
-5
View File
@@ -1,5 +0,0 @@
<script>
import { replace } from "svelte-spa-router";
replace("/collections");
</script>
+43
View File
@@ -0,0 +1,43 @@
<script>
import { tick } from "svelte";
import { replace } from "svelte-spa-router";
import ApiClient from "@/utils/ApiClient";
import FullPage from "@/components/base/FullPage.svelte";
import Installer from "@/components/base/Installer.svelte";
let showInstaller = false;
handler();
function handler() {
showInstaller = false;
const realQueryParams = new URLSearchParams(window.location.search);
if (realQueryParams.has(import.meta.env.PB_INSTALLER_PARAM)) {
ApiClient.logout(false);
showInstaller = true;
return;
}
if (ApiClient.AuthStore.isValid) {
replace("/collections");
} else {
ApiClient.logout();
}
}
</script>
{#if showInstaller}
<FullPage>
<Installer
on:submit={async () => {
showInstaller = false;
await tick();
// clear the installer param
window.location.search = "";
}}
/>
</FullPage>
{/if}
+76
View File
@@ -0,0 +1,76 @@
<script>
import { createEventDispatcher } from "svelte";
import ApiClient from "@/utils/ApiClient";
import Field from "@/components/base/Field.svelte";
const dispatch = createEventDispatcher();
let email = "";
let password = "";
let passwordConfirm = "";
let isLoading = false;
async function submit() {
if (isLoading) {
return;
}
isLoading = true;
try {
await ApiClient.Admins.create({
email,
password,
passwordConfirm,
});
await ApiClient.Admins.authViaEmail(email, password);
dispatch("submit");
} catch (err) {
ApiClient.errorResponseHandler(err);
}
isLoading = false;
}
</script>
<form class="block" autocomplete="off" on:submit|preventDefault={submit}>
<div class="content txt-center m-b-base">
<h4>Create your first admin account in order to continue</h4>
</div>
<Field class="form-field required" name="email" let:uniqueId>
<label for={uniqueId}>Email</label>
<!-- svelte-ignore a11y-autofocus -->
<input type="email" autocomplete="off" id={uniqueId} bind:value={email} required autofocus />
</Field>
<Field class="form-field required" name="password" let:uniqueId>
<label for={uniqueId}>Password</label>
<input
type="password"
autocomplete="new-password"
minlength="10"
id={uniqueId}
bind:value={password}
required
/>
<div class="help-block">Minimum 10 characters.</div>
</Field>
<Field class="form-field required" name="passwordConfirm" let:uniqueId>
<label for={uniqueId}>Password confirm</label>
<input type="password" minlength="10" id={uniqueId} bind:value={passwordConfirm} required />
</Field>
<button
type="submit"
class="btn btn-lg btn-block btn-next"
class:btn-disabled={isLoading}
class:btn-loading={isLoading}
>
<span class="txt">Create and login</span>
<i class="ri-arrow-right-line" />
</button>
</form>
@@ -22,12 +22,12 @@
// Subscribe to changes in any record from the collection
client.Realtime.subscribe("${collection?.name}", function (e) {
console.log(e.data);
console.log(e.record);
});
// Subscribe to changes in a single record
client.Realtime.subscribe("${collection?.name}/RECORD_ID", function (e) {
console.log(e.data);
console.log(e.record);
});
// Unsubscribe
@@ -18,6 +18,7 @@
required={field.required}
min={field.options?.min}
max={field.options?.max}
step="any"
bind:value
/>
</Field>
@@ -112,7 +112,7 @@
}
}}
>
Invalidate all previosly issued tokens
Invalidate all previously issued tokens
</span>
</div>
</Field>
+49 -23
View File
@@ -1,5 +1,7 @@
import { replace } from "svelte-spa-router";
import { wrap } from "svelte-spa-router/wrap";
import ApiClient from "@/utils/ApiClient";
import PageIndex from "@/components/PageIndex.svelte";
import PageLogs from "@/components/logs/PageLogs.svelte";
import PageRecords from "@/components/records/PageRecords.svelte";
import PageUsers from "@/components/users/PageUsers.svelte";
@@ -11,107 +13,131 @@ import PageStorage from "@/components/settings/PageStorage.svelte";
import PageAuthProviders from "@/components/settings/PageAuthProviders.svelte";
import PageTokenOptions from "@/components/settings/PageTokenOptions.svelte";
const routes = {
"/_elements": wrap({
asyncComponent: () => import("@/components/Elements.svelte"),
}),
const baseConditions = [
async (details) => {
const realQueryParams = new URLSearchParams(window.location.search);
if (details.location !== "/" && realQueryParams.has(import.meta.env.PB_INSTALLER_PARAM)) {
return replace("/")
}
return true
}
];
const routes = {
"/login": wrap({
component: PageAdminLogin,
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/request-password-reset": wrap({
asyncComponent: () => import("@/components/admins/PageAdminRequestPasswordReset.svelte"),
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/confirm-password-reset/:token": wrap({
asyncComponent: () => import("@/components/admins/PageAdminConfirmPasswordReset.svelte"),
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/collections": wrap({
component: PageRecords,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/logs": wrap({
component: PageLogs,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/users": wrap({
component: PageUsers,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/users/confirm-password-reset/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmPasswordReset.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/users/confirm-verification/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmVerification.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/users/confirm-email-change/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmEmailChange.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/settings": wrap({
component: PageApplication,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/admins": wrap({
component: PageAdmins,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/mail": wrap({
component: PageMail,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/storage": wrap({
component: PageStorage,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/auth-providers": wrap({
component: PageAuthProviders,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/tokens": wrap({
component: PageTokenOptions,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
// fallback
"*": wrap({
asyncComponent: () => import("@/components/NotFoundPage.svelte"),
component: PageIndex,
userData: { showAppSidebar: false },
}),
};
+1
View File
@@ -129,6 +129,7 @@
// -------------------------------------------------------------------
.page-sidebar {
z-index: 0;
display: flex;
flex-direction: column;
width: var(--pageSidebarWidth);