mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-28 18:26:36 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd9b1d26ea | ||
|
|
4b829757b2 | ||
|
|
b5b01cb6dd | ||
|
|
287314f016 | ||
|
|
73e7e0b1c5 | ||
|
|
d070b9a778 | ||
|
|
d976bf5965 | ||
|
|
052ac008c3 | ||
|
|
57a2b2bc83 | ||
|
|
043f82ad79 | ||
|
|
ba61cdba4e | ||
|
|
dcd1ae96e0 | ||
|
|
1fdb058386 | ||
|
|
29cb5513a0 | ||
|
|
6db57d9f27 | ||
|
|
1a77bd9914 | ||
|
|
350335711b | ||
|
|
988c425150 | ||
|
|
23827ba1d1 | ||
|
|
7d36bda769 | ||
|
|
8c559ea067 | ||
|
|
88832d4bc9 | ||
|
|
f5cece3b0e | ||
|
|
d5485238b8 | ||
|
|
ac5a121f66 | ||
|
|
481df3bcb9 | ||
|
|
7677a3de2c | ||
|
|
1f65c01b04 | ||
|
|
d5928f6fea | ||
|
|
bef77ac8dc | ||
|
|
c8eb034c49 | ||
|
|
c77167df46 | ||
|
|
3717a663d9 | ||
|
|
5814549cbe | ||
|
|
2e5d268798 | ||
|
|
4ed312251e | ||
|
|
946c534b08 | ||
|
|
883877adec | ||
|
|
215531d65c | ||
|
|
c0f055c3c0 | ||
|
|
d77044882d | ||
|
|
d6795300b1 | ||
|
|
fd3c76ffa3 | ||
|
|
698bc3a35a | ||
|
|
1bcb50edc3 | ||
|
|
9700afb9cb |
6
.github/workflows/build-next.yml
vendored
6
.github/workflows/build-next.yml
vendored
@@ -5,6 +5,10 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-next-image
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-next:
|
build-next:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -58,7 +62,7 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: sh scripts/development/build-binaries.sh
|
run: sh scripts/development/build-binaries.sh --docker-only
|
||||||
|
|
||||||
- name: Build and push container image
|
- name: Build and push container image
|
||||||
id: build-push-image
|
id: build-push-image
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,3 +1,48 @@
|
|||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v1.4.1...v) (2025-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* improve initial admin creation workflow ([287314f](https://github.com/pocket-id/pocket-id/commit/287314f01644e42ddb2ce1b1115bd14f2f0c1768))
|
||||||
|
* redact sensitive app config variables if set with env variable ([ba61cdb](https://github.com/pocket-id/pocket-id/commit/ba61cdba4eb3d5659f3ae6b6c21249985c0aa630))
|
||||||
|
* self-service user signup ([#672](https://github.com/pocket-id/pocket-id/issues/672)) ([dcd1ae9](https://github.com/pocket-id/pocket-id/commit/dcd1ae96e048115be34b0cce275054e990462ebf))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* double double full stops for certain error messages ([d070b9a](https://github.com/pocket-id/pocket-id/commit/d070b9a778d7d1a51f2fa62d003f2331a96d6c91))
|
||||||
|
* error page flickering after sign out ([1a77bd9](https://github.com/pocket-id/pocket-id/commit/1a77bd9914ea01e445ff3d6e116c9ed3bcfbf153))
|
||||||
|
* improve accent color picker disabled state ([d976bf5](https://github.com/pocket-id/pocket-id/commit/d976bf5965eda10e3ecb71821c23e93e5d712a02))
|
||||||
|
* less noisy logging for certain GET requests ([#681](https://github.com/pocket-id/pocket-id/issues/681)) ([043f82a](https://github.com/pocket-id/pocket-id/commit/043f82ad794eb64a5550d8b80703114a055701d9))
|
||||||
|
* margin of user sign up description ([052ac00](https://github.com/pocket-id/pocket-id/commit/052ac008c3a8c910d1ce79ee99b2b2f75e4090f4))
|
||||||
|
* remove duplicate request logging ([#678](https://github.com/pocket-id/pocket-id/issues/678)) ([988c425](https://github.com/pocket-id/pocket-id/commit/988c425150556b32cff1d341a21fcc9c69d9aaf8))
|
||||||
|
* users can't be updated by admin if self account editing is disabled ([29cb551](https://github.com/pocket-id/pocket-id/commit/29cb5513a03d1a9571969c8a42deec9b2bdee037))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v1.4.0...v) (2025-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* app not starting if UI config is disabled and Postgres is used ([7d36bda](https://github.com/pocket-id/pocket-id/commit/7d36bda769e25497dec6b76206a4f7e151b0bd72))
|
||||||
|
|
||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v1.3.1...v) (2025-06-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* allow setting unix socket mode ([#661](https://github.com/pocket-id/pocket-id/issues/661)) ([7677a3d](https://github.com/pocket-id/pocket-id/commit/7677a3de2c923c11a58bc8c4d1b2121d403a1504))
|
||||||
|
* auto-focus on the login buttons ([#647](https://github.com/pocket-id/pocket-id/issues/647)) ([d679530](https://github.com/pocket-id/pocket-id/commit/d6795300b158b85dd9feadd561b6ecd891f5db0d))
|
||||||
|
* configurable local ipv6 ranges for audit log ([#657](https://github.com/pocket-id/pocket-id/issues/657)) ([d548523](https://github.com/pocket-id/pocket-id/commit/d5485238b8fd4cc566af00eae2b17d69a119f991))
|
||||||
|
* location filter for global audit log ([#662](https://github.com/pocket-id/pocket-id/issues/662)) ([ac5a121](https://github.com/pocket-id/pocket-id/commit/ac5a121f664b8127d0faf30c0f93432f30e7f33a))
|
||||||
|
* ui accent colors ([#643](https://github.com/pocket-id/pocket-id/issues/643)) ([883877a](https://github.com/pocket-id/pocket-id/commit/883877adec6fc3e65bd5a705499449959b894fb5))
|
||||||
|
* use icon instead of text on application image update hover state ([215531d](https://github.com/pocket-id/pocket-id/commit/215531d65c6683609b0b4a5505fdb72696fdb93e))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow images with uppercase file extension ([1bcb50e](https://github.com/pocket-id/pocket-id/commit/1bcb50edc335886dd722a4c69960c48cc3cd1687))
|
||||||
|
* center oidc client images if they are smaller than the box ([946c534](https://github.com/pocket-id/pocket-id/commit/946c534b0877a074a6b658060f9af27e4061397c))
|
||||||
|
* explicitly cache images to prevent unexpected behavior ([2e5d268](https://github.com/pocket-id/pocket-id/commit/2e5d2687982186c12e530492292d49895cb6043a))
|
||||||
|
* reduce duration of animations on login and signin page ([#648](https://github.com/pocket-id/pocket-id/issues/648)) ([d770448](https://github.com/pocket-id/pocket-id/commit/d77044882d5a41da22df1c0099c1eb1f20bcbc5b))
|
||||||
|
* use inline style for dynamic background image URL instead of Tailwind class ([bef77ac](https://github.com/pocket-id/pocket-id/commit/bef77ac8dca2b98b6732677aaafbc28f79d00487))
|
||||||
## [](https://github.com/pocket-id/pocket-id/compare/v1.3.0...v) (2025-06-09)
|
## [](https://github.com/pocket-id/pocket-id/compare/v1.3.0...v) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This Dockerfile embeds a pre-built binary for the given Linux architecture
|
# This Dockerfile embeds a pre-built binary for the given Linux architecture
|
||||||
# Binaries must be built using ./scripts/development/build-binaries.sh first
|
# Binaries must be built using ""./scripts/development/build-binaries.sh --docker-only"
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|
||||||
|
|||||||
12
backend/.air.toml
Normal file
12
backend/.air.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = "."
|
||||||
|
tmp_dir = ".bin"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
bin = "./.bin/pocket-id"
|
||||||
|
cmd = "CGO_ENABLED=0 go build -o ./.bin/pocket-id ./cmd"
|
||||||
|
exclude_dir = ["resources", ".bin", "data"]
|
||||||
|
exclude_regex = [".*_test\\.go"]
|
||||||
|
stop_on_error = true
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = true
|
||||||
@@ -7,6 +7,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/frontend"
|
"github.com/pocket-id/pocket-id/backend/frontend"
|
||||||
@@ -45,8 +48,26 @@ func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
|
|||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := gin.Default()
|
// do not log these URLs
|
||||||
r.Use(gin.Logger())
|
loggerSkipPathsPrefix := []string{
|
||||||
|
"GET /application-configuration/logo",
|
||||||
|
"GET /application-configuration/background-image",
|
||||||
|
"GET /application-configuration/favicon",
|
||||||
|
"GET /_app",
|
||||||
|
"GET /fonts",
|
||||||
|
"GET /healthz",
|
||||||
|
"HEAD /healthz",
|
||||||
|
}
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{Skip: func(c *gin.Context) bool {
|
||||||
|
for _, prefix := range loggerSkipPathsPrefix {
|
||||||
|
if strings.HasPrefix(c.Request.Method+" "+c.Request.URL.String(), prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}}))
|
||||||
|
|
||||||
if !common.EnvConfig.TrustProxy {
|
if !common.EnvConfig.TrustProxy {
|
||||||
_ = r.SetTrustedProxies(nil)
|
_ = r.SetTrustedProxies(nil)
|
||||||
@@ -119,6 +140,18 @@ func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
|
|||||||
return nil, fmt.Errorf("failed to create %s listener: %w", network, err)
|
return nil, fmt.Errorf("failed to create %s listener: %w", network, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the socket mode if using a Unix socket
|
||||||
|
if network == "unix" && common.EnvConfig.UnixSocketMode != "" {
|
||||||
|
mode, err := strconv.ParseUint(common.EnvConfig.UnixSocketMode, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chmod(addr, os.FileMode(mode)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Service runner function
|
// Service runner function
|
||||||
runFn := func(ctx context.Context) error {
|
runFn := func(ctx context.Context) error {
|
||||||
log.Printf("Server listening on %s", addr)
|
log.Printf("Server listening on %s", addr)
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ type EnvConfigSchema struct {
|
|||||||
Port string `env:"PORT"`
|
Port string `env:"PORT"`
|
||||||
Host string `env:"HOST"`
|
Host string `env:"HOST"`
|
||||||
UnixSocket string `env:"UNIX_SOCKET"`
|
UnixSocket string `env:"UNIX_SOCKET"`
|
||||||
|
UnixSocketMode string `env:"UNIX_SOCKET_MODE"`
|
||||||
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
|
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
|
||||||
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
|
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
|
||||||
GeoLiteDBUrl string `env:"GEOLITE_DB_URL"`
|
GeoLiteDBUrl string `env:"GEOLITE_DB_URL"`
|
||||||
|
LocalIPv6Ranges string `env:"LOCAL_IPV6_RANGES"`
|
||||||
UiConfigDisabled bool `env:"UI_CONFIG_DISABLED"`
|
UiConfigDisabled bool `env:"UI_CONFIG_DISABLED"`
|
||||||
MetricsEnabled bool `env:"METRICS_ENABLED"`
|
MetricsEnabled bool `env:"METRICS_ENABLED"`
|
||||||
TracingEnabled bool `env:"TRACING_ENABLED"`
|
TracingEnabled bool `env:"TRACING_ENABLED"`
|
||||||
@@ -53,9 +55,11 @@ var EnvConfig = &EnvConfigSchema{
|
|||||||
Port: "1411",
|
Port: "1411",
|
||||||
Host: "0.0.0.0",
|
Host: "0.0.0.0",
|
||||||
UnixSocket: "",
|
UnixSocket: "",
|
||||||
|
UnixSocketMode: "",
|
||||||
MaxMindLicenseKey: "",
|
MaxMindLicenseKey: "",
|
||||||
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
|
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
|
||||||
GeoLiteDBUrl: MaxMindGeoLiteCityUrl,
|
GeoLiteDBUrl: MaxMindGeoLiteCityUrl,
|
||||||
|
LocalIPv6Ranges: "",
|
||||||
UiConfigDisabled: false,
|
UiConfigDisabled: false,
|
||||||
MetricsEnabled: false,
|
MetricsEnabled: false,
|
||||||
TracingEnabled: false,
|
TracingEnabled: false,
|
||||||
|
|||||||
@@ -349,3 +349,13 @@ func (e *OidcAuthorizationPendingError) Error() string {
|
|||||||
func (e *OidcAuthorizationPendingError) HttpStatusCode() int {
|
func (e *OidcAuthorizationPendingError) HttpStatusCode() int {
|
||||||
return http.StatusBadRequest
|
return http.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpenSignupDisabledError struct{}
|
||||||
|
|
||||||
|
func (e *OpenSignupDisabledError) Error() string {
|
||||||
|
return "Open user signup is not enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *OpenSignupDisabledError) HttpStatusCode() int {
|
||||||
|
return http.StatusForbidden
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
@@ -247,6 +248,8 @@ func (acc *AppConfigController) getImage(c *gin.Context, name string, imageType
|
|||||||
mimeType := utils.GetImageMimeType(imageType)
|
mimeType := utils.GetImageMimeType(imageType)
|
||||||
|
|
||||||
c.Header("Content-Type", mimeType)
|
c.Header("Content-Type", mimeType)
|
||||||
|
|
||||||
|
utils.SetCacheControlHeader(c, 15*time.Minute, 24*time.Hour)
|
||||||
c.File(imagePath)
|
c.File(imagePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ func (alc *AuditLogController) listAuditLogsForUserHandler(c *gin.Context) {
|
|||||||
// @Param filters[userId] query string false "Filter by user ID"
|
// @Param filters[userId] query string false "Filter by user ID"
|
||||||
// @Param filters[event] query string false "Filter by event type"
|
// @Param filters[event] query string false "Filter by event type"
|
||||||
// @Param filters[clientName] query string false "Filter by client name"
|
// @Param filters[clientName] query string false "Filter by client name"
|
||||||
|
// @Param filters[location] query string false "Filter by location type (external or internal)"
|
||||||
// @Success 200 {object} dto.Paginated[dto.AuditLogDto]
|
// @Success 200 {object} dto.Paginated[dto.AuditLogDto]
|
||||||
// @Router /api/audit-logs/all [get]
|
// @Router /api/audit-logs/all [get]
|
||||||
func (alc *AuditLogController) listAllAuditLogsHandler(c *gin.Context) {
|
func (alc *AuditLogController) listAllAuditLogsHandler(c *gin.Context) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func (tc *TestController) resetAndSeedHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipLdap := c.Query("skip-ldap") == "true"
|
skipLdap := c.Query("skip-ldap") == "true"
|
||||||
|
skipSeed := c.Query("skip-seed") == "true"
|
||||||
|
|
||||||
if err := tc.TestService.ResetDatabase(); err != nil {
|
if err := tc.TestService.ResetDatabase(); err != nil {
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
@@ -44,9 +45,11 @@ func (tc *TestController) resetAndSeedHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tc.TestService.SeedDatabase(baseURL); err != nil {
|
if !skipSeed {
|
||||||
_ = c.Error(err)
|
if err := tc.TestService.SeedDatabase(baseURL); err != nil {
|
||||||
return
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tc.TestService.ResetAppConfig(c.Request.Context()); err != nil {
|
if err := tc.TestService.ResetAppConfig(c.Request.Context()); err != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@@ -545,6 +546,8 @@ func (oc *OidcController) getClientLogoHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils.SetCacheControlHeader(c, 15*time.Minute, 12*time.Hour)
|
||||||
|
|
||||||
c.Header("Content-Type", mimeType)
|
c.Header("Content-Type", mimeType)
|
||||||
c.File(imagePath)
|
c.File(imagePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,17 @@ func NewUserController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi
|
|||||||
group.POST("/users/:id/one-time-access-token", authMiddleware.Add(), uc.createAdminOneTimeAccessTokenHandler)
|
group.POST("/users/:id/one-time-access-token", authMiddleware.Add(), uc.createAdminOneTimeAccessTokenHandler)
|
||||||
group.POST("/users/:id/one-time-access-email", authMiddleware.Add(), uc.RequestOneTimeAccessEmailAsAdminHandler)
|
group.POST("/users/:id/one-time-access-email", authMiddleware.Add(), uc.RequestOneTimeAccessEmailAsAdminHandler)
|
||||||
group.POST("/one-time-access-token/:token", rateLimitMiddleware.Add(rate.Every(10*time.Second), 5), uc.exchangeOneTimeAccessTokenHandler)
|
group.POST("/one-time-access-token/:token", rateLimitMiddleware.Add(rate.Every(10*time.Second), 5), uc.exchangeOneTimeAccessTokenHandler)
|
||||||
group.POST("/one-time-access-token/setup", uc.getSetupAccessTokenHandler)
|
|
||||||
group.POST("/one-time-access-email", rateLimitMiddleware.Add(rate.Every(10*time.Minute), 3), uc.RequestOneTimeAccessEmailAsUnauthenticatedUserHandler)
|
group.POST("/one-time-access-email", rateLimitMiddleware.Add(rate.Every(10*time.Minute), 3), uc.RequestOneTimeAccessEmailAsUnauthenticatedUserHandler)
|
||||||
|
|
||||||
group.DELETE("/users/:id/profile-picture", authMiddleware.Add(), uc.resetUserProfilePictureHandler)
|
group.DELETE("/users/:id/profile-picture", authMiddleware.Add(), uc.resetUserProfilePictureHandler)
|
||||||
group.DELETE("/users/me/profile-picture", authMiddleware.WithAdminNotRequired().Add(), uc.resetCurrentUserProfilePictureHandler)
|
group.DELETE("/users/me/profile-picture", authMiddleware.WithAdminNotRequired().Add(), uc.resetCurrentUserProfilePictureHandler)
|
||||||
|
|
||||||
|
group.POST("/signup-tokens", authMiddleware.Add(), uc.createSignupTokenHandler)
|
||||||
|
group.GET("/signup-tokens", authMiddleware.Add(), uc.listSignupTokensHandler)
|
||||||
|
group.DELETE("/signup-tokens/:id", authMiddleware.Add(), uc.deleteSignupTokenHandler)
|
||||||
|
group.POST("/signup", rateLimitMiddleware.Add(rate.Every(1*time.Minute), 10), uc.signupHandler)
|
||||||
|
group.POST("/signup/setup", uc.signUpInitialAdmin)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserController struct {
|
type UserController struct {
|
||||||
@@ -250,10 +256,7 @@ func (uc *UserController) getUserProfilePictureHandler(c *gin.Context) {
|
|||||||
defer picture.Close()
|
defer picture.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := c.GetQuery("skipCache")
|
utils.SetCacheControlHeader(c, 15*time.Minute, 1*time.Hour)
|
||||||
if !ok {
|
|
||||||
c.Header("Cache-Control", "public, max-age=900")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
||||||
}
|
}
|
||||||
@@ -443,14 +446,23 @@ func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, userDto)
|
c.JSON(http.StatusOK, userDto)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSetupAccessTokenHandler godoc
|
// signUpInitialAdmin godoc
|
||||||
// @Summary Setup initial admin
|
// @Summary Sign up initial admin user
|
||||||
// @Description Generate setup access token for initial admin user configuration
|
// @Description Sign up and generate setup access token for initial admin user
|
||||||
// @Tags Users
|
// @Tags Users
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body dto.SignUpDto true "User information"
|
||||||
// @Success 200 {object} dto.UserDto
|
// @Success 200 {object} dto.UserDto
|
||||||
// @Router /api/one-time-access-token/setup [post]
|
// @Router /api/signup/setup [post]
|
||||||
func (uc *UserController) getSetupAccessTokenHandler(c *gin.Context) {
|
func (uc *UserController) signUpInitialAdmin(c *gin.Context) {
|
||||||
user, token, err := uc.userService.SetupInitialAdmin(c.Request.Context())
|
var input dto.SignUpDto
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, token, err := uc.userService.SignUpInitialAdmin(c.Request.Context(), input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
return
|
return
|
||||||
@@ -498,6 +510,128 @@ func (uc *UserController) updateUserGroups(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, userDto)
|
c.JSON(http.StatusOK, userDto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createSignupTokenHandler godoc
|
||||||
|
// @Summary Create signup token
|
||||||
|
// @Description Create a new signup token that allows user registration
|
||||||
|
// @Tags Users
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param token body dto.SignupTokenCreateDto true "Signup token information"
|
||||||
|
// @Success 201 {object} dto.SignupTokenDto
|
||||||
|
// @Router /api/signup-tokens [post]
|
||||||
|
func (uc *UserController) createSignupTokenHandler(c *gin.Context) {
|
||||||
|
var input dto.SignupTokenCreateDto
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signupToken, err := uc.userService.CreateSignupToken(c.Request.Context(), input.ExpiresAt, input.UsageLimit)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenDto dto.SignupTokenDto
|
||||||
|
if err := dto.MapStruct(signupToken, &tokenDto); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, tokenDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// listSignupTokensHandler godoc
|
||||||
|
// @Summary List signup tokens
|
||||||
|
// @Description Get a paginated list of signup tokens
|
||||||
|
// @Tags Users
|
||||||
|
// @Param pagination[page] query int false "Page number for pagination" default(1)
|
||||||
|
// @Param pagination[limit] query int false "Number of items per page" default(20)
|
||||||
|
// @Param sort[column] query string false "Column to sort by"
|
||||||
|
// @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc")
|
||||||
|
// @Success 200 {object} dto.Paginated[dto.SignupTokenDto]
|
||||||
|
// @Router /api/signup-tokens [get]
|
||||||
|
func (uc *UserController) listSignupTokensHandler(c *gin.Context) {
|
||||||
|
var sortedPaginationRequest utils.SortedPaginationRequest
|
||||||
|
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, pagination, err := uc.userService.ListSignupTokens(c.Request.Context(), sortedPaginationRequest)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokensDto []dto.SignupTokenDto
|
||||||
|
if err := dto.MapStructList(tokens, &tokensDto); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, dto.Paginated[dto.SignupTokenDto]{
|
||||||
|
Data: tokensDto,
|
||||||
|
Pagination: pagination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSignupTokenHandler godoc
|
||||||
|
// @Summary Delete signup token
|
||||||
|
// @Description Delete a signup token by ID
|
||||||
|
// @Tags Users
|
||||||
|
// @Param id path string true "Token ID"
|
||||||
|
// @Success 204 "No Content"
|
||||||
|
// @Router /api/signup-tokens/{id} [delete]
|
||||||
|
func (uc *UserController) deleteSignupTokenHandler(c *gin.Context) {
|
||||||
|
tokenID := c.Param("id")
|
||||||
|
|
||||||
|
err := uc.userService.DeleteSignupToken(c.Request.Context(), tokenID)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signupWithTokenHandler godoc
|
||||||
|
// @Summary Sign up
|
||||||
|
// @Description Create a new user account
|
||||||
|
// @Tags Users
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param user body dto.SignUpDto true "User information"
|
||||||
|
// @Success 201 {object} dto.SignUpDto
|
||||||
|
// @Router /api/signup [post]
|
||||||
|
func (uc *UserController) signupHandler(c *gin.Context) {
|
||||||
|
var input dto.SignUpDto
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddress := c.ClientIP()
|
||||||
|
userAgent := c.GetHeader("User-Agent")
|
||||||
|
|
||||||
|
user, accessToken, err := uc.userService.SignUp(c.Request.Context(), input, ipAddress, userAgent)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAge := int(uc.appConfigService.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
|
||||||
|
cookie.AddAccessTokenCookie(c, maxAge, accessToken)
|
||||||
|
|
||||||
|
var userDto dto.UserDto
|
||||||
|
if err := dto.MapStruct(user, &userDto); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, userDto)
|
||||||
|
}
|
||||||
|
|
||||||
// updateUser is an internal helper method, not exposed as an API endpoint
|
// updateUser is an internal helper method, not exposed as an API endpoint
|
||||||
func (uc *UserController) updateUser(c *gin.Context, updateOwnUser bool) {
|
func (uc *UserController) updateUser(c *gin.Context, updateOwnUser bool) {
|
||||||
var input dto.UserCreateDto
|
var input dto.UserCreateDto
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type AppConfigUpdateDto struct {
|
|||||||
EmailsVerified string `json:"emailsVerified" binding:"required"`
|
EmailsVerified string `json:"emailsVerified" binding:"required"`
|
||||||
DisableAnimations string `json:"disableAnimations" binding:"required"`
|
DisableAnimations string `json:"disableAnimations" binding:"required"`
|
||||||
AllowOwnAccountEdit string `json:"allowOwnAccountEdit" binding:"required"`
|
AllowOwnAccountEdit string `json:"allowOwnAccountEdit" binding:"required"`
|
||||||
|
AllowUserSignups string `json:"allowUserSignups" binding:"required,oneof=disabled withToken open"`
|
||||||
|
AccentColor string `json:"accentColor"`
|
||||||
SmtpHost string `json:"smtpHost"`
|
SmtpHost string `json:"smtpHost"`
|
||||||
SmtpPort string `json:"smtpPort"`
|
SmtpPort string `json:"smtpPort"`
|
||||||
SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"`
|
SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"`
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ type AuditLogFilterDto struct {
|
|||||||
UserID string `form:"filters[userId]"`
|
UserID string `form:"filters[userId]"`
|
||||||
Event string `form:"filters[event]"`
|
Event string `form:"filters[event]"`
|
||||||
ClientName string `form:"filters[clientName]"`
|
ClientName string `form:"filters[clientName]"`
|
||||||
|
Location string `form:"filters[location]"`
|
||||||
}
|
}
|
||||||
|
|||||||
21
backend/internal/dto/signup_token_dto.go
Normal file
21
backend/internal/dto/signup_token_dto.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SignupTokenCreateDto struct {
|
||||||
|
ExpiresAt time.Time `json:"expiresAt" binding:"required"`
|
||||||
|
UsageLimit int `json:"usageLimit" binding:"required,min=1,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignupTokenDto struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
ExpiresAt datatype.DateTime `json:"expiresAt"`
|
||||||
|
UsageLimit int `json:"usageLimit"`
|
||||||
|
UsageCount int `json:"usageCount"`
|
||||||
|
CreatedAt datatype.DateTime `json:"createdAt"`
|
||||||
|
}
|
||||||
@@ -44,3 +44,11 @@ type OneTimeAccessEmailAsAdminDto struct {
|
|||||||
type UserUpdateUserGroupDto struct {
|
type UserUpdateUserGroupDto struct {
|
||||||
UserGroupIds []string `json:"userGroupIds" binding:"required"`
|
UserGroupIds []string `json:"userGroupIds" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SignUpDto struct {
|
||||||
|
Username string `json:"username" binding:"required,username,min=2,max=50"`
|
||||||
|
Email string `json:"email" binding:"required,email"`
|
||||||
|
FirstName string `json:"firstName" binding:"required,min=1,max=50"`
|
||||||
|
LastName string `json:"lastName" binding:"max=50"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func (s *Scheduler) RegisterDbCleanupJobs(ctx context.Context, db *gorm.DB) erro
|
|||||||
return errors.Join(
|
return errors.Join(
|
||||||
s.registerJob(ctx, "ClearWebauthnSessions", def, jobs.clearWebauthnSessions, true),
|
s.registerJob(ctx, "ClearWebauthnSessions", def, jobs.clearWebauthnSessions, true),
|
||||||
s.registerJob(ctx, "ClearOneTimeAccessTokens", def, jobs.clearOneTimeAccessTokens, true),
|
s.registerJob(ctx, "ClearOneTimeAccessTokens", def, jobs.clearOneTimeAccessTokens, true),
|
||||||
|
s.registerJob(ctx, "ClearSignupTokens", def, jobs.clearSignupTokens, true),
|
||||||
s.registerJob(ctx, "ClearOidcAuthorizationCodes", def, jobs.clearOidcAuthorizationCodes, true),
|
s.registerJob(ctx, "ClearOidcAuthorizationCodes", def, jobs.clearOidcAuthorizationCodes, true),
|
||||||
s.registerJob(ctx, "ClearOidcRefreshTokens", def, jobs.clearOidcRefreshTokens, true),
|
s.registerJob(ctx, "ClearOidcRefreshTokens", def, jobs.clearOidcRefreshTokens, true),
|
||||||
s.registerJob(ctx, "ClearAuditLogs", def, jobs.clearAuditLogs, true),
|
s.registerJob(ctx, "ClearAuditLogs", def, jobs.clearAuditLogs, true),
|
||||||
@@ -60,6 +61,21 @@ func (j *DbCleanupJobs) clearOneTimeAccessTokens(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearSignupTokens deletes signup tokens that have expired
|
||||||
|
func (j *DbCleanupJobs) clearSignupTokens(ctx context.Context) error {
|
||||||
|
// Delete tokens that are expired OR have reached their usage limit
|
||||||
|
st := j.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
Delete(&model.SignupToken{}, "expires_at < ?", datatype.DateTime(time.Now()))
|
||||||
|
if st.Error != nil {
|
||||||
|
return fmt.Errorf("failed to clean expired tokens: %w", st.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "Cleaned expired tokens", slog.Int64("count", st.RowsAffected))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ClearOidcAuthorizationCodes deletes OIDC authorization codes that have expired
|
// ClearOidcAuthorizationCodes deletes OIDC authorization codes that have expired
|
||||||
func (j *DbCleanupJobs) clearOidcAuthorizationCodes(ctx context.Context) error {
|
func (j *DbCleanupJobs) clearOidcAuthorizationCodes(ctx context.Context) error {
|
||||||
st := j.db.
|
st := j.db.
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppConfigVariable struct {
|
type AppConfigVariable struct {
|
||||||
@@ -35,8 +37,10 @@ type AppConfig struct {
|
|||||||
AppName AppConfigVariable `key:"appName,public"` // Public
|
AppName AppConfigVariable `key:"appName,public"` // Public
|
||||||
SessionDuration AppConfigVariable `key:"sessionDuration"`
|
SessionDuration AppConfigVariable `key:"sessionDuration"`
|
||||||
EmailsVerified AppConfigVariable `key:"emailsVerified"`
|
EmailsVerified AppConfigVariable `key:"emailsVerified"`
|
||||||
|
AccentColor AppConfigVariable `key:"accentColor,public"` // Public
|
||||||
DisableAnimations AppConfigVariable `key:"disableAnimations,public"` // Public
|
DisableAnimations AppConfigVariable `key:"disableAnimations,public"` // Public
|
||||||
AllowOwnAccountEdit AppConfigVariable `key:"allowOwnAccountEdit,public"` // Public
|
AllowOwnAccountEdit AppConfigVariable `key:"allowOwnAccountEdit,public"` // Public
|
||||||
|
AllowUserSignups AppConfigVariable `key:"allowUserSignups,public"` // Public
|
||||||
// Internal
|
// Internal
|
||||||
BackgroundImageType AppConfigVariable `key:"backgroundImageType,internal"` // Internal
|
BackgroundImageType AppConfigVariable `key:"backgroundImageType,internal"` // Internal
|
||||||
LogoLightImageType AppConfigVariable `key:"logoLightImageType,internal"` // Internal
|
LogoLightImageType AppConfigVariable `key:"logoLightImageType,internal"` // Internal
|
||||||
@@ -47,7 +51,7 @@ type AppConfig struct {
|
|||||||
SmtpPort AppConfigVariable `key:"smtpPort"`
|
SmtpPort AppConfigVariable `key:"smtpPort"`
|
||||||
SmtpFrom AppConfigVariable `key:"smtpFrom"`
|
SmtpFrom AppConfigVariable `key:"smtpFrom"`
|
||||||
SmtpUser AppConfigVariable `key:"smtpUser"`
|
SmtpUser AppConfigVariable `key:"smtpUser"`
|
||||||
SmtpPassword AppConfigVariable `key:"smtpPassword"`
|
SmtpPassword AppConfigVariable `key:"smtpPassword,sensitive"`
|
||||||
SmtpTls AppConfigVariable `key:"smtpTls"`
|
SmtpTls AppConfigVariable `key:"smtpTls"`
|
||||||
SmtpSkipCertVerify AppConfigVariable `key:"smtpSkipCertVerify"`
|
SmtpSkipCertVerify AppConfigVariable `key:"smtpSkipCertVerify"`
|
||||||
EmailLoginNotificationEnabled AppConfigVariable `key:"emailLoginNotificationEnabled"`
|
EmailLoginNotificationEnabled AppConfigVariable `key:"emailLoginNotificationEnabled"`
|
||||||
@@ -58,7 +62,7 @@ type AppConfig struct {
|
|||||||
LdapEnabled AppConfigVariable `key:"ldapEnabled,public"` // Public
|
LdapEnabled AppConfigVariable `key:"ldapEnabled,public"` // Public
|
||||||
LdapUrl AppConfigVariable `key:"ldapUrl"`
|
LdapUrl AppConfigVariable `key:"ldapUrl"`
|
||||||
LdapBindDn AppConfigVariable `key:"ldapBindDn"`
|
LdapBindDn AppConfigVariable `key:"ldapBindDn"`
|
||||||
LdapBindPassword AppConfigVariable `key:"ldapBindPassword"`
|
LdapBindPassword AppConfigVariable `key:"ldapBindPassword,sensitive"`
|
||||||
LdapBase AppConfigVariable `key:"ldapBase"`
|
LdapBase AppConfigVariable `key:"ldapBase"`
|
||||||
LdapUserSearchFilter AppConfigVariable `key:"ldapUserSearchFilter"`
|
LdapUserSearchFilter AppConfigVariable `key:"ldapUserSearchFilter"`
|
||||||
LdapUserGroupSearchFilter AppConfigVariable `key:"ldapUserGroupSearchFilter"`
|
LdapUserGroupSearchFilter AppConfigVariable `key:"ldapUserGroupSearchFilter"`
|
||||||
@@ -76,7 +80,7 @@ type AppConfig struct {
|
|||||||
LdapSoftDeleteUsers AppConfigVariable `key:"ldapSoftDeleteUsers"`
|
LdapSoftDeleteUsers AppConfigVariable `key:"ldapSoftDeleteUsers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AppConfig) ToAppConfigVariableSlice(showAll bool) []AppConfigVariable {
|
func (c *AppConfig) ToAppConfigVariableSlice(showAll bool, redactSensitiveValues bool) []AppConfigVariable {
|
||||||
// Use reflection to iterate through all fields
|
// Use reflection to iterate through all fields
|
||||||
cfgValue := reflect.ValueOf(c).Elem()
|
cfgValue := reflect.ValueOf(c).Elem()
|
||||||
cfgType := cfgValue.Type()
|
cfgType := cfgValue.Type()
|
||||||
@@ -96,11 +100,16 @@ func (c *AppConfig) ToAppConfigVariableSlice(showAll bool) []AppConfigVariable {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldValue := cfgValue.Field(i)
|
value := cfgValue.Field(i).FieldByName("Value").String()
|
||||||
|
|
||||||
|
// Redact sensitive values if the value isn't empty, the UI config is disabled, and redactSensitiveValues is true
|
||||||
|
if value != "" && common.EnvConfig.UiConfigDisabled && redactSensitiveValues && attrs == "sensitive" {
|
||||||
|
value = "XXXXXXXXXX"
|
||||||
|
}
|
||||||
|
|
||||||
appConfigVariable := AppConfigVariable{
|
appConfigVariable := AppConfigVariable{
|
||||||
Key: key,
|
Key: key,
|
||||||
Value: fieldValue.FieldByName("Value").String(),
|
Value: value,
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, appConfigVariable)
|
res = append(res, appConfigVariable)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type AuditLogEvent string //nolint:recvcheck
|
|||||||
const (
|
const (
|
||||||
AuditLogEventSignIn AuditLogEvent = "SIGN_IN"
|
AuditLogEventSignIn AuditLogEvent = "SIGN_IN"
|
||||||
AuditLogEventOneTimeAccessTokenSignIn AuditLogEvent = "TOKEN_SIGN_IN"
|
AuditLogEventOneTimeAccessTokenSignIn AuditLogEvent = "TOKEN_SIGN_IN"
|
||||||
|
AuditLogEventAccountCreated AuditLogEvent = "ACCOUNT_CREATED"
|
||||||
AuditLogEventClientAuthorization AuditLogEvent = "CLIENT_AUTHORIZATION"
|
AuditLogEventClientAuthorization AuditLogEvent = "CLIENT_AUTHORIZATION"
|
||||||
AuditLogEventNewClientAuthorization AuditLogEvent = "NEW_CLIENT_AUTHORIZATION"
|
AuditLogEventNewClientAuthorization AuditLogEvent = "NEW_CLIENT_AUTHORIZATION"
|
||||||
AuditLogEventDeviceCodeAuthorization AuditLogEvent = "DEVICE_CODE_AUTHORIZATION"
|
AuditLogEventDeviceCodeAuthorization AuditLogEvent = "DEVICE_CODE_AUTHORIZATION"
|
||||||
|
|||||||
28
backend/internal/model/signup_token.go
Normal file
28
backend/internal/model/signup_token.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SignupToken struct {
|
||||||
|
Base
|
||||||
|
|
||||||
|
Token string `json:"token"`
|
||||||
|
ExpiresAt datatype.DateTime `json:"expiresAt" sortable:"true"`
|
||||||
|
UsageLimit int `json:"usageLimit" sortable:"true"`
|
||||||
|
UsageCount int `json:"usageCount" sortable:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *SignupToken) IsExpired() bool {
|
||||||
|
return time.Time(st.ExpiresAt).Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *SignupToken) IsUsageLimitReached() bool {
|
||||||
|
return st.UsageCount >= st.UsageLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *SignupToken) IsValid() bool {
|
||||||
|
return !st.IsExpired() && !st.IsUsageLimitReached()
|
||||||
|
}
|
||||||
@@ -68,6 +68,8 @@ func (s *AppConfigService) getDefaultDbConfig() *model.AppConfig {
|
|||||||
EmailsVerified: model.AppConfigVariable{Value: "false"},
|
EmailsVerified: model.AppConfigVariable{Value: "false"},
|
||||||
DisableAnimations: model.AppConfigVariable{Value: "false"},
|
DisableAnimations: model.AppConfigVariable{Value: "false"},
|
||||||
AllowOwnAccountEdit: model.AppConfigVariable{Value: "true"},
|
AllowOwnAccountEdit: model.AppConfigVariable{Value: "true"},
|
||||||
|
AllowUserSignups: model.AppConfigVariable{Value: "disabled"},
|
||||||
|
AccentColor: model.AppConfigVariable{Value: "default"},
|
||||||
// Internal
|
// Internal
|
||||||
BackgroundImageType: model.AppConfigVariable{Value: "jpg"},
|
BackgroundImageType: model.AppConfigVariable{Value: "jpg"},
|
||||||
LogoLightImageType: model.AppConfigVariable{Value: "svg"},
|
LogoLightImageType: model.AppConfigVariable{Value: "svg"},
|
||||||
@@ -232,7 +234,7 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon
|
|||||||
s.dbConfig.Store(cfg)
|
s.dbConfig.Store(cfg)
|
||||||
|
|
||||||
// Return the updated config
|
// Return the updated config
|
||||||
res := cfg.ToAppConfigVariableSlice(true)
|
res := cfg.ToAppConfigVariableSlice(true, false)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,11 +319,11 @@ func (s *AppConfigService) UpdateAppConfigValues(ctx context.Context, keysAndVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppConfigService) ListAppConfig(showAll bool) []model.AppConfigVariable {
|
func (s *AppConfigService) ListAppConfig(showAll bool) []model.AppConfigVariable {
|
||||||
return s.GetDbConfig().ToAppConfigVariableSlice(showAll)
|
return s.GetDbConfig().ToAppConfigVariableSlice(showAll, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppConfigService) UpdateImage(ctx context.Context, uploadedFile *multipart.FileHeader, imageName string, oldImageType string) (err error) {
|
func (s *AppConfigService) UpdateImage(ctx context.Context, uploadedFile *multipart.FileHeader, imageName string, oldImageType string) (err error) {
|
||||||
fileType := utils.GetFileExtension(uploadedFile.Filename)
|
fileType := strings.ToLower(utils.GetFileExtension(uploadedFile.Filename))
|
||||||
mimeType := utils.GetImageMimeType(fileType)
|
mimeType := utils.GetImageMimeType(fileType)
|
||||||
if mimeType == "" {
|
if mimeType == "" {
|
||||||
return &common.FileTypeNotSupportedError{}
|
return &common.FileTypeNotSupportedError{}
|
||||||
@@ -368,7 +370,7 @@ func (s *AppConfigService) LoadDbConfig(ctx context.Context) (err error) {
|
|||||||
func (s *AppConfigService) loadDbConfigInternal(ctx context.Context, tx *gorm.DB) (*model.AppConfig, error) {
|
func (s *AppConfigService) loadDbConfigInternal(ctx context.Context, tx *gorm.DB) (*model.AppConfig, error) {
|
||||||
// If the UI config is disabled, only load from the env
|
// If the UI config is disabled, only load from the env
|
||||||
if common.EnvConfig.UiConfigDisabled {
|
if common.EnvConfig.UiConfigDisabled {
|
||||||
dest, err := s.loadDbConfigFromEnv(ctx, s.db)
|
dest, err := s.loadDbConfigFromEnv(ctx, tx)
|
||||||
return dest, err
|
return dest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ func (s *AuditLogService) ListAllAuditLogs(ctx context.Context, sortedPagination
|
|||||||
return nil, utils.PaginationResponse{}, fmt.Errorf("unsupported database dialect: %s", dialect)
|
return nil, utils.PaginationResponse{}, fmt.Errorf("unsupported database dialect: %s", dialect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if filters.Location != "" {
|
||||||
|
switch filters.Location {
|
||||||
|
case "external":
|
||||||
|
query = query.Where("country != 'Internal Network'")
|
||||||
|
case "internal":
|
||||||
|
query = query.Where("country = 'Internal Network'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs)
|
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -310,6 +310,50 @@ func (s *TestService) SeedDatabase(baseURL string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signupTokens := []model.SignupToken{
|
||||||
|
{
|
||||||
|
Base: model.Base{
|
||||||
|
ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||||
|
},
|
||||||
|
Token: "VALID1234567890A",
|
||||||
|
ExpiresAt: datatype.DateTime(time.Now().Add(24 * time.Hour)),
|
||||||
|
UsageLimit: 1,
|
||||||
|
UsageCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Base: model.Base{
|
||||||
|
ID: "b2c3d4e5-f6g7-8901-bcde-f12345678901",
|
||||||
|
},
|
||||||
|
Token: "PARTIAL567890ABC",
|
||||||
|
ExpiresAt: datatype.DateTime(time.Now().Add(7 * 24 * time.Hour)),
|
||||||
|
UsageLimit: 5,
|
||||||
|
UsageCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Base: model.Base{
|
||||||
|
ID: "c3d4e5f6-g7h8-9012-cdef-123456789012",
|
||||||
|
},
|
||||||
|
Token: "EXPIRED34567890B",
|
||||||
|
ExpiresAt: datatype.DateTime(time.Now().Add(-24 * time.Hour)), // Expired
|
||||||
|
UsageLimit: 3,
|
||||||
|
UsageCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Base: model.Base{
|
||||||
|
ID: "d4e5f6g7-h8i9-0123-def0-234567890123",
|
||||||
|
},
|
||||||
|
Token: "FULLYUSED567890C",
|
||||||
|
ExpiresAt: datatype.DateTime(time.Now().Add(24 * time.Hour)),
|
||||||
|
UsageLimit: 1,
|
||||||
|
UsageCount: 1, // Usage limit reached
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, token := range signupTokens {
|
||||||
|
if err := tx.Create(&token).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,9 +23,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GeoLiteService struct {
|
type GeoLiteService struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
disableUpdater bool
|
disableUpdater bool
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
localIPv6Ranges []*net.IPNet
|
||||||
}
|
}
|
||||||
|
|
||||||
var localhostIPNets = []*net.IPNet{
|
var localhostIPNets = []*net.IPNet{
|
||||||
@@ -54,9 +56,66 @@ func NewGeoLiteService(httpClient *http.Client) *GeoLiteService {
|
|||||||
service.disableUpdater = true
|
service.disableUpdater = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize IPv6 local ranges
|
||||||
|
if err := service.initializeIPv6LocalRanges(); err != nil {
|
||||||
|
log.Printf("Warning: Failed to initialize IPv6 local ranges: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initializeIPv6LocalRanges parses the LOCAL_IPV6_RANGES environment variable
|
||||||
|
func (s *GeoLiteService) initializeIPv6LocalRanges() error {
|
||||||
|
rangesEnv := common.EnvConfig.LocalIPv6Ranges
|
||||||
|
if rangesEnv == "" {
|
||||||
|
return nil // No local IPv6 ranges configured
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges := strings.Split(rangesEnv, ",")
|
||||||
|
localRanges := make([]*net.IPNet, 0, len(ranges))
|
||||||
|
|
||||||
|
for _, rangeStr := range ranges {
|
||||||
|
rangeStr = strings.TrimSpace(rangeStr)
|
||||||
|
if rangeStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ipNet, err := net.ParseCIDR(rangeStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid IPv6 range '%s': %w", rangeStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure it's an IPv6 range
|
||||||
|
if ipNet.IP.To4() != nil {
|
||||||
|
return fmt.Errorf("range '%s' is not a valid IPv6 range", rangeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
localRanges = append(localRanges, ipNet)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.localIPv6Ranges = localRanges
|
||||||
|
|
||||||
|
if len(localRanges) > 0 {
|
||||||
|
log.Printf("Initialized %d IPv6 local ranges", len(localRanges))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLocalIPv6 checks if the given IPv6 address is within any of the configured local ranges
|
||||||
|
func (s *GeoLiteService) isLocalIPv6(ip net.IP) bool {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return false // Not an IPv6 address
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, localRange := range s.localIPv6Ranges {
|
||||||
|
if localRange.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *GeoLiteService) DisableUpdater() bool {
|
func (s *GeoLiteService) DisableUpdater() bool {
|
||||||
return s.disableUpdater
|
return s.disableUpdater
|
||||||
}
|
}
|
||||||
@@ -65,6 +124,12 @@ func (s *GeoLiteService) DisableUpdater() bool {
|
|||||||
func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string, err error) {
|
func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string, err error) {
|
||||||
// Check the IP address against known private IP ranges
|
// Check the IP address against known private IP ranges
|
||||||
if ip := net.ParseIP(ipAddress); ip != nil {
|
if ip := net.ParseIP(ipAddress); ip != nil {
|
||||||
|
// Check IPv6 local ranges first
|
||||||
|
if s.isLocalIPv6(ip) {
|
||||||
|
return "Internal Network", "LAN", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check existing IPv4 ranges
|
||||||
for _, ipNet := range tailscaleIPNets {
|
for _, ipNet := range tailscaleIPNets {
|
||||||
if ipNet.Contains(ip) {
|
if ipNet.Contains(ip) {
|
||||||
return "Internal Network", "Tailscale", nil
|
return "Internal Network", "Tailscale", nil
|
||||||
|
|||||||
231
backend/internal/service/geolite_service_test.go
Normal file
231
backend/internal/service/geolite_service_test.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeoLiteService_IPv6LocalRanges(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
localRanges string
|
||||||
|
testIP string
|
||||||
|
expectedCountry string
|
||||||
|
expectedCity string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IPv6 in local range",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56,2001:0db8:abcd:001::/56",
|
||||||
|
testIP: "2001:0db8:abcd:000::1",
|
||||||
|
expectedCountry: "Internal Network",
|
||||||
|
expectedCity: "LAN",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 not in local range",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "2001:0db8:ffff:000::1",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple ranges - second range match",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56,2001:0db8:abcd:001::/56",
|
||||||
|
testIP: "2001:0db8:abcd:001::1",
|
||||||
|
expectedCountry: "Internal Network",
|
||||||
|
expectedCity: "LAN",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty local ranges",
|
||||||
|
localRanges: "",
|
||||||
|
testIP: "2001:0db8:abcd:000::1",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 private address still works",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "192.168.1.1",
|
||||||
|
expectedCountry: "Internal Network",
|
||||||
|
expectedCity: "LAN",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 loopback",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "::1",
|
||||||
|
expectedCountry: "Internal Network",
|
||||||
|
expectedCity: "localhost",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
originalConfig := common.EnvConfig.LocalIPv6Ranges
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = tt.localRanges
|
||||||
|
defer func() {
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = originalConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
service := NewGeoLiteService(&http.Client{})
|
||||||
|
|
||||||
|
country, city, err := service.GetLocationByIP(tt.testIP)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
if err == nil && country != "Internal Network" {
|
||||||
|
t.Errorf("Expected error or internal network classification for external IP")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error for local IP, got: %v", err)
|
||||||
|
}
|
||||||
|
if country != tt.expectedCountry {
|
||||||
|
t.Errorf("Expected country %s, got %s", tt.expectedCountry, country)
|
||||||
|
}
|
||||||
|
if city != tt.expectedCity {
|
||||||
|
t.Errorf("Expected city %s, got %s", tt.expectedCity, city)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeoLiteService_isLocalIPv6(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
localRanges string
|
||||||
|
testIP string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid IPv6 in range",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "2001:0db8:abcd:000::1",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid IPv6 not in range",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "2001:0db8:ffff:000::1",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 address should return false",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "192.168.1.1",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No ranges configured",
|
||||||
|
localRanges: "",
|
||||||
|
testIP: "2001:0db8:abcd:000::1",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Edge of range",
|
||||||
|
localRanges: "2001:0db8:abcd:000::/56",
|
||||||
|
testIP: "2001:0db8:abcd:00ff:ffff:ffff:ffff:ffff",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
originalConfig := common.EnvConfig.LocalIPv6Ranges
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = tt.localRanges
|
||||||
|
defer func() {
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = originalConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
service := NewGeoLiteService(&http.Client{})
|
||||||
|
ip := net.ParseIP(tt.testIP)
|
||||||
|
if ip == nil {
|
||||||
|
t.Fatalf("Invalid test IP: %s", tt.testIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := service.isLocalIPv6(ip)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("Expected %v, got %v for IP %s", tt.expected, result, tt.testIP)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeoLiteService_initializeIPv6LocalRanges(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
envValue string
|
||||||
|
expectError bool
|
||||||
|
expectCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid IPv6 ranges",
|
||||||
|
envValue: "2001:0db8:abcd:000::/56,2001:0db8:abcd:001::/56",
|
||||||
|
expectError: false,
|
||||||
|
expectCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty environment variable",
|
||||||
|
envValue: "",
|
||||||
|
expectError: false,
|
||||||
|
expectCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid CIDR notation",
|
||||||
|
envValue: "2001:0db8:abcd:000::/999",
|
||||||
|
expectError: true,
|
||||||
|
expectCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 range in IPv6 env var",
|
||||||
|
envValue: "192.168.1.0/24",
|
||||||
|
expectError: true,
|
||||||
|
expectCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mixed valid and invalid ranges",
|
||||||
|
envValue: "2001:0db8:abcd:000::/56,invalid-range",
|
||||||
|
expectError: true,
|
||||||
|
expectCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Whitespace handling",
|
||||||
|
envValue: " 2001:0db8:abcd:000::/56 , 2001:0db8:abcd:001::/56 ",
|
||||||
|
expectError: false,
|
||||||
|
expectCount: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
originalConfig := common.EnvConfig.LocalIPv6Ranges
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = tt.envValue
|
||||||
|
defer func() {
|
||||||
|
common.EnvConfig.LocalIPv6Ranges = originalConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
service := &GeoLiteService{
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := service.initializeIPv6LocalRanges()
|
||||||
|
|
||||||
|
if tt.expectError && err == nil {
|
||||||
|
t.Errorf("Expected error but got none")
|
||||||
|
}
|
||||||
|
if !tt.expectError && err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeCount := len(service.localIPv6Ranges)
|
||||||
|
|
||||||
|
if rangeCount != tt.expectCount {
|
||||||
|
t.Errorf("Expected %d ranges, got %d", tt.expectCount, rangeCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -820,7 +820,7 @@ func (s *OidcService) GetClientLogo(ctx context.Context, clientID string) (strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OidcService) UpdateClientLogo(ctx context.Context, clientID string, file *multipart.FileHeader) error {
|
func (s *OidcService) UpdateClientLogo(ctx context.Context, clientID string, file *multipart.FileHeader) error {
|
||||||
fileType := utils.GetFileExtension(file.Filename)
|
fileType := strings.ToLower(utils.GetFileExtension(file.Filename))
|
||||||
if mimeType := utils.GetImageMimeType(fileType); mimeType == "" {
|
if mimeType := utils.GetImageMimeType(fileType); mimeType == "" {
|
||||||
return &common.FileTypeNotSupportedError{}
|
return &common.FileTypeNotSupportedError{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,15 +296,21 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
isLdapUser := user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue()
|
isLdapUser := user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue()
|
||||||
allowOwnAccountEdit := s.appConfigService.GetDbConfig().AllowOwnAccountEdit.IsTrue()
|
allowOwnAccountEdit := s.appConfigService.GetDbConfig().AllowOwnAccountEdit.IsTrue()
|
||||||
|
|
||||||
// For LDAP users or if own account editing is not allowed, only allow updating the locale unless it's an LDAP sync
|
if !isLdapSync && (isLdapUser || (!allowOwnAccountEdit && updateOwnUser)) {
|
||||||
if !isLdapSync && (isLdapUser || (!allowOwnAccountEdit && !updateOwnUser)) {
|
// Restricted update: Only locale can be changed when:
|
||||||
|
// - User is from LDAP, OR
|
||||||
|
// - User is editing their own account but global setting disallows self-editing
|
||||||
|
// (Exception: LDAP sync operations can update everything)
|
||||||
user.Locale = updatedUser.Locale
|
user.Locale = updatedUser.Locale
|
||||||
} else {
|
} else {
|
||||||
|
// Full update: Allow updating all personal fields
|
||||||
user.FirstName = updatedUser.FirstName
|
user.FirstName = updatedUser.FirstName
|
||||||
user.LastName = updatedUser.LastName
|
user.LastName = updatedUser.LastName
|
||||||
user.Email = updatedUser.Email
|
user.Email = updatedUser.Email
|
||||||
user.Username = updatedUser.Username
|
user.Username = updatedUser.Username
|
||||||
user.Locale = updatedUser.Locale
|
user.Locale = updatedUser.Locale
|
||||||
|
|
||||||
|
// Admin-only fields: Only allow updates when not updating own account
|
||||||
if !updateOwnUser {
|
if !updateOwnUser {
|
||||||
user.IsAdmin = updatedUser.IsAdmin
|
user.IsAdmin = updatedUser.IsAdmin
|
||||||
user.Disabled = updatedUser.Disabled
|
user.Disabled = updatedUser.Disabled
|
||||||
@@ -523,7 +529,7 @@ func (s *UserService) UpdateUserGroups(ctx context.Context, id string, userGroup
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) SetupInitialAdmin(ctx context.Context) (model.User, string, error) {
|
func (s *UserService) SignUpInitialAdmin(ctx context.Context, signUpData dto.SignUpDto) (model.User, string, error) {
|
||||||
tx := s.db.Begin()
|
tx := s.db.Begin()
|
||||||
defer func() {
|
defer func() {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
@@ -533,25 +539,19 @@ func (s *UserService) SetupInitialAdmin(ctx context.Context) (model.User, string
|
|||||||
if err := tx.WithContext(ctx).Model(&model.User{}).Count(&userCount).Error; err != nil {
|
if err := tx.WithContext(ctx).Model(&model.User{}).Count(&userCount).Error; err != nil {
|
||||||
return model.User{}, "", err
|
return model.User{}, "", err
|
||||||
}
|
}
|
||||||
if userCount > 1 {
|
if userCount != 0 {
|
||||||
return model.User{}, "", &common.SetupAlreadyCompletedError{}
|
return model.User{}, "", &common.SetupAlreadyCompletedError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := model.User{
|
userToCreate := dto.UserCreateDto{
|
||||||
FirstName: "Admin",
|
FirstName: signUpData.FirstName,
|
||||||
LastName: "Admin",
|
LastName: signUpData.LastName,
|
||||||
Username: "admin",
|
Username: signUpData.Username,
|
||||||
Email: "admin@admin.com",
|
Email: signUpData.Email,
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.WithContext(ctx).Model(&model.User{}).Preload("Credentials").FirstOrCreate(&user).Error; err != nil {
|
user, err := s.createUserInternal(ctx, userToCreate, false, tx)
|
||||||
return model.User{}, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user.Credentials) > 0 {
|
|
||||||
return model.User{}, "", &common.SetupAlreadyCompletedError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := s.jwtService.GenerateAccessToken(user)
|
token, err := s.jwtService.GenerateAccessToken(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -630,6 +630,110 @@ func (s *UserService) disableUserInternal(ctx context.Context, userID string, tx
|
|||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) CreateSignupToken(ctx context.Context, expiresAt time.Time, usageLimit int) (model.SignupToken, error) {
|
||||||
|
return s.createSignupTokenInternal(ctx, expiresAt, usageLimit, s.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) createSignupTokenInternal(ctx context.Context, expiresAt time.Time, usageLimit int, tx *gorm.DB) (model.SignupToken, error) {
|
||||||
|
signupToken, err := NewSignupToken(expiresAt, usageLimit)
|
||||||
|
if err != nil {
|
||||||
|
return model.SignupToken{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.WithContext(ctx).Create(signupToken).Error; err != nil {
|
||||||
|
return model.SignupToken{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return *signupToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) SignUp(ctx context.Context, signupData dto.SignUpDto, ipAddress, userAgent string) (model.User, string, error) {
|
||||||
|
tx := s.db.Begin()
|
||||||
|
defer func() {
|
||||||
|
tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
tokenProvided := signupData.Token != ""
|
||||||
|
|
||||||
|
config := s.appConfigService.GetDbConfig()
|
||||||
|
if config.AllowUserSignups.Value != "open" && !tokenProvided {
|
||||||
|
return model.User{}, "", &common.OpenSignupDisabledError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var signupToken model.SignupToken
|
||||||
|
if tokenProvided {
|
||||||
|
err := tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Where("token = ?", signupData.Token).
|
||||||
|
First(&signupToken).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return model.User{}, "", &common.TokenInvalidOrExpiredError{}
|
||||||
|
}
|
||||||
|
return model.User{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !signupToken.IsValid() {
|
||||||
|
return model.User{}, "", &common.TokenInvalidOrExpiredError{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userToCreate := dto.UserCreateDto{
|
||||||
|
Username: signupData.Username,
|
||||||
|
Email: signupData.Email,
|
||||||
|
FirstName: signupData.FirstName,
|
||||||
|
LastName: signupData.LastName,
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.createUserInternal(ctx, userToCreate, false, tx)
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, err := s.jwtService.GenerateAccessToken(user)
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenProvided {
|
||||||
|
s.auditLogService.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
|
||||||
|
"signupToken": signupToken.Token,
|
||||||
|
}, tx)
|
||||||
|
|
||||||
|
signupToken.UsageCount++
|
||||||
|
|
||||||
|
err = tx.WithContext(ctx).Save(&signupToken).Error
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, "", err
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.auditLogService.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
|
||||||
|
"method": "open_signup",
|
||||||
|
}, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit().Error
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, accessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) ListSignupTokens(ctx context.Context, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.SignupToken, utils.PaginationResponse, error) {
|
||||||
|
var tokens []model.SignupToken
|
||||||
|
query := s.db.WithContext(ctx).Model(&model.SignupToken{})
|
||||||
|
|
||||||
|
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &tokens)
|
||||||
|
return tokens, pagination, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) DeleteSignupToken(ctx context.Context, tokenID string) error {
|
||||||
|
return s.db.WithContext(ctx).Delete(&model.SignupToken{}, "id = ?", tokenID).Error
|
||||||
|
}
|
||||||
|
|
||||||
func NewOneTimeAccessToken(userID string, expiresAt time.Time) (*model.OneTimeAccessToken, error) {
|
func NewOneTimeAccessToken(userID string, expiresAt time.Time) (*model.OneTimeAccessToken, error) {
|
||||||
// If expires at is less than 15 minutes, use a 6-character token instead of 16
|
// If expires at is less than 15 minutes, use a 6-character token instead of 16
|
||||||
tokenLength := 16
|
tokenLength := 16
|
||||||
@@ -650,3 +754,20 @@ func NewOneTimeAccessToken(userID string, expiresAt time.Time) (*model.OneTimeAc
|
|||||||
|
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewSignupToken(expiresAt time.Time, usageLimit int) (*model.SignupToken, error) {
|
||||||
|
// Generate a random token
|
||||||
|
randomString, err := utils.GenerateRandomAlphanumericString(16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &model.SignupToken{
|
||||||
|
Token: randomString,
|
||||||
|
ExpiresAt: datatype.DateTime(expiresAt),
|
||||||
|
UsageLimit: usageLimit,
|
||||||
|
UsageCount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BearerAuth returns the value of the bearer token in the Authorization header if present
|
// BearerAuth returns the value of the bearer token in the Authorization header if present
|
||||||
@@ -16,3 +20,14 @@ func BearerAuth(r *http.Request) (string, bool) {
|
|||||||
|
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheControlHeader sets the Cache-Control header for the response.
|
||||||
|
func SetCacheControlHeader(ctx *gin.Context, maxAge, staleWhileRevalidate time.Duration) {
|
||||||
|
_, ok := ctx.GetQuery("skipCache")
|
||||||
|
if !ok {
|
||||||
|
maxAgeSeconds := strconv.Itoa(int(maxAge.Seconds()))
|
||||||
|
staleWhileRevalidateSeconds := strconv.Itoa(int(staleWhileRevalidate.Seconds()))
|
||||||
|
ctx.Header("Cache-Control", "public, max-age="+maxAgeSeconds+", stale-while-revalidate="+staleWhileRevalidateSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX idx_audit_logs_country;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CREATE INDEX idx_audit_logs_country ON audit_logs(country);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_signup_tokens_expires_at;
|
||||||
|
DROP INDEX IF EXISTS idx_signup_tokens_token;
|
||||||
|
DROP TABLE IF EXISTS signup_tokens;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE signup_tokens (
|
||||||
|
id UUID NOT NULL PRIMARY KEY,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL,
|
||||||
|
usage_limit INTEGER NOT NULL DEFAULT 1,
|
||||||
|
usage_count INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_signup_tokens_token ON signup_tokens(token);
|
||||||
|
CREATE INDEX idx_signup_tokens_expires_at ON signup_tokens(expires_at);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX idx_audit_logs_country;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CREATE INDEX idx_audit_logs_country ON audit_logs(country);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_signup_tokens_expires_at;
|
||||||
|
DROP INDEX IF EXISTS idx_signup_tokens_token;
|
||||||
|
DROP TABLE IF EXISTS signup_tokens;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE signup_tokens (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
token TEXT NOT NULL UNIQUE,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
usage_limit INTEGER NOT NULL DEFAULT 1,
|
||||||
|
usage_count INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_signup_tokens_token ON signup_tokens(token);
|
||||||
|
CREATE INDEX idx_signup_tokens_expires_at ON signup_tokens(expires_at);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
pocket-id:
|
pocket-id:
|
||||||
image: ghcr.io/pocket-id/pocket-id
|
image: ghcr.io/pocket-id/pocket-id:v1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env
|
env_file: .env
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Můj Účet",
|
"my_account": "Můj Účet",
|
||||||
"logout": "Odhlásit se",
|
"logout": "Odhlásit se",
|
||||||
"confirm": "Potvrdit",
|
"confirm": "Potvrdit",
|
||||||
|
"docs": "Dokumentace",
|
||||||
"key": "Klíč",
|
"key": "Klíč",
|
||||||
"value": "Hodnota",
|
"value": "Hodnota",
|
||||||
"remove_custom_claim": "Odstranit vlastní nárok",
|
"remove_custom_claim": "Odstranit vlastní nárok",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Chcete se s účtem <b>{username}</b> odhlásit z Pocket ID?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Chcete se s účtem <b>{username}</b> odhlásit z Pocket ID?",
|
||||||
"sign_in_to_appname": "Přihlásit se k {appName}",
|
"sign_in_to_appname": "Přihlásit se k {appName}",
|
||||||
"please_try_to_sign_in_again": "Zkuste se prosím znovu přihlásit.",
|
"please_try_to_sign_in_again": "Zkuste se prosím znovu přihlásit.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Autentizujte se pomocí Vašeho přístupového klíče pro přístup k administrátorskému panelu.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Autentizovat",
|
"authenticate": "Autentizovat",
|
||||||
"appname_setup": "{appName} konfigurace",
|
|
||||||
"please_try_again": "Prosím, zkuste znovu.",
|
"please_try_again": "Prosím, zkuste znovu.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Chystáte se přihlásit k počátečnímu účtu správce. Kdokoli s tímto odkazem může přistupovat k účtu, dokud nebude přidán přístupový účet. Prosím nastavte přístupový klíč co nejdříve, abyste zabránili neoprávněnému přístupu.",
|
|
||||||
"continue": "Pokračovat",
|
"continue": "Pokračovat",
|
||||||
"alternative_sign_in": "Alternativní přihlášení",
|
"alternative_sign_in": "Alternativní přihlášení",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Pokud nemáte přístup k Vašemu přístupovému klíči, můžete se přihlášit pomocí jedné z následujících metod.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Pokud nemáte přístup k Vašemu přístupovému klíči, můžete se přihlášit pomocí jedné z následujících metod.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "E-mailovová oznámení o přihlášení",
|
"email_login_notification": "E-mailovová oznámení o přihlášení",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Poslat uživateli e-mail, když se přihlásí z nového zařízení.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Poslat uživateli e-mail, když se přihlásí z nového zařízení.",
|
||||||
"emai_login_code_requested_by_user": "Přihlašovací kód e-mailu vyžádaný uživatelem",
|
"emai_login_code_requested_by_user": "Přihlašovací kód e-mailu vyžádaný uživatelem",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Umožňuje uživatelům přihlásit se pomocí přihlašovacího kódu, který je odeslán na jejich e-mail. To výrazně snižuje bezpečnost, protože každý, kdo má přístup k e-mailu uživatele, může vstoupit.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Poslat e-mail přihlašovacímu kódu od administrátora",
|
"email_login_code_from_admin": "Poslat e-mail přihlašovacímu kódu od administrátora",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Umožňuje administrátorovi odeslat přihlašovací kód uživateli e-mailem.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Umožňuje administrátorovi odeslat přihlašovací kód uživateli e-mailem.",
|
||||||
"send_test_email": "Odeslat testovací e-mail",
|
"send_test_email": "Odeslat testovací e-mail",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "Obrázek na pozadí",
|
"background_image": "Obrázek na pozadí",
|
||||||
"language": "Jazyk",
|
"language": "Jazyk",
|
||||||
"reset_profile_picture_question": "Resetovat profilový obrázek?",
|
"reset_profile_picture_question": "Resetovat profilový obrázek?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Tímto odstraníte nahraný obrázek a obnovíte výchozí. Chcete pokračovat?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Obnovit",
|
"reset": "Obnovit",
|
||||||
"reset_to_default": "Obnovit výchozí",
|
"reset_to_default": "Obnovit výchozí",
|
||||||
"profile_picture_has_been_reset": "Profilový obrázek byl obnoven. Aktualizace může trvat několik minut.",
|
"profile_picture_has_been_reset": "Profilový obrázek byl obnoven. Aktualizace může trvat několik minut.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "Všichni uživatelé",
|
"all_users": "Všichni uživatelé",
|
||||||
"all_events": "Všechny události",
|
"all_events": "Všechny události",
|
||||||
"all_clients": "Všichni klienti",
|
"all_clients": "Všichni klienti",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Globální protokol auditu",
|
"global_audit_log": "Globální protokol auditu",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Zobrazit veškerou aktivitu uživatele za poslední 3 měsíce.",
|
"see_all_account_activities_from_the_last_3_months": "Zobrazit veškerou aktivitu uživatele za poslední 3 měsíce.",
|
||||||
"token_sign_in": "Přihlášení tokenem",
|
"token_sign_in": "Přihlášení tokenem",
|
||||||
"client_authorization": "Autorizace klienta",
|
"client_authorization": "Autorizace klienta",
|
||||||
"new_client_authorization": "Nová autorizace klienta",
|
"new_client_authorization": "Nová autorizace klienta",
|
||||||
"disable_animations": "Zakázat animace",
|
"disable_animations": "Zakázat animace",
|
||||||
"turn_off_ui_animations": "Vypnout všechny animace v celém administrátorském rozhraní.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Účet deaktivován",
|
"user_disabled": "Účet deaktivován",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Zakázaní uživatelé se nemohou přihlásit nebo používat služby.",
|
"disabled_users_cannot_log_in_or_use_services": "Zakázaní uživatelé se nemohou přihlásit nebo používat služby.",
|
||||||
"user_disabled_successfully": "Uživatel byl úspěšně deaktivován.",
|
"user_disabled_successfully": "Uživatel byl úspěšně deaktivován.",
|
||||||
@@ -346,30 +346,77 @@
|
|||||||
"the_device_has_been_authorized": "Zařízení bylo autorizováno.",
|
"the_device_has_been_authorized": "Zařízení bylo autorizováno.",
|
||||||
"enter_code_displayed_in_previous_step": "Zadejte kód, který byl zobrazen v předchozím kroku.",
|
"enter_code_displayed_in_previous_step": "Zadejte kód, který byl zobrazen v předchozím kroku.",
|
||||||
"authorize": "Autorizovat",
|
"authorize": "Autorizovat",
|
||||||
"federated_client_credentials": "Federated Client Credentials",
|
"federated_client_credentials": "Údaje o klientovi ve federaci",
|
||||||
"federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.",
|
"federated_client_credentials_description": "Pomocí federovaných přihlašovacích údajů klienta můžete ověřit klienty OIDC pomocí JWT tokenů vydaných třetí stranou.",
|
||||||
"add_federated_client_credential": "Add Federated Client Credential",
|
"add_federated_client_credential": "Přidat údaje federovaného klienta",
|
||||||
"add_another_federated_client_credential": "Add another federated client credential",
|
"add_another_federated_client_credential": "Přidat dalšího federovaného klienta",
|
||||||
"oidc_allowed_group_count": "Počet povolených skupin",
|
"oidc_allowed_group_count": "Počet povolených skupin",
|
||||||
"unrestricted": "Bez omezení",
|
"unrestricted": "Bez omezení",
|
||||||
"show_advanced_options": "Show Advanced Options",
|
"show_advanced_options": "Zobrazit rozšířené možnosti",
|
||||||
"hide_advanced_options": "Hide Advanced Options",
|
"hide_advanced_options": "Skrýt rozšířené rožnosti",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "Náhled OIDC dat",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Náhled údajů OIDC, které by měly být odeslány pro různé uživatele",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID Token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Access Token",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Userinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "ID Token Payload",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Access Token Payload",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
||||||
"copy": "Copy",
|
"copy": "Kopírovat",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "Nejsou k dispozici žádná náhledová data",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "Kopírovat vše",
|
||||||
"preview": "Preview",
|
"preview": "Náhled",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "Náhled pro {name} ({email})",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Náhled OIDC dat, která by byla odeslána pro uživatele",
|
||||||
"show": "Show",
|
"show": "Zobrazit",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Vyberte možnost",
|
||||||
"select_user": "Select User",
|
"select_user": "Vyberte uživatele",
|
||||||
"error": "Error"
|
"error": "Chyba",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,373 +3,420 @@
|
|||||||
"my_account": "Min konto",
|
"my_account": "Min konto",
|
||||||
"logout": "Log ud",
|
"logout": "Log ud",
|
||||||
"confirm": "Bekræft",
|
"confirm": "Bekræft",
|
||||||
"key": "Key",
|
"docs": "Dokumentation",
|
||||||
"value": "Value",
|
"key": "Nøgle",
|
||||||
"remove_custom_claim": "Remove custom claim",
|
"value": "Værdi",
|
||||||
"add_custom_claim": "Add custom claim",
|
"remove_custom_claim": "Fjern brugerdefineret claim",
|
||||||
"add_another": "Add another",
|
"add_custom_claim": "Tilføj brugerdefineret claim",
|
||||||
"select_a_date": "Select a date",
|
"add_another": "Tilføj endnu en",
|
||||||
"select_file": "Select File",
|
"select_a_date": "Vælg en dato",
|
||||||
"profile_picture": "Profile Picture",
|
"select_file": "Vælg en fil",
|
||||||
"profile_picture_is_managed_by_ldap_server": "The profile picture is managed by the LDAP server and cannot be changed here.",
|
"profile_picture": "Profilbillede",
|
||||||
"click_profile_picture_to_upload_custom": "Click on the profile picture to upload a custom one from your files.",
|
"profile_picture_is_managed_by_ldap_server": "Profilbilledet administreres af LDAP-serveren og kan ikke ændres her.",
|
||||||
"image_should_be_in_format": "The image should be in PNG or JPEG format.",
|
"click_profile_picture_to_upload_custom": "Klik på profilbilledet for at uploade et brugerdefineret billede fra dine filer.",
|
||||||
"items_per_page": "Items per page",
|
"image_should_be_in_format": "Billedet skal være i PNG eller JPEG-format.",
|
||||||
"no_items_found": "No items found",
|
"items_per_page": "Emner pr. side",
|
||||||
|
"no_items_found": "Ingen emner fundet",
|
||||||
"search": "Søg...",
|
"search": "Søg...",
|
||||||
"expand_card": "Expand card",
|
"expand_card": "Udvid kortet",
|
||||||
"copied": "Kopieret",
|
"copied": "Kopieret",
|
||||||
"click_to_copy": "Click to copy",
|
"click_to_copy": "Klik for at kopiere",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Noget gik galt",
|
||||||
"go_back_to_home": "Go back to home",
|
"go_back_to_home": "Gå tilbage til hjem",
|
||||||
"dont_have_access_to_your_passkey": "Don't have access to your passkey?",
|
"dont_have_access_to_your_passkey": "Har du ikke adgang til din adgangsnøgle?",
|
||||||
"login_background": "Login background",
|
"login_background": "Log ind baggrund",
|
||||||
"logo": "Logo",
|
"logo": "Logo",
|
||||||
"login_code": "Login Code",
|
"login_code": "Loginkode",
|
||||||
"create_a_login_code_to_sign_in_without_a_passkey_once": "Create a login code that the user can use to sign in without a passkey once.",
|
"create_a_login_code_to_sign_in_without_a_passkey_once": "Opret en loginkode, som brugeren kan bruge til at logge ind uden en adgangsnøgle én gang.",
|
||||||
"one_hour": "1 time",
|
"one_hour": "1 time",
|
||||||
"twelve_hours": "12 timer",
|
"twelve_hours": "12 timer",
|
||||||
"one_day": "1 dag",
|
"one_day": "1 dag",
|
||||||
"one_week": "1 uge",
|
"one_week": "1 uge",
|
||||||
"one_month": "1 måned",
|
"one_month": "1 måned",
|
||||||
"expiration": "Expiration",
|
"expiration": "Udløbstid",
|
||||||
"generate_code": "Generer kode",
|
"generate_code": "Generer kode",
|
||||||
"name": "Navn",
|
"name": "Navn",
|
||||||
"browser_unsupported": "Browser unsupported",
|
"browser_unsupported": "Browseren understøttes ikke",
|
||||||
"this_browser_does_not_support_passkeys": "This browser doesn't support passkeys. Please use an alternative sign in method.",
|
"this_browser_does_not_support_passkeys": "Denne browser understøtter ikke adgangsnøgler. Benyt venligst en alternativ login metode.",
|
||||||
"an_unknown_error_occurred": "An unknown error occurred",
|
"an_unknown_error_occurred": "En ukendt fejl opstod",
|
||||||
"authentication_process_was_aborted": "The authentication process was aborted",
|
"authentication_process_was_aborted": "Godkendelsesprocessen blev afbrudt",
|
||||||
"error_occurred_with_authenticator": "An error occurred with the authenticator",
|
"error_occurred_with_authenticator": "Der opstod en fejl med godkendelsesenheden",
|
||||||
"authenticator_does_not_support_discoverable_credentials": "The authenticator does not support discoverable credentials",
|
"authenticator_does_not_support_discoverable_credentials": "Godkenderen understøtter ikke synlige legitimationsoplysninger",
|
||||||
"authenticator_does_not_support_resident_keys": "The authenticator does not support resident keys",
|
"authenticator_does_not_support_resident_keys": "Godkenderen understøtter ikke gemte nøgler",
|
||||||
"passkey_was_previously_registered": "This passkey was previously registered",
|
"passkey_was_previously_registered": "Denne adgangsnøgle er allerede registreret",
|
||||||
"authenticator_does_not_support_any_of_the_requested_algorithms": "The authenticator does not support any of the requested algorithms",
|
"authenticator_does_not_support_any_of_the_requested_algorithms": "Godkenderen understøtter ikke nogen af de algoritmer, der anmodes om",
|
||||||
"authenticator_timed_out": "The authenticator timed out",
|
"authenticator_timed_out": "Godkenderen overskred tidsgrænsen",
|
||||||
"critical_error_occurred_contact_administrator": "A critical error occurred. Please contact your administrator.",
|
"critical_error_occurred_contact_administrator": "En kritisk fejl opstod. Kontakt venligst din administrator.",
|
||||||
"sign_in_to": "Sign in to {name}",
|
"sign_in_to": "Log ind på {name}",
|
||||||
"client_not_found": "Client not found",
|
"client_not_found": "Klient ikke fundet",
|
||||||
"client_wants_to_access_the_following_information": "<b>{client}</b> wants to access the following information:",
|
"client_wants_to_access_the_following_information": "{client} ønsker at få adgang til følgende oplysninger:",
|
||||||
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "Do you want to sign in to <b>{client}</b> with your {appName} account?",
|
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "Vil du logge ind på {client} med din {appName}-konto?",
|
||||||
"email": "Email",
|
"email": "E-mail",
|
||||||
"view_your_email_address": "View your email address",
|
"view_your_email_address": "Se din e-mailadresse",
|
||||||
"profile": "Profile",
|
"profile": "Profil",
|
||||||
"view_your_profile_information": "View your profile information",
|
"view_your_profile_information": "Se dine profiloplysninger",
|
||||||
"groups": "Groups",
|
"groups": "Grupper",
|
||||||
"view_the_groups_you_are_a_member_of": "View the groups you are a member of",
|
"view_the_groups_you_are_a_member_of": "Se de grupper, du er medlem af",
|
||||||
"cancel": "Cancel",
|
"cancel": "Annuller",
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Log ind",
|
||||||
"try_again": "Try again",
|
"try_again": "Prøv igen",
|
||||||
"client_logo": "Client Logo",
|
"client_logo": "Klientlogo",
|
||||||
"sign_out": "Sign out",
|
"sign_out": "Log ud",
|
||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Do you want to sign out of {appName} with the account <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Vil du logge ud af {appName} med kontoen <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Sign in to {appName}",
|
"sign_in_to_appname": "Log ind på {appName}",
|
||||||
"please_try_to_sign_in_again": "Please try to sign in again.",
|
"please_try_to_sign_in_again": "Prøv at logge ind igen.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Authenticate yourself with your passkey to access the admin panel.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Authenticate",
|
"authenticate": "Bekræft identitet",
|
||||||
"appname_setup": "{appName} Setup",
|
"please_try_again": "Prøv venligst igen.",
|
||||||
"please_try_again": "Please try again.",
|
"continue": "Fortsæt",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "You're about to sign in to the initial admin account. Anyone with this link can access the account until a passkey is added. Please set up a passkey as soon as possible to prevent unauthorized access.",
|
"alternative_sign_in": "Andre loginmetoder",
|
||||||
"continue": "Continue",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Hvis du ikke har adgang til din adgangsnøgle, kan du logge ind med en af følgende metoder.",
|
||||||
"alternative_sign_in": "Alternative Sign In",
|
"use_your_passkey_instead": "Vil du i stedet bruge din adgangsnøgle?",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
|
||||||
"use_your_passkey_instead": "Use your passkey instead?",
|
|
||||||
"email_login": "E-mail Login",
|
"email_login": "E-mail Login",
|
||||||
"enter_a_login_code_to_sign_in": "Enter a login code to sign in.",
|
"enter_a_login_code_to_sign_in": "Indtast en loginkode for at logge ind.",
|
||||||
"request_a_login_code_via_email": "Request a login code via email.",
|
"request_a_login_code_via_email": "Anmod om en loginkode via e-mail.",
|
||||||
"go_back": "Gå tilbage",
|
"go_back": "Gå tilbage",
|
||||||
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "An email has been sent to the provided email, if it exists in the system.",
|
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "En e-mail er blevet sendt til den angivne e-mailadresse, hvis den findes i systemet.",
|
||||||
"enter_code": "Indtast kode",
|
"enter_code": "Indtast kode",
|
||||||
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Enter your email address to receive an email with a login code.",
|
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Indtast din e-mailadresse for at modtage en login-kode.\n",
|
||||||
"your_email": "Din e-mail",
|
"your_email": "Din e-mail",
|
||||||
"submit": "Submit",
|
"submit": "Indsend",
|
||||||
"enter_the_code_you_received_to_sign_in": "Enter the code you received to sign in.",
|
"enter_the_code_you_received_to_sign_in": "Indtast den kode, du har modtaget, for at logge ind.",
|
||||||
"code": "Code",
|
"code": "Kode",
|
||||||
"invalid_redirect_url": "Invalid redirect URL",
|
"invalid_redirect_url": "Ugyldig redirect-URL",
|
||||||
"audit_log": "Audit Log",
|
"audit_log": "Aktivitetslog",
|
||||||
"users": "Brugere",
|
"users": "Brugere",
|
||||||
"user_groups": "Brugergrupper",
|
"user_groups": "Brugergrupper",
|
||||||
"oidc_clients": "OIDC Clients",
|
"oidc_clients": "OIDC-klienter",
|
||||||
"api_keys": "API Keys",
|
"api_keys": "API-nøgler",
|
||||||
"application_configuration": "Application Configuration",
|
"application_configuration": "Applikationsindstillinger",
|
||||||
"settings": "Indstillinger",
|
"settings": "Indstillinger",
|
||||||
"update_pocket_id": "Opdater Pocket ID",
|
"update_pocket_id": "Opdater Pocket ID",
|
||||||
"powered_by": "Powered by",
|
"powered_by": "Drevet af",
|
||||||
"see_your_account_activities_from_the_last_3_months": "See your account activities from the last 3 months.",
|
"see_your_account_activities_from_the_last_3_months": "Se dine kontoaktiviteter fra de sidste 3 måneder.",
|
||||||
"time": "Tid",
|
"time": "Tid",
|
||||||
"event": "Event",
|
"event": "Hændelse",
|
||||||
"approximate_location": "Approximate Location",
|
"approximate_location": "Omtrentlig placering",
|
||||||
"ip_address": "IP-adresse",
|
"ip_address": "IP-adresse",
|
||||||
"device": "Enhed",
|
"device": "Enhed",
|
||||||
"client": "Klient",
|
"client": "Klient",
|
||||||
"unknown": "Ukendt",
|
"unknown": "Ukendt",
|
||||||
"account_details_updated_successfully": "Account details updated successfully",
|
"account_details_updated_successfully": "Kontodetaljer blev opdateret",
|
||||||
"profile_picture_updated_successfully": "Profile picture updated successfully. It may take a few minutes to update.",
|
"profile_picture_updated_successfully": "Profilbillede opdateret. Det kan tage et par minutter før ændringen vises.",
|
||||||
"account_settings": "Account Settings",
|
"account_settings": "Kontoindstillinger",
|
||||||
"passkey_missing": "Passkey missing",
|
"passkey_missing": "Adgangsnøgle mangler",
|
||||||
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Please add a passkey to prevent losing access to your account.",
|
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Tilføj en adgangsnøgle for at undgå at miste adgangen til din konto.",
|
||||||
"single_passkey_configured": "Single Passkey Configured",
|
"single_passkey_configured": "Én adgangsnøgle er konfigureret",
|
||||||
"it_is_recommended_to_add_more_than_one_passkey": "It is recommended to add more than one passkey to avoid losing access to your account.",
|
"it_is_recommended_to_add_more_than_one_passkey": "Det anbefales at tilføje mere end én adgangsnøgle for at undgå at miste adgangen til din konto.",
|
||||||
"account_details": "Account Details",
|
"account_details": "Kontooplysninger",
|
||||||
"passkeys": "Passkeys",
|
"passkeys": "Adgangsnøgler",
|
||||||
"manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "Manage your passkeys that you can use to authenticate yourself.",
|
"manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "Administrér dine adgangsnøgler, som du kan bruge til at godkende dig selv.",
|
||||||
"add_passkey": "Add Passkey",
|
"add_passkey": "Tilføj adgangsnøgle",
|
||||||
"create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "Create a one-time login code to sign in from a different device without a passkey.",
|
"create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "Opret en engangskode for at logge ind fra en anden enhed uden en adgangsnøgle.",
|
||||||
"create": "Create",
|
"create": "Opret",
|
||||||
"first_name": "First name",
|
"first_name": "Fornavn",
|
||||||
"last_name": "Last name",
|
"last_name": "Efternavn",
|
||||||
"username": "Username",
|
"username": "Brugernavn",
|
||||||
"save": "Save",
|
"save": "Gem",
|
||||||
"username_can_only_contain": "Username can only contain lowercase letters, numbers, underscores, dots, hyphens, and '@' symbols",
|
"username_can_only_contain": "Brugernavn må kun indeholde små bogstaver, tal, understregninger (_), punktummer (.), bindestreger (-) og @-tegn",
|
||||||
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Sign in using the following code. The code will expire in 15 minutes.",
|
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Log ind med nedenstående kode. Koden udløber om 15 minutter.",
|
||||||
"or_visit": "or visit",
|
"or_visit": "eller besøg",
|
||||||
"added_on": "Added on",
|
"added_on": "Tilføjet den",
|
||||||
"rename": "Rename",
|
"rename": "Omdøb",
|
||||||
"delete": "Delete",
|
"delete": "Slet",
|
||||||
"are_you_sure_you_want_to_delete_this_passkey": "Are you sure you want to delete this passkey?",
|
"are_you_sure_you_want_to_delete_this_passkey": "Er du sikker på, at du vil slette denne adgangsnøgle?",
|
||||||
"passkey_deleted_successfully": "Passkey deleted successfully",
|
"passkey_deleted_successfully": "Adgangsnøgle blev slettet",
|
||||||
"delete_passkey_name": "Delete {passkeyName}",
|
"delete_passkey_name": "Slet {passkeyName}",
|
||||||
"passkey_name_updated_successfully": "Passkey name updated successfully",
|
"passkey_name_updated_successfully": "Navnet på adgangsnøglen blev opdateret",
|
||||||
"name_passkey": "Name Passkey",
|
"name_passkey": "Navngiv adgangsnøgle",
|
||||||
"name_your_passkey_to_easily_identify_it_later": "Name your passkey to easily identify it later.",
|
"name_your_passkey_to_easily_identify_it_later": "Giv din adgangsnøgle et navn, så du nemt kan genkende den senere.",
|
||||||
"create_api_key": "Create API Key",
|
"create_api_key": "Opret API-nøgle",
|
||||||
"add_a_new_api_key_for_programmatic_access": "Add a new API key for programmatic access.",
|
"add_a_new_api_key_for_programmatic_access": "Tilføj en ny API-nøgle til programmatisk adgang.",
|
||||||
"add_api_key": "Add API Key",
|
"add_api_key": "Tilføj API-nøgle",
|
||||||
"manage_api_keys": "Manage API Keys",
|
"manage_api_keys": "Administrér API-nøgler",
|
||||||
"api_key_created": "API Key Created",
|
"api_key_created": "API-nøgle oprettet",
|
||||||
"for_security_reasons_this_key_will_only_be_shown_once": "For security reasons, this key will only be shown once. Please store it securely.",
|
"for_security_reasons_this_key_will_only_be_shown_once": "Af sikkerhedshensyn vises denne nøgle kun én gang. Gem den et sikkert sted.",
|
||||||
"description": "Description",
|
"description": "Beskrivelse",
|
||||||
"api_key": "API Key",
|
"api_key": "API-nøgle",
|
||||||
"close": "Close",
|
"close": "Luk",
|
||||||
"name_to_identify_this_api_key": "Name to identify this API key.",
|
"name_to_identify_this_api_key": "Navn til at identificere denne API-nøgle.",
|
||||||
"expires_at": "Expires At",
|
"expires_at": "Udløber den",
|
||||||
"when_this_api_key_will_expire": "When this API key will expire.",
|
"when_this_api_key_will_expire": "Hvornår denne API-nøgle udløber.",
|
||||||
"optional_description_to_help_identify_this_keys_purpose": "Optional description to help identify this key's purpose.",
|
"optional_description_to_help_identify_this_keys_purpose": "Valgfri beskrivelse for at identificere nøglens formål.",
|
||||||
"expiration_date_must_be_in_the_future": "Expiration date must be in the future",
|
"expiration_date_must_be_in_the_future": "Udløbsdatoen skal ligge i fremtiden",
|
||||||
"revoke_api_key": "Revoke API Key",
|
"revoke_api_key": "Tilbagekald API-nøgle",
|
||||||
"never": "Never",
|
"never": "Aldrig",
|
||||||
"revoke": "Revoke",
|
"revoke": "Tilbagekald",
|
||||||
"api_key_revoked_successfully": "API key revoked successfully",
|
"api_key_revoked_successfully": "API-nøgle blev tilbagekaldt",
|
||||||
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Are you sure you want to revoke the API key \"{apiKeyName}\"? This will break any integrations using this key.",
|
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Er du sikker på, at du vil tilbagekalde API-nøglen \"{apiKeyName}\"? Dette vil afbryde alle integrationer, der bruger nøglen.",
|
||||||
"last_used": "Last Used",
|
"last_used": "Sidst brugt",
|
||||||
"actions": "Actions",
|
"actions": "Handlinger",
|
||||||
"images_updated_successfully": "Images updated successfully",
|
"images_updated_successfully": "Billeder blev opdateret",
|
||||||
"general": "General",
|
"general": "Generelt",
|
||||||
"configure_smtp_to_send_emails": "Enable email notifications to alert users when a login is detected from a new device or location.",
|
"configure_smtp_to_send_emails": "Aktivér e-mailnotifikationer for at advare brugere, når et login registreres fra en ny enhed eller placering.",
|
||||||
"ldap": "LDAP",
|
"ldap": "LDAP",
|
||||||
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Configure LDAP settings to sync users and groups from an LDAP server.",
|
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Konfigurer LDAP-indstillinger for at synkronisere brugere og grupper fra en LDAP-server",
|
||||||
"images": "Images",
|
"images": "Billeder",
|
||||||
"update": "Update",
|
"update": "Opdater",
|
||||||
"email_configuration_updated_successfully": "Email configuration updated successfully",
|
"email_configuration_updated_successfully": "E-mailkonfiguration blev opdateret",
|
||||||
"save_changes_question": "Save changes?",
|
"save_changes_question": "Gem ændringer?",
|
||||||
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "You have to save the changes before sending a test email. Do you want to save now?",
|
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "Du skal gemme ændringerne, før du kan sende en test-e-mail. Vil du gemme nu?",
|
||||||
"save_and_send": "Save and send",
|
"save_and_send": "Gem og send",
|
||||||
"test_email_sent_successfully": "Test email sent successfully to your email address.",
|
"test_email_sent_successfully": "Test-e-mail blev sendt til din e-mailadresse.",
|
||||||
"failed_to_send_test_email": "Failed to send test email. Check the server logs for more information.",
|
"failed_to_send_test_email": "Kunne ikke sende test-e-mail. Tjek serverloggen for flere oplysninger.",
|
||||||
"smtp_configuration": "SMTP Configuration",
|
"smtp_configuration": "SMTP-konfiguration",
|
||||||
"smtp_host": "SMTP Host",
|
"smtp_host": "SMTP-vært",
|
||||||
"smtp_port": "SMTP Port",
|
"smtp_port": "SMTP-port",
|
||||||
"smtp_user": "SMTP User",
|
"smtp_user": "SMTP-bruger",
|
||||||
"smtp_password": "SMTP Password",
|
"smtp_password": "SMTP-adgangskode",
|
||||||
"smtp_from": "SMTP From",
|
"smtp_from": "SMTP-afsender",
|
||||||
"smtp_tls_option": "SMTP TLS Option",
|
"smtp_tls_option": "SMTP TLS-indstilling",
|
||||||
"email_tls_option": "Email TLS Option",
|
"email_tls_option": "E-mail TLS-indstilling",
|
||||||
"skip_certificate_verification": "Skip Certificate Verification",
|
"skip_certificate_verification": "Spring certifikatverificering over",
|
||||||
"this_can_be_useful_for_selfsigned_certificates": "This can be useful for self-signed certificates.",
|
"this_can_be_useful_for_selfsigned_certificates": "Dette kan være nyttigt ved selvsignerede certifikater.",
|
||||||
"enabled_emails": "Enabled Emails",
|
"enabled_emails": "Aktiverede e-mails",
|
||||||
"email_login_notification": "Email Login Notification",
|
"email_login_notification": "Notifikation om login via e-mail",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send an email to the user when they log in from a new device.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send en e-mail til brugeren, når de logger ind fra en ny enhed.",
|
||||||
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
"emai_login_code_requested_by_user": "Tillad brugere at anmode om login-koder via e-mail",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Tillader brugere at omgå adgangsnøgler ved at anmode om en login-kode sendt til deres e-mail. Dette reducerer sikkerheden væsentligt, da enhver med adgang til brugerens e-mail kan få adgang.\n",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Tillad administratorer at sende login-koder via e-mail",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Giver en administrator mulighed for at sende en login-kode til brugeren via e-mail.",
|
||||||
"send_test_email": "Send test email",
|
"send_test_email": "Send test-e-mail",
|
||||||
"application_configuration_updated_successfully": "Application configuration updated successfully",
|
"application_configuration_updated_successfully": "Applikationsindstillinger blev opdateret",
|
||||||
"application_name": "Application Name",
|
"application_name": "Applikationsnavn",
|
||||||
"session_duration": "Session Duration",
|
"session_duration": "Sessionsvarighed",
|
||||||
"the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "The duration of a session in minutes before the user has to sign in again.",
|
"the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "Varighed i minutter før brugeren skal logge ind igen.",
|
||||||
"enable_self_account_editing": "Enable Self-Account Editing",
|
"enable_self_account_editing": "Aktivér redigering af egen konto",
|
||||||
"whether_the_users_should_be_able_to_edit_their_own_account_details": "Whether the users should be able to edit their own account details.",
|
"whether_the_users_should_be_able_to_edit_their_own_account_details": "Om brugere må redigere deres egne kontooplysninger.",
|
||||||
"emails_verified": "Emails Verified",
|
"emails_verified": "E-mailadresser verificeret",
|
||||||
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "Whether the user's email should be marked as verified for the OIDC clients.",
|
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "Om brugerens e-mail skal markeres som verificeret for OIDC-klienter.",
|
||||||
"ldap_configuration_updated_successfully": "LDAP configuration updated successfully",
|
"ldap_configuration_updated_successfully": "LDAP-konfiguration blev opdateret",
|
||||||
"ldap_disabled_successfully": "LDAP disabled successfully",
|
"ldap_disabled_successfully": "LDAP blev deaktiveret",
|
||||||
"ldap_sync_finished": "LDAP sync finished",
|
"ldap_sync_finished": "LDAP-synkronisering fuldført",
|
||||||
"client_configuration": "Client Configuration",
|
"client_configuration": "Klientkonfiguration",
|
||||||
"ldap_url": "LDAP URL",
|
"ldap_url": "LDAP-URL",
|
||||||
"ldap_bind_dn": "LDAP Bind DN",
|
"ldap_bind_dn": "LDAP Bind DN",
|
||||||
"ldap_bind_password": "LDAP Bind Password",
|
"ldap_bind_password": "LDAP-bindingsadgangskode",
|
||||||
"ldap_base_dn": "LDAP Base DN",
|
"ldap_base_dn": "LDAP Base DN",
|
||||||
"user_search_filter": "User Search Filter",
|
"user_search_filter": "Brugersøgningsfilter",
|
||||||
"the_search_filter_to_use_to_search_or_sync_users": "The Search filter to use to search/sync users.",
|
"the_search_filter_to_use_to_search_or_sync_users": "Søgefilteret der bruges til at finde eller synkronisere brugere.",
|
||||||
"groups_search_filter": "Groups Search Filter",
|
"groups_search_filter": "Gruppesøgningsfilter",
|
||||||
"the_search_filter_to_use_to_search_or_sync_groups": "The Search filter to use to search/sync groups.",
|
"the_search_filter_to_use_to_search_or_sync_groups": "Søgefilteret der bruges til at finde eller synkronisere grupper.",
|
||||||
"attribute_mapping": "Attribute Mapping",
|
"attribute_mapping": "Attributtilknytning",
|
||||||
"user_unique_identifier_attribute": "User Unique Identifier Attribute",
|
"user_unique_identifier_attribute": "Unik brugeridentifikator-attribut",
|
||||||
"the_value_of_this_attribute_should_never_change": "The value of this attribute should never change.",
|
"the_value_of_this_attribute_should_never_change": "Værdien af denne attribut bør aldrig ændres.",
|
||||||
"username_attribute": "Username Attribute",
|
"username_attribute": "Brugernavn-attribut",
|
||||||
"user_mail_attribute": "User Mail Attribute",
|
"user_mail_attribute": "E-mail-attribut",
|
||||||
"user_first_name_attribute": "User First Name Attribute",
|
"user_first_name_attribute": "Fornavns-attribut",
|
||||||
"user_last_name_attribute": "User Last Name Attribute",
|
"user_last_name_attribute": "Efternavns-attribut",
|
||||||
"user_profile_picture_attribute": "User Profile Picture Attribute",
|
"user_profile_picture_attribute": "Profilbilled-attribut",
|
||||||
"the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "The value of this attribute can either be a URL, a binary or a base64 encoded image.",
|
"the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "Værdien af denne attribut kan være en URL, en binær fil eller et base64-kodet billede.",
|
||||||
"group_members_attribute": "Group Members Attribute",
|
"group_members_attribute": "Gruppemedlems-attribut",
|
||||||
"the_attribute_to_use_for_querying_members_of_a_group": "The attribute to use for querying members of a group.",
|
"the_attribute_to_use_for_querying_members_of_a_group": "Attributten der bruges til at hente gruppemedlemmer.",
|
||||||
"group_unique_identifier_attribute": "Group Unique Identifier Attribute",
|
"group_unique_identifier_attribute": "Unik gruppe-ID-attribut",
|
||||||
"group_name_attribute": "Group Name Attribute",
|
"group_name_attribute": "Gruppenavns-attribut",
|
||||||
"admin_group_name": "Admin Group Name",
|
"admin_group_name": "Administratorgruppe-navn",
|
||||||
"members_of_this_group_will_have_admin_privileges_in_pocketid": "Members of this group will have Admin Privileges in Pocket ID.",
|
"members_of_this_group_will_have_admin_privileges_in_pocketid": "Medlemmer af denne gruppe får administratorrettigheder i Pocket ID.",
|
||||||
"disable": "Disable",
|
"disable": "Deaktivér",
|
||||||
"sync_now": "Sync now",
|
"sync_now": "Synkronisér nu",
|
||||||
"enable": "Enable",
|
"enable": "Aktivér",
|
||||||
"user_created_successfully": "User created successfully",
|
"user_created_successfully": "Bruger blev oprettet",
|
||||||
"create_user": "Create User",
|
"create_user": "Opret bruger",
|
||||||
"add_a_new_user_to_appname": "Add a new user to {appName}",
|
"add_a_new_user_to_appname": "Tilføj en ny bruger til {appName}",
|
||||||
"add_user": "Add User",
|
"add_user": "Tilføj bruger",
|
||||||
"manage_users": "Manage Users",
|
"manage_users": "Administrér brugere",
|
||||||
"admin_privileges": "Admin Privileges",
|
"admin_privileges": "Administratorrettigheder",
|
||||||
"admins_have_full_access_to_the_admin_panel": "Admins have full access to the admin panel.",
|
"admins_have_full_access_to_the_admin_panel": "Administratorer har fuld adgang til administratorpanelet.",
|
||||||
"delete_firstname_lastname": "Delete {firstName} {lastName}",
|
"delete_firstname_lastname": "Slet {firstName} {lastName}",
|
||||||
"are_you_sure_you_want_to_delete_this_user": "Are you sure you want to delete this user?",
|
"are_you_sure_you_want_to_delete_this_user": "Er du sikker på, at du vil slette denne bruger?",
|
||||||
"user_deleted_successfully": "User deleted successfully",
|
"user_deleted_successfully": "Brugeren blev slettet",
|
||||||
"role": "Role",
|
"role": "Rolle",
|
||||||
"source": "Source",
|
"source": "Kilde",
|
||||||
"admin": "Admin",
|
"admin": "Administrator",
|
||||||
"user": "User",
|
"user": "Bruger",
|
||||||
"local": "Local",
|
"local": "Lokal",
|
||||||
"toggle_menu": "Toggle menu",
|
"toggle_menu": "Åbn/luk menu",
|
||||||
"edit": "Edit",
|
"edit": "Redigér",
|
||||||
"user_groups_updated_successfully": "User groups updated successfully",
|
"user_groups_updated_successfully": "Brugergrupper blev opdateret",
|
||||||
"user_updated_successfully": "User updated successfully",
|
"user_updated_successfully": "Bruger blev opdateret",
|
||||||
"custom_claims_updated_successfully": "Custom claims updated successfully",
|
"custom_claims_updated_successfully": "Brugerdefinerede claims blev opdateret",
|
||||||
"back": "Back",
|
"back": "Tilbage",
|
||||||
"user_details_firstname_lastname": "User Details {firstName} {lastName}",
|
"user_details_firstname_lastname": "Brugeroplysninger for {firstName} {lastName}",
|
||||||
"manage_which_groups_this_user_belongs_to": "Manage which groups this user belongs to.",
|
"manage_which_groups_this_user_belongs_to": "Administrér hvilke grupper denne bruger tilhører.",
|
||||||
"custom_claims": "Custom Claims",
|
"custom_claims": "Brugerdefinerede claims",
|
||||||
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "Custom claims are key-value pairs that can be used to store additional information about a user. These claims will be included in the ID token if the scope 'profile' is requested.",
|
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "Brugerdefinerede claims er nøgle-værdi-par, der kan bruges til at gemme yderligere information om en bruger. Disse claims vil blive inkluderet i ID-tokenet, hvis scopen ’profile’ er anmodet.",
|
||||||
"user_group_created_successfully": "User group created successfully",
|
"user_group_created_successfully": "Brugergruppe blev oprettet",
|
||||||
"create_user_group": "Create User Group",
|
"create_user_group": "Opret brugergruppe",
|
||||||
"create_a_new_group_that_can_be_assigned_to_users": "Create a new group that can be assigned to users.",
|
"create_a_new_group_that_can_be_assigned_to_users": "Opret en ny gruppe, der kan tildeles brugere.",
|
||||||
"add_group": "Add Group",
|
"add_group": "Tilføj gruppe",
|
||||||
"manage_user_groups": "Manage User Groups",
|
"manage_user_groups": "Administrér brugergrupper",
|
||||||
"friendly_name": "Friendly Name",
|
"friendly_name": "Kaldenavn",
|
||||||
"name_that_will_be_displayed_in_the_ui": "Name that will be displayed in the UI",
|
"name_that_will_be_displayed_in_the_ui": "Navn der vises i brugerfladen",
|
||||||
"name_that_will_be_in_the_groups_claim": "Name that will be in the \"groups\" claim",
|
"name_that_will_be_in_the_groups_claim": "Navn der vises i “groups”-claimet",
|
||||||
"delete_name": "Delete {name}",
|
"delete_name": "Slet {name}",
|
||||||
"are_you_sure_you_want_to_delete_this_user_group": "Are you sure you want to delete this user group?",
|
"are_you_sure_you_want_to_delete_this_user_group": "Er du sikker på, at du vil slette denne brugergruppe?",
|
||||||
"user_group_deleted_successfully": "User group deleted successfully",
|
"user_group_deleted_successfully": "Brugergruppe blev slettet",
|
||||||
"user_count": "User Count",
|
"user_count": "Antal brugere",
|
||||||
"user_group_updated_successfully": "User group updated successfully",
|
"user_group_updated_successfully": "Brugergruppe blev opdateret",
|
||||||
"users_updated_successfully": "Users updated successfully",
|
"users_updated_successfully": "Brugere blev opdateret",
|
||||||
"user_group_details_name": "User Group Details {name}",
|
"user_group_details_name": "Detaljer for brugergruppe {name}",
|
||||||
"assign_users_to_this_group": "Assign users to this group.",
|
"assign_users_to_this_group": "Tildel brugere til denne gruppe.",
|
||||||
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "Custom claims are key-value pairs that can be used to store additional information about a user. These claims will be included in the ID token if the scope 'profile' is requested. Custom claims defined on the user will be prioritized if there are conflicts.",
|
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "Brugerdefinerede claims er nøgle-værdi-par, der bruges til at gemme yderligere information om en bruger. Disse claims vil blive inkluderet i ID-tokenet, hvis scopen ’profile’ anmodes. Brugerdefinerede claims defineret direkte på brugeren har prioritet, hvis der opstår konflikter.",
|
||||||
"oidc_client_created_successfully": "OIDC client created successfully",
|
"oidc_client_created_successfully": "OIDC-klient blev oprettet",
|
||||||
"create_oidc_client": "Create OIDC Client",
|
"create_oidc_client": "Opret OIDC-klient",
|
||||||
"add_a_new_oidc_client_to_appname": "Add a new OIDC client to {appName}.",
|
"add_a_new_oidc_client_to_appname": "Tilføj en ny OIDC-klient til {appName}",
|
||||||
"add_oidc_client": "Add OIDC Client",
|
"add_oidc_client": "Tilføj OIDC-klient",
|
||||||
"manage_oidc_clients": "Manage OIDC Clients",
|
"manage_oidc_clients": "Administrér OIDC-klienter",
|
||||||
"one_time_link": "One Time Link",
|
"one_time_link": "Engangslink",
|
||||||
"use_this_link_to_sign_in_once": "Use this link to sign in once. This is needed for users who haven't added a passkey yet or have lost it.",
|
"use_this_link_to_sign_in_once": "Brug dette link til at logge ind én gang. Det er nødvendigt for brugere, som endnu ikke har tilføjet en adgangsnøgle eller har mistet den.",
|
||||||
"add": "Add",
|
"add": "Tilføj",
|
||||||
"callback_urls": "Callback URLs",
|
"callback_urls": "Callback-URL’er",
|
||||||
"logout_callback_urls": "Logout Callback URLs",
|
"logout_callback_urls": "Logout Callback-URL’er",
|
||||||
"public_client": "Public Client",
|
"public_client": "Public-klient",
|
||||||
"public_clients_description": "Public clients do not have a client secret. They are designed for mobile, web, and native applications where secrets cannot be securely stored.",
|
"public_clients_description": "Public-klienter har ikke en klienthemmelighed. De er designet til mobil-, web- og native-apps, hvor hemmeligheder ikke kan opbevares sikkert.",
|
||||||
"pkce": "PKCE",
|
"pkce": "PKCE",
|
||||||
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Public Key Code Exchange is a security feature to prevent CSRF and authorization code interception attacks.",
|
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Public Key Code Exchange er en sikkerhedsfunktion, der beskytter mod CSRF- og authorization code-angreb.",
|
||||||
"name_logo": "{name} logo",
|
"name_logo": "Logo for {name}",
|
||||||
"change_logo": "Change Logo",
|
"change_logo": "Skift logo",
|
||||||
"upload_logo": "Upload Logo",
|
"upload_logo": "Upload logo",
|
||||||
"remove_logo": "Remove Logo",
|
"remove_logo": "Fjern logo",
|
||||||
"are_you_sure_you_want_to_delete_this_oidc_client": "Are you sure you want to delete this OIDC client?",
|
"are_you_sure_you_want_to_delete_this_oidc_client": "Er du sikker på, at du vil slette denne OIDC-klient?",
|
||||||
"oidc_client_deleted_successfully": "OIDC client deleted successfully",
|
"oidc_client_deleted_successfully": "OIDC-klient blev slettet",
|
||||||
"authorization_url": "Authorization URL",
|
"authorization_url": "Authorization URL",
|
||||||
"oidc_discovery_url": "OIDC Discovery URL",
|
"oidc_discovery_url": "OIDC Discovery URL",
|
||||||
"token_url": "Token URL",
|
"token_url": "Token URL",
|
||||||
"userinfo_url": "Userinfo URL",
|
"userinfo_url": "Userinfo URL",
|
||||||
"logout_url": "Logout URL",
|
"logout_url": "Logout URL",
|
||||||
"certificate_url": "Certificate URL",
|
"certificate_url": "Certificate URL",
|
||||||
"enabled": "Enabled",
|
"enabled": "Aktiveret",
|
||||||
"disabled": "Disabled",
|
"disabled": "Deaktiveret",
|
||||||
"oidc_client_updated_successfully": "OIDC client updated successfully",
|
"oidc_client_updated_successfully": "OIDC-klient blev opdateret",
|
||||||
"create_new_client_secret": "Create new client secret",
|
"create_new_client_secret": "Opret ny client secret",
|
||||||
"are_you_sure_you_want_to_create_a_new_client_secret": "Are you sure you want to create a new client secret? The old one will be invalidated.",
|
"are_you_sure_you_want_to_create_a_new_client_secret": "Vil du oprette en ny client secret? Den gamle bliver ugyldig og kan ikke længere bruges.",
|
||||||
"generate": "Generate",
|
"generate": "Generér",
|
||||||
"new_client_secret_created_successfully": "New client secret created successfully",
|
"new_client_secret_created_successfully": "Ny client secret blev oprettet",
|
||||||
"allowed_user_groups_updated_successfully": "Allowed user groups updated successfully",
|
"allowed_user_groups_updated_successfully": "Tilladte brugergrupper blev opdateret",
|
||||||
"oidc_client_name": "OIDC Client {name}",
|
"oidc_client_name": "OIDC-klient {name}",
|
||||||
"client_id": "Client ID",
|
"client_id": "Client ID",
|
||||||
"client_secret": "Client secret",
|
"client_secret": "Client secret",
|
||||||
"show_more_details": "Show more details",
|
"show_more_details": "Vis flere detaljer",
|
||||||
"allowed_user_groups": "Allowed User Groups",
|
"allowed_user_groups": "Tilladte brugergrupper",
|
||||||
"add_user_groups_to_this_client_to_restrict_access_to_users_in_these_groups": "Add user groups to this client to restrict access to users in these groups. If no user groups are selected, all users will have access to this client.",
|
"add_user_groups_to_this_client_to_restrict_access_to_users_in_these_groups": "Tilføj brugergrupper til denne klient for at begrænse adgangen til brugere i disse grupper. Hvis ingen brugergrupper er valgt, vil alle brugere have adgang til klienten.",
|
||||||
"favicon": "Favicon",
|
"favicon": "Favicon",
|
||||||
"light_mode_logo": "Light Mode Logo",
|
"light_mode_logo": "Logo til lys tilstand",
|
||||||
"dark_mode_logo": "Dark Mode Logo",
|
"dark_mode_logo": "Logo til mørk tilstand",
|
||||||
"background_image": "Background Image",
|
"background_image": "Baggrundsbillede",
|
||||||
"language": "Language",
|
"language": "Sprog",
|
||||||
"reset_profile_picture_question": "Reset profile picture?",
|
"reset_profile_picture_question": "Nulstil profilbillede?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image, and reset the profile picture to default. Do you want to continue?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Dette vil fjerne det uploadede billede og nulstille profilbilledet til standard. Vil du fortsætte?",
|
||||||
"reset": "Reset",
|
"reset": "Nulstil",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Nulstil til standard",
|
||||||
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
"profile_picture_has_been_reset": "Profilbilledet er blevet nulstillet. Det kan tage et par minutter at opdatere.",
|
||||||
"select_the_language_you_want_to_use": "Select the language you want to use. Some languages may not be fully translated.",
|
"select_the_language_you_want_to_use": "Vælg det sprog, du vil bruge. Nogle sprog er muligvis ikke fuldt oversat.",
|
||||||
"personal": "Personal",
|
"personal": "Personlig",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"all_users": "All Users",
|
"all_users": "Alle brugere",
|
||||||
"all_events": "All Events",
|
"all_events": "Alle hændelser",
|
||||||
"all_clients": "All Clients",
|
"all_clients": "Alle klienter",
|
||||||
"global_audit_log": "Global Audit Log",
|
"all_locations": "All Locations",
|
||||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
"global_audit_log": "Global aktivitetslog",
|
||||||
"token_sign_in": "Token Sign In",
|
"see_all_account_activities_from_the_last_3_months": "Se al brugeraktivitet for de seneste 3 måneder.",
|
||||||
"client_authorization": "Client Authorization",
|
"token_sign_in": "Token-login",
|
||||||
"new_client_authorization": "New Client Authorization",
|
"client_authorization": "Godkendelse af klient",
|
||||||
"disable_animations": "Disable Animations",
|
"new_client_authorization": "Ny klientgodkendelse",
|
||||||
"turn_off_ui_animations": "Turn off all animations throughout the Admin UI.",
|
"disable_animations": "Deaktiver animationer",
|
||||||
"user_disabled": "Account Disabled",
|
"turn_off_ui_animations": "Slå animationer fra for hele brugergrænsefladen.",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"user_disabled": "Konto deaktiveret",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"disabled_users_cannot_log_in_or_use_services": "Deaktiverede brugere kan ikke logge ind eller bruge tjenester.",
|
||||||
"user_enabled_successfully": "User has been enabled successfully.",
|
"user_disabled_successfully": "Brugeren blev deaktiveret.",
|
||||||
|
"user_enabled_successfully": "Brugeren blev aktiveret.",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"disable_firstname_lastname": "Disable {firstName} {lastName}",
|
"disable_firstname_lastname": "Deaktiver {firstName} {lastName}",
|
||||||
"are_you_sure_you_want_to_disable_this_user": "Are you sure you want to disable this user? They will not be able to log in or access any services.",
|
"are_you_sure_you_want_to_disable_this_user": "Er du sikker på, at du vil deaktivere denne bruger? Brugeren vil ikke kunne logge ind eller få adgang til tjenester.",
|
||||||
"ldap_soft_delete_users": "Keep disabled users from LDAP.",
|
"ldap_soft_delete_users": "Behold deaktiverede brugere i LDAP.",
|
||||||
"ldap_soft_delete_users_description": "When enabled, users removed from LDAP will be disabled rather than deleted from the system.",
|
"ldap_soft_delete_users_description": "Når aktiveret, vil brugere fjernet fra LDAP blive deaktiveret i stedet for at blive slettet fra systemet.",
|
||||||
"login_code_email_success": "The login code has been sent to the user.",
|
"login_code_email_success": "Loginkoden er sendt til brugeren.",
|
||||||
"send_email": "Send Email",
|
"send_email": "Send e-mail",
|
||||||
"show_code": "Show Code",
|
"show_code": "Vis kode",
|
||||||
"callback_url_description": "URL(s) provided by your client. Will be automatically added if left blank. Wildcards (*) are supported, but best avoided for better security.",
|
"callback_url_description": "En eller flere URL’er angivet af din klient. Tilføjes automatisk, hvis feltet er tomt. Wildcards (*) understøttes, men bør undgås af hensyn til sikkerheden.",
|
||||||
"logout_callback_url_description": "URL(s) provided by your client for logout. Wildcards (*) are supported, but best avoided for better security.",
|
"logout_callback_url_description": "En eller flere URL’er angivet af din klient til logout. Wildcards (*) understøttes, men bør undgås af hensyn til sikkerheden.",
|
||||||
"api_key_expiration": "API Key Expiration",
|
"api_key_expiration": "Udløb af API-nøgle",
|
||||||
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Send an email to the user when their API key is about to expire.",
|
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Send en e-mail til brugeren, når deres API-nøgle er ved at udløbe.",
|
||||||
"authorize_device": "Authorize Device",
|
"authorize_device": "Godkend enhed",
|
||||||
"the_device_has_been_authorized": "The device has been authorized.",
|
"the_device_has_been_authorized": "Enheden er godkendt.",
|
||||||
"enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.",
|
"enter_code_displayed_in_previous_step": "Indtast koden, der blev vist i det forrige trin.",
|
||||||
"authorize": "Authorize",
|
"authorize": "Godkend",
|
||||||
"federated_client_credentials": "Federated Client Credentials",
|
"federated_client_credentials": "Federated klientlegitimationsoplysninger",
|
||||||
"federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.",
|
"federated_client_credentials_description": "Ved hjælp af federated klientlegitimationsoplysninger kan du godkende OIDC-klienter med JWT-tokens udstedt af tredjepartsudbydere.",
|
||||||
"add_federated_client_credential": "Add Federated Client Credential",
|
"add_federated_client_credential": "Tilføj federated klientlegitimation",
|
||||||
"add_another_federated_client_credential": "Add another federated client credential",
|
"add_another_federated_client_credential": "Tilføj endnu en federated klientlegitimation",
|
||||||
"oidc_allowed_group_count": "Allowed Group Count",
|
"oidc_allowed_group_count": "Tilladt antal grupper",
|
||||||
"unrestricted": "Unrestricted",
|
"unrestricted": "Ubegrænset",
|
||||||
"show_advanced_options": "Show Advanced Options",
|
"show_advanced_options": "Vis avancerede indstillinger",
|
||||||
"hide_advanced_options": "Hide Advanced Options",
|
"hide_advanced_options": "Skjul avancerede indstillinger",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "Forhåndsvisning af OIDC-data",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Forhåndsvis OIDC-data, der ville blive sendt for forskellige brugere",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID-token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Adgangstoken",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Brugerinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "ID-token-payload",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Adgangstoken-payload",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Svar fra brugerinfo-endpoint",
|
||||||
"copy": "Copy",
|
"copy": "Kopiér",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "Ingen forhåndsvisningsdata tilgængelig",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "Kopiér alt",
|
||||||
"preview": "Preview",
|
"preview": "Forhåndsvisning",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "Forhåndsvisning for {name} ({email})",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Forhåndsvis OIDC-data, der ville blive sendt for denne bruger",
|
||||||
"show": "Show",
|
"show": "Vis",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Vælg en indstilling",
|
||||||
"select_user": "Select User",
|
"select_user": "Vælg en bruger",
|
||||||
"error": "Error"
|
"error": "Fejl",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Vælg en accentfarve for at tilpasse udseendet af Pocket ID.",
|
||||||
|
"accent_color": "Accentfarve",
|
||||||
|
"custom_accent_color": "Brugerdefineret accentfarve",
|
||||||
|
"custom_accent_color_description": "Indtast en brugerdefineret farve i et gyldigt CSS-format (f.eks. hex, rgb, hsl).",
|
||||||
|
"color_value": "Farveværdi",
|
||||||
|
"apply": "Anvend",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Mein Konto",
|
"my_account": "Mein Konto",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
|
"docs": "Dokumentation",
|
||||||
"key": "Schlüssel",
|
"key": "Schlüssel",
|
||||||
"value": "Wert",
|
"value": "Wert",
|
||||||
"remove_custom_claim": "Benutzerdefinierten Claim entfernen",
|
"remove_custom_claim": "Benutzerdefinierten Claim entfernen",
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
"expiration": "Ablaufdatum",
|
"expiration": "Ablaufdatum",
|
||||||
"generate_code": "Code erzeugen",
|
"generate_code": "Code erzeugen",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"browser_unsupported": "Browser nicht unterstützt",
|
"browser_unsupported": "Browser wird nicht unterstützt",
|
||||||
"this_browser_does_not_support_passkeys": "Dieser Browser unterstützt keine Passkeys. Bitte verwende eine alternative Anmeldemethode.",
|
"this_browser_does_not_support_passkeys": "Dieser Browser unterstützt keine Passkeys. Bitte verwende eine alternative Anmeldemethode.",
|
||||||
"an_unknown_error_occurred": "Ein unbekannter Fehler ist aufgetreten",
|
"an_unknown_error_occurred": "Ein unbekannter Fehler ist aufgetreten",
|
||||||
"authentication_process_was_aborted": "Der Authentifizierungsprozess wurde abgebrochen",
|
"authentication_process_was_aborted": "Der Authentifizierungsprozess wurde abgebrochen",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Möchtest du dich mit deinem Konto <b>{username}</b> von Pocket ID abmelden?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Möchtest du dich mit deinem Konto <b>{username}</b> von Pocket ID abmelden?",
|
||||||
"sign_in_to_appname": "Bei {appName} anmelden",
|
"sign_in_to_appname": "Bei {appName} anmelden",
|
||||||
"please_try_to_sign_in_again": "Bitte versuche dich erneut anzumelden.",
|
"please_try_to_sign_in_again": "Bitte versuche dich erneut anzumelden.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Authentifiziere dich mit deinem Passkey, um auf das Admin Panel zugreifen zu können.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Authentifizieren",
|
"authenticate": "Authentifizieren",
|
||||||
"appname_setup": "{appName} Einrichtung",
|
|
||||||
"please_try_again": "Bitte versuche es noch einmal.",
|
"please_try_again": "Bitte versuche es noch einmal.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Du bist dabei, dich beim initialen Administratorkonto anzumelden. Jeder, der diesen Link hat, kann auf das Konto zugreifen, bis ein Passkey hinzugefügt wird. Bitte richte so schnell wie möglich einen Passkey ein, um unbefugten Zugriff zu verhindern.",
|
|
||||||
"continue": "Fortsetzen",
|
"continue": "Fortsetzen",
|
||||||
"alternative_sign_in": "Alternative Anmeldemethoden",
|
"alternative_sign_in": "Alternative Anmeldemethoden",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Wenn du keinen Zugang zu deinem Passkey hast, kannst du dich mit einer der folgenden Methoden anmelden.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Wenn du keinen Zugang zu deinem Passkey hast, kannst du dich mit einer der folgenden Methoden anmelden.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "E-Mail Benachrichtigung bei Login",
|
"email_login_notification": "E-Mail Benachrichtigung bei Login",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Sende dem Benutzer eine E-Mail, wenn er sich von einem neuen Gerät aus anmeldet.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Sende dem Benutzer eine E-Mail, wenn er sich von einem neuen Gerät aus anmeldet.",
|
||||||
"emai_login_code_requested_by_user": "E-Mail-Logincode angefordert vom Benutzer",
|
"emai_login_code_requested_by_user": "E-Mail-Logincode angefordert vom Benutzer",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Ermöglicht Benutzern, den Passkey zu umgehen, indem sie das Senden eines Logincodes an ihre E-Mail-Adresse anfordern. Dies reduziert die Sicherheit erheblich, da jeder, der Zugriff auf die E-Mail des Benutzers hat, Zugang bekommen kann.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Ermöglicht Benutzern Passkeys zu umgehen, indem sie einen Login-Code anfordern, der an ihre E-Mail gesendet wurde. Dies verringert die Sicherheit erheblich, da jeder, der Zugriff auf die E-Mail des Benutzers hat, Zugang erhalten kann.",
|
||||||
"email_login_code_from_admin": "E-Mail-Logincode von Administratoren",
|
"email_login_code_from_admin": "E-Mail-Logincode von Administratoren",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Erlaube Administratoren das Senden von Logincodes an den Nutzer via E-Mail.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Erlaube Administratoren das Senden von Logincodes an den Nutzer via E-Mail.",
|
||||||
"send_test_email": "Test-E-Mail senden",
|
"send_test_email": "Test-E-Mail senden",
|
||||||
@@ -233,7 +232,7 @@
|
|||||||
"user_deleted_successfully": "Benutzer erfolgreich gelöscht",
|
"user_deleted_successfully": "Benutzer erfolgreich gelöscht",
|
||||||
"role": "Rolle",
|
"role": "Rolle",
|
||||||
"source": "Quelle",
|
"source": "Quelle",
|
||||||
"admin": "Admin",
|
"admin": "Administrator",
|
||||||
"user": "Benutzer",
|
"user": "Benutzer",
|
||||||
"local": "Lokal",
|
"local": "Lokal",
|
||||||
"toggle_menu": "Menü umschalten",
|
"toggle_menu": "Menü umschalten",
|
||||||
@@ -319,6 +318,7 @@
|
|||||||
"all_users": "Alle Benutzer",
|
"all_users": "Alle Benutzer",
|
||||||
"all_events": "Alle Ereignisse",
|
"all_events": "Alle Ereignisse",
|
||||||
"all_clients": "Alle Clients",
|
"all_clients": "Alle Clients",
|
||||||
|
"all_locations": "Alle Orte",
|
||||||
"global_audit_log": "Globaler Aktivitäts-Log",
|
"global_audit_log": "Globaler Aktivitäts-Log",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Sieh dir alle Benutzeraktivitäten der letzten 3 Monate an.",
|
"see_all_account_activities_from_the_last_3_months": "Sieh dir alle Benutzeraktivitäten der letzten 3 Monate an.",
|
||||||
"token_sign_in": "Token-Anmeldung",
|
"token_sign_in": "Token-Anmeldung",
|
||||||
@@ -352,24 +352,71 @@
|
|||||||
"add_another_federated_client_credential": "Add another federated client credential",
|
"add_another_federated_client_credential": "Add another federated client credential",
|
||||||
"oidc_allowed_group_count": "Erlaubte Gruppenanzahl",
|
"oidc_allowed_group_count": "Erlaubte Gruppenanzahl",
|
||||||
"unrestricted": "Uneingeschränkt",
|
"unrestricted": "Uneingeschränkt",
|
||||||
"show_advanced_options": "Show Advanced Options",
|
"show_advanced_options": "Erweiterte Optionen anzeigen",
|
||||||
"hide_advanced_options": "Hide Advanced Options",
|
"hide_advanced_options": "Erweiterte Optionen ausblenden",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "OIDC Daten-Vorschau",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Vorschau der OIDC-Daten, die für verschiedene Nutzer gesendet werden sollen",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID Token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Access Token",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Userinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "ID Token Payload",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Access Token Payload",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Userinfo Endpoint Antwort",
|
||||||
"copy": "Copy",
|
"copy": "Kopieren",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "Keine Vorschaudaten verfügbar",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "Alles kopieren",
|
||||||
"preview": "Preview",
|
"preview": "Vorschau",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "Vorschau für {name} ({email})",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Vorschau der OIDC-Daten, für diesen Benutzer",
|
||||||
"show": "Show",
|
"show": "Anzeigen",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Wähle eine Option",
|
||||||
"select_user": "Select User",
|
"select_user": "Benutzer auswählen",
|
||||||
"error": "Error"
|
"error": "Fehler",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Akzentfarbe",
|
||||||
|
"custom_accent_color": "Benutzerdefinierte Akzentfarbe",
|
||||||
|
"custom_accent_color_description": "Geben Sie eine benutzerdefinierte Farbe mit gültigen CSS-Farbformaten ein (z.B. hex, rgb, hsl).",
|
||||||
|
"color_value": "Farbwert",
|
||||||
|
"apply": "Übernehmen",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Do you want to sign out of {appName} with the account <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Do you want to sign out of {appName} with the account <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Sign in to {appName}",
|
"sign_in_to_appname": "Sign in to {appName}",
|
||||||
"please_try_to_sign_in_again": "Please try to sign in again.",
|
"please_try_to_sign_in_again": "Please try to sign in again.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Authenticate yourself with your passkey to access the admin panel.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Authenticate",
|
"authenticate": "Authenticate",
|
||||||
"appname_setup": "{appName} Setup",
|
|
||||||
"please_try_again": "Please try again.",
|
"please_try_again": "Please try again.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "You're about to sign in to the initial admin account. Anyone with this link can access the account until a passkey is added. Please set up a passkey as soon as possible to prevent unauthorized access.",
|
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"alternative_sign_in": "Alternative Sign In",
|
"alternative_sign_in": "Alternative Sign In",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
||||||
@@ -180,7 +178,7 @@
|
|||||||
"email_login_notification": "Email Login Notification",
|
"email_login_notification": "Email Login Notification",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send an email to the user when they log in from a new device.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send an email to the user when they log in from a new device.",
|
||||||
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Email Login Code from Admin",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
||||||
"send_test_email": "Send test email",
|
"send_test_email": "Send test email",
|
||||||
@@ -310,7 +308,7 @@
|
|||||||
"background_image": "Background Image",
|
"background_image": "Background Image",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"reset_profile_picture_question": "Reset profile picture?",
|
"reset_profile_picture_question": "Reset profile picture?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image, and reset the profile picture to default. Do you want to continue?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
||||||
@@ -320,13 +318,14 @@
|
|||||||
"all_users": "All Users",
|
"all_users": "All Users",
|
||||||
"all_events": "All Events",
|
"all_events": "All Events",
|
||||||
"all_clients": "All Clients",
|
"all_clients": "All Clients",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Global Audit Log",
|
"global_audit_log": "Global Audit Log",
|
||||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
||||||
"token_sign_in": "Token Sign In",
|
"token_sign_in": "Token Sign In",
|
||||||
"client_authorization": "Client Authorization",
|
"client_authorization": "Client Authorization",
|
||||||
"new_client_authorization": "New Client Authorization",
|
"new_client_authorization": "New Client Authorization",
|
||||||
"disable_animations": "Disable Animations",
|
"disable_animations": "Disable Animations",
|
||||||
"turn_off_ui_animations": "Turn off animations troughout the UI.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Account Disabled",
|
"user_disabled": "Account Disabled",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"user_disabled_successfully": "User has been disabled successfully.",
|
||||||
@@ -372,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Mi Cuenta",
|
"my_account": "Mi Cuenta",
|
||||||
"logout": "Cerrar sesión",
|
"logout": "Cerrar sesión",
|
||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
|
"docs": "Docs",
|
||||||
"key": "Clave",
|
"key": "Clave",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"remove_custom_claim": "Eliminar reclamo personalizado",
|
"remove_custom_claim": "Eliminar reclamo personalizado",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "¿Quieres cerrar sesión de Pocket ID con la cuenta <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "¿Quieres cerrar sesión de Pocket ID con la cuenta <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Iniciar sesión en {appName}",
|
"sign_in_to_appname": "Iniciar sesión en {appName}",
|
||||||
"please_try_to_sign_in_again": "Por favor, intente iniciar sesión de nuevo.",
|
"please_try_to_sign_in_again": "Por favor, intente iniciar sesión de nuevo.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Autenticar con tu Passkey para acceder al panel de administración.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Autenticar",
|
"authenticate": "Autenticar",
|
||||||
"appname_setup": "Configuración de {appName}",
|
|
||||||
"please_try_again": "Por favor intente nuevamente.",
|
"please_try_again": "Por favor intente nuevamente.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Estás a punto de iniciar sesión en la cuenta de administrador inicial. Cualquiera con este enlace puede acceder a la cuenta hasta que se agregue un Passkey. Por favor, configure un Passkey lo antes posible para evitar acceso no autorizado.",
|
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"alternative_sign_in": "Inicio de sesión alternativa",
|
"alternative_sign_in": "Inicio de sesión alternativa",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Si no tiene acceso a su Passkey, puede iniciar sesión usando uno de los siguientes métodos.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Si no tiene acceso a su Passkey, puede iniciar sesión usando uno de los siguientes métodos.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "Email Login Notification",
|
"email_login_notification": "Email Login Notification",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Enviar un correo electrónico al usuario cuando inicie sesión desde un dispositivo nuevo.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Enviar un correo electrónico al usuario cuando inicie sesión desde un dispositivo nuevo.",
|
||||||
"emai_login_code_requested_by_user": "Código de acceso solicitado por el usuario",
|
"emai_login_code_requested_by_user": "Código de acceso solicitado por el usuario",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Permite a los usuarios saltarse las claves de acceso solicitando un código de acceso enviado a su correo electrónico. Esto reduce la seguridad significativamente, ya que cualquiera con acceso al correo electrónico del usuario puede obtener acceso.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Email Login Code from Admin",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Permite a un administrador enviar un código de acceso al usuario por correo electrónico.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Permite a un administrador enviar un código de acceso al usuario por correo electrónico.",
|
||||||
"send_test_email": "Enviar correo de prueba",
|
"send_test_email": "Enviar correo de prueba",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "Background Image",
|
"background_image": "Background Image",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"reset_profile_picture_question": "Reset profile picture?",
|
"reset_profile_picture_question": "Reset profile picture?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image, and reset the profile picture to default. Do you want to continue?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "All Users",
|
"all_users": "All Users",
|
||||||
"all_events": "All Events",
|
"all_events": "All Events",
|
||||||
"all_clients": "All Clients",
|
"all_clients": "All Clients",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Global Audit Log",
|
"global_audit_log": "Global Audit Log",
|
||||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
||||||
"token_sign_in": "Token Sign In",
|
"token_sign_in": "Token Sign In",
|
||||||
"client_authorization": "Client Authorization",
|
"client_authorization": "Client Authorization",
|
||||||
"new_client_authorization": "New Client Authorization",
|
"new_client_authorization": "New Client Authorization",
|
||||||
"disable_animations": "Disable Animations",
|
"disable_animations": "Disable Animations",
|
||||||
"turn_off_ui_animations": "Turn off all animations throughout the Admin UI.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Account Disabled",
|
"user_disabled": "Account Disabled",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"user_disabled_successfully": "User has been disabled successfully.",
|
||||||
@@ -371,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Mon compte",
|
"my_account": "Mon compte",
|
||||||
"logout": "Déconnexion",
|
"logout": "Déconnexion",
|
||||||
"confirm": "Confirmer",
|
"confirm": "Confirmer",
|
||||||
|
"docs": "Documentation",
|
||||||
"key": "Clé",
|
"key": "Clé",
|
||||||
"value": "Valeur",
|
"value": "Valeur",
|
||||||
"remove_custom_claim": "Remove custom claim",
|
"remove_custom_claim": "Remove custom claim",
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
"generate_code": "Générer un code",
|
"generate_code": "Générer un code",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"browser_unsupported": "Navigateur non pris en charge",
|
"browser_unsupported": "Navigateur non pris en charge",
|
||||||
"this_browser_does_not_support_passkeys": "This browser doesn't support passkeys. Please use an alternative sign in method.",
|
"this_browser_does_not_support_passkeys": "Ce navigateur ne supporte pas les clés d'accès. Veuillez utiliser une autre méthode d'authentification.",
|
||||||
"an_unknown_error_occurred": "Une erreur inconnue est survenue",
|
"an_unknown_error_occurred": "Une erreur inconnue est survenue",
|
||||||
"authentication_process_was_aborted": "Le processus d'authentification a été interrompu",
|
"authentication_process_was_aborted": "Le processus d'authentification a été interrompu",
|
||||||
"error_occurred_with_authenticator": "Une erreur est survenue pendant l'authentification",
|
"error_occurred_with_authenticator": "Une erreur est survenue pendant l'authentification",
|
||||||
@@ -64,14 +65,12 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Voulez-vous vous déconnecter de Pocket ID avec le compte <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Voulez-vous vous déconnecter de Pocket ID avec le compte <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Se connecter à {appName}",
|
"sign_in_to_appname": "Se connecter à {appName}",
|
||||||
"please_try_to_sign_in_again": "Veuillez essayer de vous connecter à nouveau.",
|
"please_try_to_sign_in_again": "Veuillez essayer de vous connecter à nouveau.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Authentifiez-vous avec votre clé d'accès pour accéder au panneau d'administration.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "S'authentifier",
|
"authenticate": "S'authentifier",
|
||||||
"appname_setup": "Configuration {appName}",
|
|
||||||
"please_try_again": "Veuillez réessayer.",
|
"please_try_again": "Veuillez réessayer.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Vous êtes sur le point de vous connecter au compte administrateur initial. N'importe qui avec ce lien peut accéder au compte jusqu'à ce qu'une clé d'accès soit ajouté. Veuillez configurer une clé d'accès dès que possible pour éviter tout accès non autorisé.",
|
|
||||||
"continue": "Continuer",
|
"continue": "Continuer",
|
||||||
"alternative_sign_in": "Connexion alternative",
|
"alternative_sign_in": "Connexion alternative",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Si vous n'avez pas accès à votre clé d'accès, vous pouvez vous authentifier en utilisant une des méthodes suivantes.",
|
||||||
"use_your_passkey_instead": "Utiliser votre clé d'accès à la place ?",
|
"use_your_passkey_instead": "Utiliser votre clé d'accès à la place ?",
|
||||||
"email_login": "Connexion par e-mail",
|
"email_login": "Connexion par e-mail",
|
||||||
"enter_a_login_code_to_sign_in": "Entrez un code de connexion pour vous connecter.",
|
"enter_a_login_code_to_sign_in": "Entrez un code de connexion pour vous connecter.",
|
||||||
@@ -107,7 +106,7 @@
|
|||||||
"account_settings": "Paramètres du compte",
|
"account_settings": "Paramètres du compte",
|
||||||
"passkey_missing": "Clé d'accès manquante",
|
"passkey_missing": "Clé d'accès manquante",
|
||||||
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Veuillez ajouter une clé d'accès pour éviter de perdre l'accès à votre compte.",
|
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Veuillez ajouter une clé d'accès pour éviter de perdre l'accès à votre compte.",
|
||||||
"single_passkey_configured": "Une seul clé d'accès configuré",
|
"single_passkey_configured": "Une seule clé d'accès configurée",
|
||||||
"it_is_recommended_to_add_more_than_one_passkey": "Il est recommandé d'ajouter plus d'une clé d'accès pour éviter de perdre l'accès à votre compte.",
|
"it_is_recommended_to_add_more_than_one_passkey": "Il est recommandé d'ajouter plus d'une clé d'accès pour éviter de perdre l'accès à votre compte.",
|
||||||
"account_details": "Paramètres du compte",
|
"account_details": "Paramètres du compte",
|
||||||
"passkeys": "Clés d'accès",
|
"passkeys": "Clés d'accès",
|
||||||
@@ -154,7 +153,7 @@
|
|||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"images_updated_successfully": "Image mise à jour avec succès",
|
"images_updated_successfully": "Image mise à jour avec succès",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"configure_smtp_to_send_emails": "Enable email notifications to alert users when a login is detected from a new device or location.",
|
"configure_smtp_to_send_emails": "Activer les notifications par e-mail pour alerter les utilisateurs lorsqu'une connexion est détectée à partir d'un nouvel appareil ou d'un nouvel emplacement.",
|
||||||
"ldap": "LDAP",
|
"ldap": "LDAP",
|
||||||
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Configurer les paramètres LDAP pour synchroniser les utilisateurs et les groupes à partir d'un serveur LDAP.",
|
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Configurer les paramètres LDAP pour synchroniser les utilisateurs et les groupes à partir d'un serveur LDAP.",
|
||||||
"images": "Images",
|
"images": "Images",
|
||||||
@@ -178,11 +177,11 @@
|
|||||||
"enabled_emails": "Emails activés",
|
"enabled_emails": "Emails activés",
|
||||||
"email_login_notification": "Notification de connexion par e-mail",
|
"email_login_notification": "Notification de connexion par e-mail",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Envoyer un email à l'utilisateur lorsqu'il se connecte à partir d'un nouvel appareil.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Envoyer un email à l'utilisateur lorsqu'il se connecte à partir d'un nouvel appareil.",
|
||||||
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
"emai_login_code_requested_by_user": "Code de connexion reçu par e-mail à la demande de l'utilisateur.",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Permet aux utilisateurs de contourner les clés d'accès en demandant un code de connexion envoyé à leur adresse e-mail. Cela réduit considérablement la sécurité car toute personne ayant accès à l'e-mail de l'utilisateur peut récupérer la clé d'accès.",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Code de connexion reçu par e-mail envoyé par l'administrateur.",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Permet à un administrateur d'envoyer un code de connexion à l'utilisateur par e-mail.",
|
||||||
"send_test_email": "Send test email",
|
"send_test_email": "Envoyer un e-mail de test",
|
||||||
"application_configuration_updated_successfully": "Mise à jour de l'application avec succès",
|
"application_configuration_updated_successfully": "Mise à jour de l'application avec succès",
|
||||||
"application_name": "Nom de l'application",
|
"application_name": "Nom de l'application",
|
||||||
"session_duration": "Durée de la session",
|
"session_duration": "Durée de la session",
|
||||||
@@ -269,7 +268,7 @@
|
|||||||
"add_oidc_client": "Ajouter un client OIDC",
|
"add_oidc_client": "Ajouter un client OIDC",
|
||||||
"manage_oidc_clients": "Gérer les clients OIDC",
|
"manage_oidc_clients": "Gérer les clients OIDC",
|
||||||
"one_time_link": "Lien de connexion unique",
|
"one_time_link": "Lien de connexion unique",
|
||||||
"use_this_link_to_sign_in_once": "Use this link to sign in once. This is needed for users who haven't added a passkey yet or have lost it.",
|
"use_this_link_to_sign_in_once": "Utilisez ce lien pour vous connecter. Ceci est nécessaire pour les utilisateurs qui n'ont pas encore ajouté de clé d'accès ou l'ont perdu.",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"callback_urls": "URL de callback",
|
"callback_urls": "URL de callback",
|
||||||
"logout_callback_urls": "URL de callback de déconnexion",
|
"logout_callback_urls": "URL de callback de déconnexion",
|
||||||
@@ -309,67 +308,115 @@
|
|||||||
"background_image": "Image d'arrière-plan",
|
"background_image": "Image d'arrière-plan",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"reset_profile_picture_question": "Réinitialiser la photo de profil ?",
|
"reset_profile_picture_question": "Réinitialiser la photo de profil ?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Cela réinitialisera l'image de profil par défaut. Voulez-vous continuer ?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Cela supprimera l’image téléchargée et réinitialisera la photo de profil par défaut. Voulez-vous continuer ?",
|
||||||
"reset": "Réinitialiser",
|
"reset": "Réinitialiser",
|
||||||
"reset_to_default": "Valeurs par défaut",
|
"reset_to_default": "Valeurs par défaut",
|
||||||
"profile_picture_has_been_reset": "La photo de profil a été réinitialisée. La mise à jour peut prendre quelques minutes.",
|
"profile_picture_has_been_reset": "La photo de profil a été réinitialisée. La mise à jour peut prendre quelques minutes.",
|
||||||
"select_the_language_you_want_to_use": "Sélectionnez la langue que vous souhaitez utiliser. Certaines langues peuvent ne pas être entièrement traduites.",
|
"select_the_language_you_want_to_use": "Sélectionnez la langue que vous souhaitez utiliser. Certaines langues peuvent ne pas être entièrement traduites.",
|
||||||
"personal": "Personal",
|
"personal": "Personnel",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"all_users": "All Users",
|
"all_users": "Tous les utilisateurs",
|
||||||
"all_events": "All Events",
|
"all_events": "Tous les événements",
|
||||||
"all_clients": "All Clients",
|
"all_clients": "Tous les clients",
|
||||||
"global_audit_log": "Global Audit Log",
|
"all_locations": "Tous les emplacements",
|
||||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
"global_audit_log": "Journal d'audit global",
|
||||||
"token_sign_in": "Token Sign In",
|
"see_all_account_activities_from_the_last_3_months": "Voir toutes les activités des utilisateurs des 3 derniers mois.",
|
||||||
"client_authorization": "Client Authorization",
|
"token_sign_in": "Connexion par jeton",
|
||||||
"new_client_authorization": "New Client Authorization",
|
"client_authorization": "Autorisation client",
|
||||||
"disable_animations": "Disable Animations",
|
"new_client_authorization": "Nouvelle autorisation client",
|
||||||
"turn_off_ui_animations": "Turn off all animations throughout the Admin UI.",
|
"disable_animations": "Désactiver les animations",
|
||||||
"user_disabled": "Account Disabled",
|
"turn_off_ui_animations": "Désactiver les animations dans toute l'interface.",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"user_disabled": "Compte désactivé",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"disabled_users_cannot_log_in_or_use_services": "Les utilisateurs désactivés ne peuvent pas se connecter ni utiliser les services.",
|
||||||
"user_enabled_successfully": "User has been enabled successfully.",
|
"user_disabled_successfully": "L'utilisateur a été désactivé avec succès.",
|
||||||
"status": "Status",
|
"user_enabled_successfully": "L'utilisateur a été activé avec succès.",
|
||||||
"disable_firstname_lastname": "Disable {firstName} {lastName}",
|
"status": "Statut",
|
||||||
"are_you_sure_you_want_to_disable_this_user": "Are you sure you want to disable this user? They will not be able to log in or access any services.",
|
"disable_firstname_lastname": "Désactiver {firstName} {lastName}",
|
||||||
"ldap_soft_delete_users": "Keep disabled users from LDAP.",
|
"are_you_sure_you_want_to_disable_this_user": "Êtes-vous sûr de vouloir désactiver cet utilisateur ? Il ne pourra plus se connecter ni accéder aux services.",
|
||||||
"ldap_soft_delete_users_description": "When enabled, users removed from LDAP will be disabled rather than deleted from the system.",
|
"ldap_soft_delete_users": "Conserver les utilisateurs désactivés de LDAP.",
|
||||||
"login_code_email_success": "The login code has been sent to the user.",
|
"ldap_soft_delete_users_description": "Quand activé, les utilisateurs retirés de LDAP seront désactivés plutôt que supprimés du système.",
|
||||||
"send_email": "Send Email",
|
"login_code_email_success": "Le code de connexion a été envoyé à l'utilisateur.",
|
||||||
"show_code": "Show Code",
|
"send_email": "Envoyer un email",
|
||||||
"callback_url_description": "URL(s) provided by your client. Will be automatically added if left blank. Wildcards (*) are supported, but best avoided for better security.",
|
"show_code": "Afficher le code",
|
||||||
"logout_callback_url_description": "URL(s) provided by your client for logout. Wildcards (*) are supported, but best avoided for better security.",
|
"callback_url_description": "URL(s) fournies par votre client. Sera automatiquement ajoutée si laissée vide. Les jokers (*) sont supportés, mais il est préférable de les éviter pour plus de sécurité.",
|
||||||
"api_key_expiration": "API Key Expiration",
|
"logout_callback_url_description": "URL(s) fournies par votre client pour la déconnexion. Les jokers (*) sont supportés, mais il est préférable de les éviter pour plus de sécurité.",
|
||||||
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Send an email to the user when their API key is about to expire.",
|
"api_key_expiration": "Expiration de la clé API",
|
||||||
"authorize_device": "Authorize Device",
|
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Envoyer un email à l'utilisateur lorsque sa clé API est sur le point d'expirer.",
|
||||||
"the_device_has_been_authorized": "The device has been authorized.",
|
"authorize_device": "Autoriser l'appareil",
|
||||||
"enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.",
|
"the_device_has_been_authorized": "L'appareil a été autorisé.",
|
||||||
"authorize": "Authorize",
|
"enter_code_displayed_in_previous_step": "Entrez le code affiché à l'étape précédente.",
|
||||||
"federated_client_credentials": "Federated Client Credentials",
|
"authorize": "Autoriser",
|
||||||
"federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.",
|
"federated_client_credentials": "Identifiants client fédérés",
|
||||||
"add_federated_client_credential": "Add Federated Client Credential",
|
"federated_client_credentials_description": "Avec des identifiants clients fédérés, vous pouvez authentifier des clients OIDC avec des tokens JWT émis par des autorités tierces.",
|
||||||
"add_another_federated_client_credential": "Add another federated client credential",
|
"add_federated_client_credential": "Ajouter un identifiant client fédéré",
|
||||||
"oidc_allowed_group_count": "Allowed Group Count",
|
"add_another_federated_client_credential": "Ajouter un autre identifiant client fédéré",
|
||||||
"unrestricted": "Unrestricted",
|
"oidc_allowed_group_count": "Nombre de groupes autorisés",
|
||||||
"show_advanced_options": "Show Advanced Options",
|
"unrestricted": "Illimité",
|
||||||
"hide_advanced_options": "Hide Advanced Options",
|
"show_advanced_options": "Afficher les options avancées",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"hide_advanced_options": "Masquer les options avancées",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"oidc_data_preview": "Aperçu des données OIDC",
|
||||||
"id_token": "ID Token",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Aperçu des données OIDC qui seraient envoyées pour différents utilisateurs",
|
||||||
"access_token": "Access Token",
|
"id_token": "Jeton ID",
|
||||||
"userinfo": "Userinfo",
|
"access_token": "Jeton d'accès",
|
||||||
"id_token_payload": "ID Token Payload",
|
"userinfo": "Informations utilisateur",
|
||||||
"access_token_payload": "Access Token Payload",
|
"id_token_payload": "Charge utile du jeton ID",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"access_token_payload": "Charge utile du jeton d'accès",
|
||||||
"copy": "Copy",
|
"userinfo_endpoint_response": "Réponse du point d'accès Userinfo",
|
||||||
"no_preview_data_available": "No preview data available",
|
"copy": "Copier",
|
||||||
"copy_all": "Copy All",
|
"no_preview_data_available": "Aucune donnée d'aperçu disponible",
|
||||||
"preview": "Preview",
|
"copy_all": "Tout copier",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview": "Aperçu",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_for_user": "Aperçu pour {name} ({email})",
|
||||||
"show": "Show",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Aperçu des données OIDC qui seraient envoyées pour cet utilisateur",
|
||||||
"select_an_option": "Select an option",
|
"show": "Afficher",
|
||||||
"select_user": "Select User",
|
"select_an_option": "Sélectionner une option",
|
||||||
"error": "Error"
|
"select_user": "Sélectionner un utilisateur",
|
||||||
|
"error": "Erreur",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Sélectionnez une couleur d'accent pour personnaliser l'apparence de Pocket ID.",
|
||||||
|
"accent_color": "Couleur d'accent",
|
||||||
|
"custom_accent_color": "Couleur d'accent personnalisée",
|
||||||
|
"custom_accent_color_description": "Entrez une couleur personnalisée en utilisant un format CSS valide (par ex. hex, rgb, hsl).",
|
||||||
|
"color_value": "Valeur de la couleur",
|
||||||
|
"apply": "Appliquer",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Il mio account",
|
"my_account": "Il mio account",
|
||||||
"logout": "Disconnetti",
|
"logout": "Disconnetti",
|
||||||
"confirm": "Conferma",
|
"confirm": "Conferma",
|
||||||
|
"docs": "Documentazione",
|
||||||
"key": "Chiave",
|
"key": "Chiave",
|
||||||
"value": "Valore",
|
"value": "Valore",
|
||||||
"remove_custom_claim": "Rimuovi attributo personalizzato",
|
"remove_custom_claim": "Rimuovi attributo personalizzato",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Vuoi disconnetterti da Pocket ID con l'account <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Vuoi disconnetterti da Pocket ID con l'account <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Accedi a {appName}",
|
"sign_in_to_appname": "Accedi a {appName}",
|
||||||
"please_try_to_sign_in_again": "Per favore, prova ad accedere di nuovo.",
|
"please_try_to_sign_in_again": "Per favore, prova ad accedere di nuovo.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Autenticati con la tua passkey per accedere al pannello di amministrazione.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Autentica",
|
"authenticate": "Autentica",
|
||||||
"appname_setup": "Configurazione di {appName}",
|
|
||||||
"please_try_again": "Per favore, riprova.",
|
"please_try_again": "Per favore, riprova.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Stai per accedere all'account amministratore iniziale. Chiunque abbia questo link può accedere all'account finché non viene aggiunta una passkey. Configura una passkey il prima possibile per prevenire accessi non autorizzati.",
|
|
||||||
"continue": "Continua",
|
"continue": "Continua",
|
||||||
"alternative_sign_in": "Accesso Alternativo",
|
"alternative_sign_in": "Accesso Alternativo",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Se non hai accesso alla tua passkey, puoi accedere utilizzando uno dei seguenti metodi.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Se non hai accesso alla tua passkey, puoi accedere utilizzando uno dei seguenti metodi.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "Tutti gli utenti",
|
"all_users": "Tutti gli utenti",
|
||||||
"all_events": "Tutti gli eventi",
|
"all_events": "Tutti gli eventi",
|
||||||
"all_clients": "Tutti i client",
|
"all_clients": "Tutti i client",
|
||||||
|
"all_locations": "Tutte le posizioni",
|
||||||
"global_audit_log": "Registro attività globale",
|
"global_audit_log": "Registro attività globale",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Visualizza tutte le attività degli utenti degli ultimi 3 mesi.",
|
"see_all_account_activities_from_the_last_3_months": "Visualizza tutte le attività degli utenti degli ultimi 3 mesi.",
|
||||||
"token_sign_in": "Accesso con token",
|
"token_sign_in": "Accesso con token",
|
||||||
"client_authorization": "Autorizzazione client",
|
"client_authorization": "Autorizzazione client",
|
||||||
"new_client_authorization": "Nuova autorizzazione client",
|
"new_client_authorization": "Nuova autorizzazione client",
|
||||||
"disable_animations": "Disabilita animazioni",
|
"disable_animations": "Disabilita animazioni",
|
||||||
"turn_off_ui_animations": "Disattiva tutte le animazioni nell'interfaccia di amministrazione.",
|
"turn_off_ui_animations": "Disattiva tutte le animazioni della UI.",
|
||||||
"user_disabled": "Account disabilitato",
|
"user_disabled": "Account disabilitato",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Gli utenti disabilitati non possono accedere o utilizzare i servizi.",
|
"disabled_users_cannot_log_in_or_use_services": "Gli utenti disabilitati non possono accedere o utilizzare i servizi.",
|
||||||
"user_disabled_successfully": "Utente disabilitato con successo.",
|
"user_disabled_successfully": "Utente disabilitato con successo.",
|
||||||
@@ -354,22 +354,69 @@
|
|||||||
"unrestricted": "Illimitati",
|
"unrestricted": "Illimitati",
|
||||||
"show_advanced_options": "Mostra Opzioni Avanzate",
|
"show_advanced_options": "Mostra Opzioni Avanzate",
|
||||||
"hide_advanced_options": "Nascondi Opzioni Avanzate",
|
"hide_advanced_options": "Nascondi Opzioni Avanzate",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "Anteprima Dati OIDC",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Anteprima dei dati OIDC che saranno inviati agli utenti",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID Token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Access Token",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Userinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "ID Token Payload",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Access Token Payload",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Risposta Endpoint Userinfo",
|
||||||
"copy": "Copy",
|
"copy": "Copia",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "Dati di anteprima non disponibili",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "Copia tutto",
|
||||||
"preview": "Preview",
|
"preview": "Anteprima",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "Anteprima per {name} ({email})",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Anteprima dei dati OIDC che saranno inviati per l'utente",
|
||||||
"show": "Show",
|
"show": "Mostra",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Seleziona un'opzione",
|
||||||
"select_user": "Select User",
|
"select_user": "Seleziona utente",
|
||||||
"error": "Error"
|
"error": "Errore",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Seleziona un colore in risalto per personalizzare l'aspetto di Pocket ID.",
|
||||||
|
"accent_color": "Colore in Risalto",
|
||||||
|
"custom_accent_color": "Colore in Risalto Personalizzato",
|
||||||
|
"custom_accent_color_description": "Inserisci un colore personalizzato usando formati di colore CSS validi (es: hex, rgb, hsl).",
|
||||||
|
"color_value": "Valore Colore",
|
||||||
|
"apply": "Applica",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Mijn account",
|
"my_account": "Mijn account",
|
||||||
"logout": "Uitloggen",
|
"logout": "Uitloggen",
|
||||||
"confirm": "Bevestigen",
|
"confirm": "Bevestigen",
|
||||||
|
"docs": "Docs",
|
||||||
"key": "Sleutel",
|
"key": "Sleutel",
|
||||||
"value": "Waarde",
|
"value": "Waarde",
|
||||||
"remove_custom_claim": "Aangepaste claim verwijderen",
|
"remove_custom_claim": "Aangepaste claim verwijderen",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Wilt u zich afmelden bij Pocket ID met het account <b>{username}</b> ?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Wilt u zich afmelden bij Pocket ID met het account <b>{username}</b> ?",
|
||||||
"sign_in_to_appname": "Meld u aan bij {appName}",
|
"sign_in_to_appname": "Meld u aan bij {appName}",
|
||||||
"please_try_to_sign_in_again": "Probeer opnieuw in te loggen.",
|
"please_try_to_sign_in_again": "Probeer opnieuw in te loggen.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Verifieer uzelf met uw toegangscode om toegang te krijgen tot het beheerderspaneel.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Authenticeren",
|
"authenticate": "Authenticeren",
|
||||||
"appname_setup": "{appName} Instellen",
|
|
||||||
"please_try_again": "Probeer het opnieuw.",
|
"please_try_again": "Probeer het opnieuw.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "U staat op het punt om in te loggen op het oorspronkelijke beheerdersaccount. Iedereen met deze link heeft toegang tot het account totdat er een passkey is toegevoegd. Stel zo snel mogelijk een passkey in om ongeautoriseerde toegang te voorkomen.",
|
|
||||||
"continue": "Doorgaan",
|
"continue": "Doorgaan",
|
||||||
"alternative_sign_in": "Alternatieve aanmelding",
|
"alternative_sign_in": "Alternatieve aanmelding",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Als u geen toegang hebt tot uw passkeys, kunt u zich op een van de volgende manieren aanmelden.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Als u geen toegang hebt tot uw passkeys, kunt u zich op een van de volgende manieren aanmelden.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "E-mail-inlogmelding",
|
"email_login_notification": "E-mail-inlogmelding",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Stuur een e-mail naar de gebruiker wanneer deze zich aanmeldt vanaf een nieuw apparaat.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Stuur een e-mail naar de gebruiker wanneer deze zich aanmeldt vanaf een nieuw apparaat.",
|
||||||
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Email Login Code from Admin",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
||||||
"send_test_email": "Test-e-mail verzenden",
|
"send_test_email": "Test-e-mail verzenden",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "Achtergrondfoto",
|
"background_image": "Achtergrondfoto",
|
||||||
"language": "Taal",
|
"language": "Taal",
|
||||||
"reset_profile_picture_question": "Profielfoto opnieuw instellen?",
|
"reset_profile_picture_question": "Profielfoto opnieuw instellen?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Dit verwijdert de geüploade afbeelding en de zet de profielfoto terug naar de standaard-profielfoto. Wilt u doorgaan?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"reset_to_default": "Standaardinstellingen herstellen",
|
"reset_to_default": "Standaardinstellingen herstellen",
|
||||||
"profile_picture_has_been_reset": "Profielfoto is gereset. Het kan enkele minuten duren voordat de wijzigingen zichtbaar zijn.",
|
"profile_picture_has_been_reset": "Profielfoto is gereset. Het kan enkele minuten duren voordat de wijzigingen zichtbaar zijn.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "Alle gebruikers",
|
"all_users": "Alle gebruikers",
|
||||||
"all_events": "Alle activiteiten",
|
"all_events": "Alle activiteiten",
|
||||||
"all_clients": "Alle clients",
|
"all_clients": "Alle clients",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Algemeen audit logboek",
|
"global_audit_log": "Algemeen audit logboek",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Bekijk alle gebruikersactiviteit van de afgelopen 3 maanden.",
|
"see_all_account_activities_from_the_last_3_months": "Bekijk alle gebruikersactiviteit van de afgelopen 3 maanden.",
|
||||||
"token_sign_in": "Token Sign In",
|
"token_sign_in": "Token Sign In",
|
||||||
"client_authorization": "Client autorisatie",
|
"client_authorization": "Client autorisatie",
|
||||||
"new_client_authorization": "Nieuwe clientautorisatie",
|
"new_client_authorization": "Nieuwe clientautorisatie",
|
||||||
"disable_animations": "Disable Animations",
|
"disable_animations": "Disable Animations",
|
||||||
"turn_off_ui_animations": "Turn off all animations throughout the Admin UI.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Account Disabled",
|
"user_disabled": "Account Disabled",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"user_disabled_successfully": "User has been disabled successfully.",
|
||||||
@@ -371,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Moje konto",
|
"my_account": "Moje konto",
|
||||||
"logout": "Wyloguj się",
|
"logout": "Wyloguj się",
|
||||||
"confirm": "Potwierdź",
|
"confirm": "Potwierdź",
|
||||||
|
"docs": "Docs",
|
||||||
"key": "Klucz",
|
"key": "Klucz",
|
||||||
"value": "Wartość",
|
"value": "Wartość",
|
||||||
"remove_custom_claim": "Usuń niestandardowy atrybut",
|
"remove_custom_claim": "Usuń niestandardowy atrybut",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Czy chcesz się wylogować z Pocket ID z konta <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Czy chcesz się wylogować z Pocket ID z konta <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Zaloguj się do {appName}",
|
"sign_in_to_appname": "Zaloguj się do {appName}",
|
||||||
"please_try_to_sign_in_again": "Spróbuj zalogować się ponownie.",
|
"please_try_to_sign_in_again": "Spróbuj zalogować się ponownie.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Uwierzytelnij się swoim kluczem, aby uzyskać dostęp do panelu administracyjnego.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Uwierzytelnij",
|
"authenticate": "Uwierzytelnij",
|
||||||
"appname_setup": "Konfiguracja {appName}",
|
|
||||||
"please_try_again": "Spróbuj ponownie.",
|
"please_try_again": "Spróbuj ponownie.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Zaraz zalogujesz się na początkowe konto administratora. Każdy z tym linkiem ma dostęp do konta, dopóki nie zostanie dodany klucz. Dodaj klucz jak najszybciej, aby zapobiec nieautoryzowanemu dostępowi.",
|
|
||||||
"continue": "Kontynuuj",
|
"continue": "Kontynuuj",
|
||||||
"alternative_sign_in": "Alternatywne logowanie",
|
"alternative_sign_in": "Alternatywne logowanie",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Jeśli nie masz dostępu do swojego klucza, możesz zalogować się, używając jednej z następujących metod.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Jeśli nie masz dostępu do swojego klucza, możesz zalogować się, używając jednej z następujących metod.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "Powiadomienie o logowaniu przez e-mail",
|
"email_login_notification": "Powiadomienie o logowaniu przez e-mail",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Wyślij e-mail do użytkownika, gdy zaloguje się z nowego urządzenia.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Wyślij e-mail do użytkownika, gdy zaloguje się z nowego urządzenia.",
|
||||||
"emai_login_code_requested_by_user": "Kod logowania e-mailem zażądany przez użytkownika",
|
"emai_login_code_requested_by_user": "Kod logowania e-mailem zażądany przez użytkownika",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Pozwól użytkownikom zalogować się za pomocą kodu logowania wysłanego na ich e-mail. Znacząco obniża to bezpieczeństwo, ponieważ każdy, kto ma dostęp do e-maila użytkownika, może uzyskać dostęp.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Kod logowania e-mailem od administratora",
|
"email_login_code_from_admin": "Kod logowania e-mailem od administratora",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Pozwala administratorowi wysłać kod logowania do użytkownika za pomocą e-maila.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Pozwala administratorowi wysłać kod logowania do użytkownika za pomocą e-maila.",
|
||||||
"send_test_email": "Wyślij testowy e-mail",
|
"send_test_email": "Wyślij testowy e-mail",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "Obraz tła",
|
"background_image": "Obraz tła",
|
||||||
"language": "Język",
|
"language": "Język",
|
||||||
"reset_profile_picture_question": "Zresetować zdjęcie profilowe?",
|
"reset_profile_picture_question": "Zresetować zdjęcie profilowe?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "To usunie przesłany obraz i zresetuje zdjęcie profilowe do domyślnego. Czy chcesz kontynuować?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Zresetuj",
|
"reset": "Zresetuj",
|
||||||
"reset_to_default": "Zresetuj do domyślnych",
|
"reset_to_default": "Zresetuj do domyślnych",
|
||||||
"profile_picture_has_been_reset": "Zdjęcie profilowe zostało zresetowane. Może to potrwać kilka minut.",
|
"profile_picture_has_been_reset": "Zdjęcie profilowe zostało zresetowane. Może to potrwać kilka minut.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "Wszyscy użytkownicy",
|
"all_users": "Wszyscy użytkownicy",
|
||||||
"all_events": "Wszystkie wydarzenia",
|
"all_events": "Wszystkie wydarzenia",
|
||||||
"all_clients": "Wszyscy klienci",
|
"all_clients": "Wszyscy klienci",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Globalny dziennik audytu",
|
"global_audit_log": "Globalny dziennik audytu",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Zobacz wszystkie działania użytkowników z ostatnich 3 miesięcy.",
|
"see_all_account_activities_from_the_last_3_months": "Zobacz wszystkie działania użytkowników z ostatnich 3 miesięcy.",
|
||||||
"token_sign_in": "Logowanie za pomocą tokena",
|
"token_sign_in": "Logowanie za pomocą tokena",
|
||||||
"client_authorization": "Autoryzacja klienta",
|
"client_authorization": "Autoryzacja klienta",
|
||||||
"new_client_authorization": "Nowa autoryzacja klienta",
|
"new_client_authorization": "Nowa autoryzacja klienta",
|
||||||
"disable_animations": "Wyłącz animacje",
|
"disable_animations": "Wyłącz animacje",
|
||||||
"turn_off_ui_animations": "Wyłącz wszystkie animacje w całym interfejsie administracyjnym.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Konto wyłączone",
|
"user_disabled": "Konto wyłączone",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Wyłączone konta użytkowników nie mogą się logować ani korzystać z usług.",
|
"disabled_users_cannot_log_in_or_use_services": "Wyłączone konta użytkowników nie mogą się logować ani korzystać z usług.",
|
||||||
"user_disabled_successfully": "Sukces! Konto zostało wyłączone.",
|
"user_disabled_successfully": "Sukces! Konto zostało wyłączone.",
|
||||||
@@ -371,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Minha Conta",
|
"my_account": "Minha Conta",
|
||||||
"logout": "Sair",
|
"logout": "Sair",
|
||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
|
"docs": "Docs",
|
||||||
"key": "Chave",
|
"key": "Chave",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"remove_custom_claim": "Remove custom claim",
|
"remove_custom_claim": "Remove custom claim",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Do you want to sign out of Pocket ID with the account <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Do you want to sign out of Pocket ID with the account <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Entrar em {appName}",
|
"sign_in_to_appname": "Entrar em {appName}",
|
||||||
"please_try_to_sign_in_again": "Please try to sign in again.",
|
"please_try_to_sign_in_again": "Please try to sign in again.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Authenticate yourself with your passkey to access the admin panel.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Autenticar",
|
"authenticate": "Autenticar",
|
||||||
"appname_setup": "{appName} Setup",
|
|
||||||
"please_try_again": "Please try again.",
|
"please_try_again": "Please try again.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "You're about to sign in to the initial admin account. Anyone with this link can access the account until a passkey is added. Please set up a passkey as soon as possible to prevent unauthorized access.",
|
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"alternative_sign_in": "Alternative Sign In",
|
"alternative_sign_in": "Alternative Sign In",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "If you don't have access to your passkey, you can sign in using one of the following methods.",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "Email Login Notification",
|
"email_login_notification": "Email Login Notification",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send an email to the user when they log in from a new device.",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Send an email to the user when they log in from a new device.",
|
||||||
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
"emai_login_code_requested_by_user": "Email Login Code Requested by User",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry.",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "Email Login Code from Admin",
|
"email_login_code_from_admin": "Email Login Code from Admin",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "Allows an admin to send a login code to the user via email.",
|
||||||
"send_test_email": "Send test email",
|
"send_test_email": "Send test email",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "Background Image",
|
"background_image": "Background Image",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"reset_profile_picture_question": "Reset profile picture?",
|
"reset_profile_picture_question": "Reset profile picture?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image, and reset the profile picture to default. Do you want to continue?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "Redefinir",
|
"reset": "Redefinir",
|
||||||
"reset_to_default": "Redefinir para o padrão",
|
"reset_to_default": "Redefinir para o padrão",
|
||||||
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "All Users",
|
"all_users": "All Users",
|
||||||
"all_events": "All Events",
|
"all_events": "All Events",
|
||||||
"all_clients": "All Clients",
|
"all_clients": "All Clients",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "Global Audit Log",
|
"global_audit_log": "Global Audit Log",
|
||||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
||||||
"token_sign_in": "Token Sign In",
|
"token_sign_in": "Token Sign In",
|
||||||
"client_authorization": "Client Authorization",
|
"client_authorization": "Client Authorization",
|
||||||
"new_client_authorization": "New Client Authorization",
|
"new_client_authorization": "New Client Authorization",
|
||||||
"disable_animations": "Disable Animations",
|
"disable_animations": "Disable Animations",
|
||||||
"turn_off_ui_animations": "Turn off all animations throughout the Admin UI.",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "Account Disabled",
|
"user_disabled": "Account Disabled",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
"disabled_users_cannot_log_in_or_use_services": "Disabled users cannot log in or use services.",
|
||||||
"user_disabled_successfully": "User has been disabled successfully.",
|
"user_disabled_successfully": "User has been disabled successfully.",
|
||||||
@@ -371,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "Моя учетная запись",
|
"my_account": "Моя учетная запись",
|
||||||
"logout": "Выйти",
|
"logout": "Выйти",
|
||||||
"confirm": "Подтвердить",
|
"confirm": "Подтвердить",
|
||||||
|
"docs": "Документация",
|
||||||
"key": "Ключ",
|
"key": "Ключ",
|
||||||
"value": "Значение",
|
"value": "Значение",
|
||||||
"remove_custom_claim": "Удалить пользовательский claim",
|
"remove_custom_claim": "Удалить пользовательский claim",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Вы хотите выйти из Pocket ID с учетной записью <b>{username}</b>?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "Вы хотите выйти из Pocket ID с учетной записью <b>{username}</b>?",
|
||||||
"sign_in_to_appname": "Вход в {appName}",
|
"sign_in_to_appname": "Вход в {appName}",
|
||||||
"please_try_to_sign_in_again": "Пожалуйста, попробуйте войти снова.",
|
"please_try_to_sign_in_again": "Пожалуйста, попробуйте войти снова.",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "Авторизуйтесь с использованием passkey для доступа к панели администратора.",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "Авторизоваться",
|
"authenticate": "Авторизоваться",
|
||||||
"appname_setup": "Настройка {appName}",
|
|
||||||
"please_try_again": "Пожалуйста, повторите попытку.",
|
"please_try_again": "Пожалуйста, повторите попытку.",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "Вы собираетесь впервые войти в учетную запись администратора. Любой пользователь с этой ссылкой может получить доступ к учетной записи до тех пор, пока не будет добавлен passkey. Пожалуйста, настройте passkey как можно скорее для предотвращения несанкционированного доступа.",
|
|
||||||
"continue": "Продолжить",
|
"continue": "Продолжить",
|
||||||
"alternative_sign_in": "Альтернативный вход",
|
"alternative_sign_in": "Альтернативный вход",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Если у вас нет доступа к вашему passkey, вы можете войти одним из следующих способов.",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Если у вас нет доступа к вашему passkey, вы можете войти одним из следующих способов.",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "Все пользователи",
|
"all_users": "Все пользователи",
|
||||||
"all_events": "Все события",
|
"all_events": "Все события",
|
||||||
"all_clients": "Все клиенты",
|
"all_clients": "Все клиенты",
|
||||||
|
"all_locations": "Все местоположения",
|
||||||
"global_audit_log": "Глобальный журнал аудита",
|
"global_audit_log": "Глобальный журнал аудита",
|
||||||
"see_all_account_activities_from_the_last_3_months": "Смотрите всю активность пользователей за последние 3 месяца.",
|
"see_all_account_activities_from_the_last_3_months": "Смотрите всю активность пользователей за последние 3 месяца.",
|
||||||
"token_sign_in": "Вход с помощью токена",
|
"token_sign_in": "Вход с помощью токена",
|
||||||
"client_authorization": "Авторизация в клиенте",
|
"client_authorization": "Авторизация в клиенте",
|
||||||
"new_client_authorization": "Новая авторизация в клиенте",
|
"new_client_authorization": "Новая авторизация в клиенте",
|
||||||
"disable_animations": "Отключить анимации",
|
"disable_animations": "Отключить анимации",
|
||||||
"turn_off_ui_animations": "Выключить все анимации в интерфейсе администратора.",
|
"turn_off_ui_animations": "Выключить анимации по всему интерфейсу.",
|
||||||
"user_disabled": "Аккаунт отключен",
|
"user_disabled": "Аккаунт отключен",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "Отключенные пользователи не могут войти или использовать сервисы.",
|
"disabled_users_cannot_log_in_or_use_services": "Отключенные пользователи не могут войти или использовать сервисы.",
|
||||||
"user_disabled_successfully": "Пользователь успешно отключен.",
|
"user_disabled_successfully": "Пользователь успешно отключен.",
|
||||||
@@ -346,30 +346,77 @@
|
|||||||
"the_device_has_been_authorized": "Устройство авторизовано.",
|
"the_device_has_been_authorized": "Устройство авторизовано.",
|
||||||
"enter_code_displayed_in_previous_step": "Введите код, который был отображен на предыдущем шаге.",
|
"enter_code_displayed_in_previous_step": "Введите код, который был отображен на предыдущем шаге.",
|
||||||
"authorize": "Авторизируйте",
|
"authorize": "Авторизируйте",
|
||||||
"federated_client_credentials": "Federated Client Credentials",
|
"federated_client_credentials": "Федеративные учетные данные клиента",
|
||||||
"federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.",
|
"federated_client_credentials_description": "Используя федеративные учетные данные клиента, вы можете авторизовывать OIDC клиентов, используя JWT токены, выпущенные третьими сторонами.",
|
||||||
"add_federated_client_credential": "Add Federated Client Credential",
|
"add_federated_client_credential": "Добавить федеративные учетные данные клиента",
|
||||||
"add_another_federated_client_credential": "Add another federated client credential",
|
"add_another_federated_client_credential": "Добавить другие федеративные учетные данные клиента",
|
||||||
"oidc_allowed_group_count": "Кол-во разрешенных групп",
|
"oidc_allowed_group_count": "Кол-во разрешенных групп",
|
||||||
"unrestricted": "Не ограничено",
|
"unrestricted": "Не ограничено",
|
||||||
"show_advanced_options": "Show Advanced Options",
|
"show_advanced_options": "Показать дополнительные опции",
|
||||||
"hide_advanced_options": "Hide Advanced Options",
|
"hide_advanced_options": "Скрыть дополнительные опции",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "Предпросмотр данных OIDC",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Предпросмотр данных OIDC, которые будут отправлены разным пользователям",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID Token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Access Token",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Userinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "Содержимое ID Token",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Содержимое Access Token",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Ответ Userinfo эндпоинта",
|
||||||
"copy": "Copy",
|
"copy": "Копировать",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "Предварительный просмотр данных не доступен",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "Копировать все",
|
||||||
"preview": "Preview",
|
"preview": "Предпросмотр",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "Предпросмотр для {name} ({email})",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Предпросмотр данных OIDC, которые будут отправлены для этого пользователя",
|
||||||
"show": "Show",
|
"show": "Показать",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Выберите опцию",
|
||||||
"select_user": "Select User",
|
"select_user": "Выбрать пользователя",
|
||||||
"error": "Error"
|
"error": "Ошибка",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Выберите цвет акцента, чтобы настроить внешний вид Pocket ID.",
|
||||||
|
"accent_color": "Цвет акцента",
|
||||||
|
"custom_accent_color": "Пользовательский цвет акцента",
|
||||||
|
"custom_accent_color_description": "Введите пользовательский цвет, используя правильные цветовые форматы CSS (например, hex, rgb, hsl).",
|
||||||
|
"color_value": "Значение цвета",
|
||||||
|
"apply": "Применить",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "",
|
||||||
"my_account": "账户",
|
"my_account": "账户",
|
||||||
"logout": "登出",
|
"logout": "登出",
|
||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
|
"docs": "文档",
|
||||||
"key": "Key",
|
"key": "Key",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"remove_custom_claim": "删除自定义声明",
|
"remove_custom_claim": "删除自定义声明",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "您确定要退出 {appName} 应用中的帐号 <b>{username}</b> 吗?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "您确定要退出 {appName} 应用中的帐号 <b>{username}</b> 吗?",
|
||||||
"sign_in_to_appname": "登录到 {appName}",
|
"sign_in_to_appname": "登录到 {appName}",
|
||||||
"please_try_to_sign_in_again": "请尝试重新登录。",
|
"please_try_to_sign_in_again": "请尝试重新登录。",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "使用通行密钥或通过临时登录码进行登录。",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "登录",
|
"authenticate": "登录",
|
||||||
"appname_setup": "{appName} 设置",
|
|
||||||
"please_try_again": "请再试一次。",
|
"please_try_again": "请再试一次。",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "您即将登录到初始管理员账户。在此添加通行密钥之前,任何拥有此链接的人都可以访问该账户。请尽快设置通行密钥以防止未经授权的访问。",
|
|
||||||
"continue": "继续",
|
"continue": "继续",
|
||||||
"alternative_sign_in": "替代登录方式",
|
"alternative_sign_in": "替代登录方式",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "如果您无法使用通行密钥,可以通过以下方式之一登录。",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "如果您无法使用通行密钥,可以通过以下方式之一登录。",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "背景图片",
|
"background_image": "背景图片",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
"reset_profile_picture_question": "重置头像?",
|
"reset_profile_picture_question": "重置头像?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "这将删除已上传的图片,并将头像重置为默认图片。您确定要继续吗?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "这将删除已上传的图片,并将头像重置为默认。您确定要继续吗?",
|
||||||
"reset": "重置",
|
"reset": "重置",
|
||||||
"reset_to_default": "恢复默认设置",
|
"reset_to_default": "恢复默认设置",
|
||||||
"profile_picture_has_been_reset": "头像已重置。可能需要几分钟才能更新。",
|
"profile_picture_has_been_reset": "头像已重置。可能需要几分钟才能更新。",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "所有用户",
|
"all_users": "所有用户",
|
||||||
"all_events": "所有事件",
|
"all_events": "所有事件",
|
||||||
"all_clients": "所有客户端",
|
"all_clients": "所有客户端",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "全局日志",
|
"global_audit_log": "全局日志",
|
||||||
"see_all_account_activities_from_the_last_3_months": "查看过去 3 个月的所有用户活动。",
|
"see_all_account_activities_from_the_last_3_months": "查看过去 3 个月的所有用户活动。",
|
||||||
"token_sign_in": "Token 登录",
|
"token_sign_in": "Token 登录",
|
||||||
"client_authorization": "客户端授权",
|
"client_authorization": "客户端授权",
|
||||||
"new_client_authorization": "首次客户端授权",
|
"new_client_authorization": "首次客户端授权",
|
||||||
"disable_animations": "关闭动画",
|
"disable_animations": "关闭动画",
|
||||||
"turn_off_ui_animations": "关闭管理界面中的所有动画效果。",
|
"turn_off_ui_animations": "关闭界面中的所有动画效果。",
|
||||||
"user_disabled": "账户已禁用",
|
"user_disabled": "账户已禁用",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "禁用的用户无法登录或使用服务。",
|
"disabled_users_cannot_log_in_or_use_services": "禁用的用户无法登录或使用服务。",
|
||||||
"user_disabled_successfully": "用户已成功禁用。",
|
"user_disabled_successfully": "用户已成功禁用。",
|
||||||
@@ -354,22 +354,69 @@
|
|||||||
"unrestricted": "不受限制",
|
"unrestricted": "不受限制",
|
||||||
"show_advanced_options": "显示高级选项",
|
"show_advanced_options": "显示高级选项",
|
||||||
"hide_advanced_options": "隐藏高级选项",
|
"hide_advanced_options": "隐藏高级选项",
|
||||||
"oidc_data_preview": "OIDC Data Preview",
|
"oidc_data_preview": "OIDC 数据预览",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
|
"preview_the_oidc_data_that_would_be_sent_for_different_users": "预览将发送给不同用户的 OIDC 数据",
|
||||||
"id_token": "ID Token",
|
"id_token": "ID Token",
|
||||||
"access_token": "Access Token",
|
"access_token": "Access Token",
|
||||||
"userinfo": "Userinfo",
|
"userinfo": "Userinfo",
|
||||||
"id_token_payload": "ID Token Payload",
|
"id_token_payload": "ID Token 有效载载",
|
||||||
"access_token_payload": "Access Token Payload",
|
"access_token_payload": "Access Token 有效载载",
|
||||||
"userinfo_endpoint_response": "Userinfo Endpoint Response",
|
"userinfo_endpoint_response": "Userinfo 端点响应",
|
||||||
"copy": "Copy",
|
"copy": "复制",
|
||||||
"no_preview_data_available": "No preview data available",
|
"no_preview_data_available": "暂无可用的预览数据",
|
||||||
"copy_all": "Copy All",
|
"copy_all": "全部复制",
|
||||||
"preview": "Preview",
|
"preview": "预览",
|
||||||
"preview_for_user": "Preview for {name} ({email})",
|
"preview_for_user": "为 {name} ({email}) 预览",
|
||||||
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
|
"preview_the_oidc_data_that_would_be_sent_for_this_user": "预览将为此用户发送的 OIDC 数据",
|
||||||
"show": "Show",
|
"show": "显示",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "请选择",
|
||||||
"select_user": "Select User",
|
"select_user": "选择用户",
|
||||||
"error": "Error"
|
"error": "错误",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"my_account": "我的帳號",
|
"my_account": "我的帳號",
|
||||||
"logout": "登出",
|
"logout": "登出",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
|
"docs": "Docs",
|
||||||
"key": "Key",
|
"key": "Key",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"remove_custom_claim": "移除自定義 claim",
|
"remove_custom_claim": "移除自定義 claim",
|
||||||
@@ -64,11 +65,9 @@
|
|||||||
"do_you_want_to_sign_out_of_pocketid_with_the_account": "您確定要使用帳號 <b>{username}</b> 登出 {appName} 嗎?",
|
"do_you_want_to_sign_out_of_pocketid_with_the_account": "您確定要使用帳號 <b>{username}</b> 登出 {appName} 嗎?",
|
||||||
"sign_in_to_appname": "登入 {appName}",
|
"sign_in_to_appname": "登入 {appName}",
|
||||||
"please_try_to_sign_in_again": "請嘗試重新登入。",
|
"please_try_to_sign_in_again": "請嘗試重新登入。",
|
||||||
"authenticate_yourself_with_your_passkey_to_access_the_admin_panel": "請使用您的密碼金鑰進行驗證以存取管理面板。",
|
"authenticate_with_passkey_to_access_account": "Authenticate yourself with your passkey to access your account.",
|
||||||
"authenticate": "驗證",
|
"authenticate": "驗證",
|
||||||
"appname_setup": "{appName} 設定",
|
|
||||||
"please_try_again": "請再試一次。",
|
"please_try_again": "請再試一次。",
|
||||||
"you_are_about_to_sign_in_to_the_initial_admin_account": "您即將登入初始管理員帳號。在新增密碼金鑰之前,任何擁有此連結的人都可以存取該帳號。為避免未經授權的存取,請儘快設定密碼金鑰。",
|
|
||||||
"continue": "繼續",
|
"continue": "繼續",
|
||||||
"alternative_sign_in": "替代登入方式",
|
"alternative_sign_in": "替代登入方式",
|
||||||
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "如果您無法使用您的密碼金鑰,可以改用下列其中一種方式登入。",
|
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "如果您無法使用您的密碼金鑰,可以改用下列其中一種方式登入。",
|
||||||
@@ -179,7 +178,7 @@
|
|||||||
"email_login_notification": "電子郵件登入通知",
|
"email_login_notification": "電子郵件登入通知",
|
||||||
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "使用者從新裝置登入時寄送電子郵件通知。",
|
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "使用者從新裝置登入時寄送電子郵件通知。",
|
||||||
"emai_login_code_requested_by_user": "使用者請求電子郵件登入代碼",
|
"emai_login_code_requested_by_user": "使用者請求電子郵件登入代碼",
|
||||||
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "允許使用者請求一組登入代碼並透過電子郵件接收,藉此繞過密碼金鑰驗證。 這將大幅降低安全性,因為只要取得使用者的信箱存取權,就可能登入系統。",
|
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Allows users to bypass passkeys by requesting a login code sent to their email. This significantly reduces security as anyone with access to the user's email can gain entry.",
|
||||||
"email_login_code_from_admin": "來自管理員的使用者登入代碼",
|
"email_login_code_from_admin": "來自管理員的使用者登入代碼",
|
||||||
"allows_an_admin_to_send_a_login_code_to_the_user": "允許管理員透過電子郵件向使用者發送登入代碼。",
|
"allows_an_admin_to_send_a_login_code_to_the_user": "允許管理員透過電子郵件向使用者發送登入代碼。",
|
||||||
"send_test_email": "發送測試郵件",
|
"send_test_email": "發送測試郵件",
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
"background_image": "背景圖片",
|
"background_image": "背景圖片",
|
||||||
"language": "語言",
|
"language": "語言",
|
||||||
"reset_profile_picture_question": "重設個人資料圖片?",
|
"reset_profile_picture_question": "重設個人資料圖片?",
|
||||||
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "這將會移除已上傳的圖片,並將個人資料圖片重設為預設圖像。是否繼續?",
|
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "This will remove the uploaded image and reset the profile picture to default. Do you want to continue?",
|
||||||
"reset": "重設",
|
"reset": "重設",
|
||||||
"reset_to_default": "重設至預設值",
|
"reset_to_default": "重設至預設值",
|
||||||
"profile_picture_has_been_reset": "個人資料圖片已經重設。 這可能會花幾分鐘更新。",
|
"profile_picture_has_been_reset": "個人資料圖片已經重設。 這可能會花幾分鐘更新。",
|
||||||
@@ -319,13 +318,14 @@
|
|||||||
"all_users": "所有使用者",
|
"all_users": "所有使用者",
|
||||||
"all_events": "所有事件",
|
"all_events": "所有事件",
|
||||||
"all_clients": "所有客戶端",
|
"all_clients": "所有客戶端",
|
||||||
|
"all_locations": "All Locations",
|
||||||
"global_audit_log": "全域稽核日誌",
|
"global_audit_log": "全域稽核日誌",
|
||||||
"see_all_account_activities_from_the_last_3_months": "查看過去 3 個月的所有使用者活動。",
|
"see_all_account_activities_from_the_last_3_months": "查看過去 3 個月的所有使用者活動。",
|
||||||
"token_sign_in": "Token 登入",
|
"token_sign_in": "Token 登入",
|
||||||
"client_authorization": "客戶端授權",
|
"client_authorization": "客戶端授權",
|
||||||
"new_client_authorization": "新客戶端授權",
|
"new_client_authorization": "新客戶端授權",
|
||||||
"disable_animations": "停用動畫",
|
"disable_animations": "停用動畫",
|
||||||
"turn_off_ui_animations": "關閉整個系統中的所有動畫效果。",
|
"turn_off_ui_animations": "Turn off animations throughout the UI.",
|
||||||
"user_disabled": "帳戶已停用",
|
"user_disabled": "帳戶已停用",
|
||||||
"disabled_users_cannot_log_in_or_use_services": "已停用的使用者不能登入或使用服務。",
|
"disabled_users_cannot_log_in_or_use_services": "已停用的使用者不能登入或使用服務。",
|
||||||
"user_disabled_successfully": "使用者已成功停用。",
|
"user_disabled_successfully": "使用者已成功停用。",
|
||||||
@@ -371,5 +371,52 @@
|
|||||||
"show": "Show",
|
"show": "Show",
|
||||||
"select_an_option": "Select an option",
|
"select_an_option": "Select an option",
|
||||||
"select_user": "Select User",
|
"select_user": "Select User",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Select an accent color to customize the appearance of Pocket ID.",
|
||||||
|
"accent_color": "Accent Color",
|
||||||
|
"custom_accent_color": "Custom Accent Color",
|
||||||
|
"custom_accent_color_description": "Enter a custom color using valid CSS color formats (e.g., hex, rgb, hsl).",
|
||||||
|
"color_value": "Color Value",
|
||||||
|
"apply": "Apply",
|
||||||
|
"signup_token": "Signup Token",
|
||||||
|
"create_a_signup_token_to_allow_new_user_registration": "Create a signup token to allow new user registration.",
|
||||||
|
"usage_limit": "Usage Limit",
|
||||||
|
"number_of_times_token_can_be_used": "Number of times the signup token can be used.",
|
||||||
|
"expires": "Expires",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"signup_requires_valid_token": "A valid signup token is required to create an account",
|
||||||
|
"validating_signup_token": "Validating signup token",
|
||||||
|
"go_to_login": "Go to login",
|
||||||
|
"signup_to_appname": "Sign Up to {appName}",
|
||||||
|
"create_your_account_to_get_started": "Create your account to get started.",
|
||||||
|
"initial_account_creation_description": "Please create your account to get started. You will be able to set up a passkey later.",
|
||||||
|
"setup_your_passkey": "Set up your passkey",
|
||||||
|
"create_a_passkey_to_securely_access_your_account": "Create a passkey to securely access your account. This will be your primary way to sign in.",
|
||||||
|
"skip_for_now": "Skip for now",
|
||||||
|
"account_created": "Account Created",
|
||||||
|
"enable_user_signups": "Enable User Signups",
|
||||||
|
"enable_user_signups_description": "Whether the User Signup functionality should be enabled.",
|
||||||
|
"user_signups_are_disabled": "User signups are currently disabled",
|
||||||
|
"create_signup_token": "Create Signup Token",
|
||||||
|
"view_active_signup_tokens": "View Active Signup Tokens",
|
||||||
|
"manage_signup_tokens": "Manage Signup Tokens",
|
||||||
|
"view_and_manage_active_signup_tokens": "View and manage active signup tokens.",
|
||||||
|
"signup_token_deleted_successfully": "Signup token deleted successfully.",
|
||||||
|
"expired": "Expired",
|
||||||
|
"used_up": "Used Up",
|
||||||
|
"active": "Active",
|
||||||
|
"usage": "Usage",
|
||||||
|
"created": "Created",
|
||||||
|
"token": "Token",
|
||||||
|
"loading": "Loading",
|
||||||
|
"delete_signup_token": "Delete Signup Token",
|
||||||
|
"are_you_sure_you_want_to_delete_this_signup_token": "Are you sure you want to delete this signup token? This action cannot be undone.",
|
||||||
|
"signup_disabled_description": "User signups are completely disabled. Only administrators can create new user accounts.",
|
||||||
|
"signup_with_token": "Signup with token",
|
||||||
|
"signup_with_token_description": "Users can only sign up using a valid signup token created by an administrator.",
|
||||||
|
"signup_open": "Open Signup",
|
||||||
|
"signup_open_description": "Anyone can create a new account without restrictions.",
|
||||||
|
"of": "of",
|
||||||
|
"skip_passkey_setup": "Skip Passkey Setup",
|
||||||
|
"skip_passkey_setup_description": "It's highly recommended to set up a passkey because without one, you will be locked out of your account as soon as the session expires."
|
||||||
}
|
}
|
||||||
|
|||||||
282
frontend/package-lock.json
generated
282
frontend/package-lock.json
generated
@@ -1,22 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "1.2.0",
|
"version": "1.4.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "1.2.0",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/svelte": "^0.511.0",
|
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto": "^1.0.1",
|
|
||||||
"jose": "^5.9.6",
|
"jose": "^5.9.6",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"rollup-plugin-visualizer": "^6.0.1",
|
|
||||||
"sveltekit-superforms": "^2.23.1",
|
"sveltekit-superforms": "^2.23.1",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0",
|
||||||
"zod": "^3.25.55"
|
"zod": "^3.25.55"
|
||||||
@@ -25,7 +22,8 @@
|
|||||||
"@inlang/paraglide-js": "^2.0.13",
|
"@inlang/paraglide-js": "^2.0.13",
|
||||||
"@inlang/plugin-m-function-matcher": "^2.0.10",
|
"@inlang/plugin-m-function-matcher": "^2.0.10",
|
||||||
"@inlang/plugin-message-format": "^4.0.0",
|
"@inlang/plugin-message-format": "^4.0.0",
|
||||||
"@internationalized/date": "^3.7.0",
|
"@internationalized/date": "^3.8.2",
|
||||||
|
"@lucide/svelte": "^0.522.0",
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.50.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.8",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/kit": "^2.20.7",
|
"@sveltejs/kit": "^2.20.7",
|
||||||
@@ -33,7 +31,7 @@
|
|||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^22.10.10",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"bits-ui": "^1.5.3",
|
"bits-ui": "^2.8.8",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
@@ -633,21 +631,23 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.6.9",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz",
|
||||||
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
|
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/utils": "^0.2.9"
|
"@floating-ui/utils": "^0.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/dom": {
|
"node_modules/@floating-ui/dom": {
|
||||||
"version": "1.6.13",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz",
|
||||||
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
|
"integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.6.0",
|
"@floating-ui/core": "^1.7.1",
|
||||||
"@floating-ui/utils": "^0.2.9"
|
"@floating-ui/utils": "^0.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -655,7 +655,8 @@
|
|||||||
"version": "0.2.9",
|
"version": "0.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@gcornut/valibot-json-schema": {
|
"node_modules/@gcornut/valibot-json-schema": {
|
||||||
"version": "0.31.0",
|
"version": "0.31.0",
|
||||||
@@ -828,10 +829,11 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@internationalized/date": {
|
"node_modules/@internationalized/date": {
|
||||||
"version": "3.7.0",
|
"version": "3.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz",
|
||||||
"integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==",
|
"integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/helpers": "^0.5.0"
|
"@swc/helpers": "^0.5.0"
|
||||||
}
|
}
|
||||||
@@ -918,9 +920,10 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@lucide/svelte": {
|
"node_modules/@lucide/svelte": {
|
||||||
"version": "0.511.0",
|
"version": "0.522.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.511.0.tgz",
|
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.522.0.tgz",
|
||||||
"integrity": "sha512-aLCSPMUJmHlCuLXzXENXa4Z1NV2mN1iAZAFKk4bEbey+/MdsNlu+/DqwVkgW3Yvj6p8y8Vn5xZ2v9CLmPlA6Vw==",
|
"integrity": "sha512-Q10WkheTfb0sZ6y+EQVTqW4DN8nD2mym00gVpinbe3dJWRW3/R7SeTGF9pW+Ux4PrzkNV9dUdUTWcbO/AdKwQQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^5"
|
"svelte": "^5"
|
||||||
@@ -2075,40 +2078,40 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/bits-ui": {
|
"node_modules/bits-ui": {
|
||||||
"version": "1.5.3",
|
"version": "2.8.8",
|
||||||
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.8.8.tgz",
|
||||||
"integrity": "sha512-BTZ9/GU11DaEGyQp+AY+sXCMLZO0gbDC5J8l7+Ngj4Vf6hNOwrpMmoh5iuKktA6cphXYolVkUDgBWmkh415I+w==",
|
"integrity": "sha512-SFBFztROZf04e/zUsOlsbm4l6Nn5rBKAXS1nTWYJDxgbcFA+8DtjlZgbr03oFEkTcVZ5GAmLDIjbZ/KNg5GFxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.6.4",
|
"@floating-ui/core": "^1.7.1",
|
||||||
"@floating-ui/dom": "^1.6.7",
|
"@floating-ui/dom": "^1.7.1",
|
||||||
"@internationalized/date": "^3.5.6",
|
|
||||||
"esm-env": "^1.1.2",
|
"esm-env": "^1.1.2",
|
||||||
"runed": "^0.23.2",
|
"runed": "^0.28.0",
|
||||||
"svelte-toolbelt": "^0.7.1",
|
"svelte-toolbelt": "^0.9.2",
|
||||||
"tabbable": "^6.2.0"
|
"tabbable": "^6.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=20"
|
||||||
"pnpm": ">=8.7.0"
|
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/huntabyte"
|
"url": "https://github.com/sponsors/huntabyte"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^5.11.0"
|
"@internationalized/date": "^3.8.1",
|
||||||
|
"svelte": "^5.33.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bits-ui/node_modules/runed": {
|
"node_modules/bits-ui/node_modules/runed": {
|
||||||
"version": "0.23.4",
|
"version": "0.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz",
|
"resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz",
|
||||||
"integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==",
|
"integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/huntabyte",
|
"https://github.com/sponsors/huntabyte",
|
||||||
"https://github.com/sponsors/tglide"
|
"https://github.com/sponsors/tglide"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esm-env": "^1.0.0"
|
"esm-env": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -2116,6 +2119,27 @@
|
|||||||
"svelte": "^5.7.0"
|
"svelte": "^5.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bits-ui/node_modules/svelte-toolbelt": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-REb1cENGnFbhNSmIdCb1SDIpjEa3n1kXhNVHqGNEesjmPX3bG87gUZiCG8cqOt9AAarqzTzOtI2jEEWr/ZbHwA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/huntabyte"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"runed": "^0.28.0",
|
||||||
|
"style-to-object": "^1.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18",
|
||||||
|
"pnpm": ">=8.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^5.30.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@@ -2335,12 +2359,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/crypto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
|
|
||||||
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
|
|
||||||
},
|
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -2413,15 +2431,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/define-lazy-prop": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -2546,15 +2555,6 @@
|
|||||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@@ -2764,9 +2764,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esrap": {
|
"node_modules/esrap": {
|
||||||
"version": "1.4.6",
|
"version": "1.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz",
|
||||||
"integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==",
|
"integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
@@ -3175,21 +3175,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-docker": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
|
||||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"is-docker": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -3229,18 +3214,6 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-wsl": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-docker": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
@@ -3837,23 +3810,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/open": {
|
|
||||||
"version": "8.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
|
|
||||||
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"define-lazy-prop": "^2.0.0",
|
|
||||||
"is-docker": "^2.1.1",
|
|
||||||
"is-wsl": "^2.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -4419,112 +4375,6 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup-plugin-visualizer": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-NjlGElvLXCSZSAi3gNRZbfX3qlQbQcJ9TW97c5JpqfVwMhttj9YwEdPwcvbKj91RnMX2PWAjonvSEv6UEYtnRQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"open": "^8.0.0",
|
|
||||||
"picomatch": "^4.0.2",
|
|
||||||
"source-map": "^0.7.4",
|
|
||||||
"yargs": "^17.5.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"rollup-plugin-visualizer": "dist/bin/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"rolldown": "1.x",
|
|
||||||
"rollup": "2.x || 3.x || 4.x"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"rolldown": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"rollup": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/cliui": {
|
|
||||||
"version": "8.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
|
||||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"string-width": "^4.2.0",
|
|
||||||
"strip-ansi": "^6.0.1",
|
|
||||||
"wrap-ansi": "^7.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
|
|
||||||
"version": "0.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
|
||||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/wrap-ansi": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": "^4.0.0",
|
|
||||||
"string-width": "^4.1.0",
|
|
||||||
"strip-ansi": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/y18n": {
|
|
||||||
"version": "5.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/yargs": {
|
|
||||||
"version": "17.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
|
||||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cliui": "^8.0.1",
|
|
||||||
"escalade": "^3.1.1",
|
|
||||||
"get-caller-file": "^2.0.5",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"string-width": "^4.2.3",
|
|
||||||
"y18n": "^5.0.5",
|
|
||||||
"yargs-parser": "^21.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": {
|
|
||||||
"version": "21.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
|
||||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -4742,9 +4592,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "5.31.1",
|
"version": "5.33.18",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.18.tgz",
|
||||||
"integrity": "sha512-09fup3U7NQobUCUJnLhed6pxG6MzUS8rPsALB5Jr8m8u3pVKITs0ejYiKS/wsVjfkXHvKc2g260KA8o7dWypHA==",
|
"integrity": "sha512-GVAhi8vi8pGne/wlEdnfWIJvSR9eKvEknxjfL5Sr8gQALiyk8Ey+H0lhUYLpjW+MrqgH9h4dgh2NF6/BTFprRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.3.0",
|
"@ampproject/remapping": "^2.3.0",
|
||||||
@@ -4756,7 +4606,7 @@
|
|||||||
"axobject-query": "^4.1.0",
|
"axobject-query": "^4.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"esm-env": "^1.2.1",
|
"esm-env": "^1.2.1",
|
||||||
"esrap": "^1.4.6",
|
"esrap": "^1.4.8",
|
||||||
"is-reference": "^3.0.3",
|
"is-reference": "^3.0.3",
|
||||||
"locate-character": "^3.0.0",
|
"locate-character": "^3.0.0",
|
||||||
"magic-string": "^0.30.11",
|
"magic-string": "^0.30.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "1.3.1",
|
"version": "1.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -13,12 +13,10 @@
|
|||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/svelte": "^0.511.0",
|
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto": "^1.0.1",
|
|
||||||
"jose": "^5.9.6",
|
"jose": "^5.9.6",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sveltekit-superforms": "^2.23.1",
|
"sveltekit-superforms": "^2.23.1",
|
||||||
@@ -29,7 +27,8 @@
|
|||||||
"@inlang/paraglide-js": "^2.0.13",
|
"@inlang/paraglide-js": "^2.0.13",
|
||||||
"@inlang/plugin-m-function-matcher": "^2.0.10",
|
"@inlang/plugin-m-function-matcher": "^2.0.10",
|
||||||
"@inlang/plugin-message-format": "^4.0.0",
|
"@inlang/plugin-message-format": "^4.0.0",
|
||||||
"@internationalized/date": "^3.7.0",
|
"@internationalized/date": "^3.8.2",
|
||||||
|
"@lucide/svelte": "^0.522.0",
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.50.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.8",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/kit": "^2.20.7",
|
"@sveltejs/kit": "^2.20.7",
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^22.10.10",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"bits-ui": "^1.5.3",
|
"bits-ui": "^2.8.8",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
|
|||||||
@@ -203,7 +203,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in {
|
.animate-fade-in {
|
||||||
animation: fadeIn 0.8s ease-out forwards;
|
animation: fadeIn 0.3s ease-out forwards;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
animation: slide-bg-container 1.2s cubic-bezier(0.33, 1, 0.68, 1) forwards;
|
animation: slide-bg-container 0.6s cubic-bezier(0.33, 1, 0.68, 1) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes delayed-fade {
|
@keyframes delayed-fade {
|
||||||
@@ -247,5 +247,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.animate-delayed-fade {
|
.animate-delayed-fade {
|
||||||
animation: delayed-fade 1.5s ease-out forwards;
|
animation: delayed-fade 0.5s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<div class="w-full" {...restProps}>
|
<div class="w-full" {...restProps}>
|
||||||
<Popover.Root bind:open>
|
<Popover.Root bind:open>
|
||||||
<Popover.Trigger {id} class="w-full" >
|
<Popover.Trigger {id} class="w-full">
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if input?.error}
|
{#if input?.error}
|
||||||
<p class="text-destructive mt-1 text-xs">{input.error}</p>
|
<p class="text-destructive mt-1 text-xs text-start">{input.error}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as Avatar from '$lib/components/ui/avatar';
|
import * as Avatar from '$lib/components/ui/avatar';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { LucideLoader, LucideRefreshCw, LucideUpload } from '@lucide/svelte';
|
import { LucideLoader, LucideRefreshCw, LucideUpload } from '@lucide/svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { openConfirmDialog } from '../confirm-dialog';
|
import { openConfirmDialog } from '../confirm-dialog';
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
// The "skipCache" query will only be added to the profile picture url on client-side
|
// The "skipCache" query will only be added to the profile picture url on client-side
|
||||||
// because of that we need to set the imageDataURL after the component is mounted
|
// because of that we need to set the imageDataURL after the component is mounted
|
||||||
imageDataURL = getProfilePictureUrl(userId);
|
imageDataURL = cachedProfilePicture.getUrl(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onImageChange(e: Event) {
|
async function onImageChange(e: Event) {
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
await updateCallback(file).catch(() => {
|
await updateCallback(file).catch(() => {
|
||||||
imageDataURL = getProfilePictureUrl(userId);
|
imageDataURL = cachedProfilePicture.getUrl(userId);
|
||||||
});
|
});
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
onSelect,
|
onSelect,
|
||||||
oninput,
|
oninput,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
disableSearch = false,
|
||||||
selectText = m.select_an_option(),
|
selectText = m.select_an_option(),
|
||||||
...restProps
|
...restProps
|
||||||
}: HTMLAttributes<HTMLButtonElement> & {
|
}: HTMLAttributes<HTMLButtonElement> & {
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
oninput?: FormEventHandler<HTMLInputElement>;
|
oninput?: FormEventHandler<HTMLInputElement>;
|
||||||
onSelect?: (value: string) => void;
|
onSelect?: (value: string) => void;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
disableSearch?: boolean;
|
||||||
selectText?: string;
|
selectText?: string;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
@@ -76,13 +78,15 @@
|
|||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
<Popover.Content class="p-0" sameWidth>
|
<Popover.Content class="p-0" sameWidth>
|
||||||
<Command.Root shouldFilter={false}>
|
<Command.Root shouldFilter={false}>
|
||||||
<Command.Input
|
{#if !disableSearch}
|
||||||
placeholder={m.search()}
|
<Command.Input
|
||||||
oninput={(e) => {
|
placeholder={m.search()}
|
||||||
filterItems(e.currentTarget.value);
|
oninput={(e) => {
|
||||||
oninput?.(e);
|
filterItems(e.currentTarget.value);
|
||||||
}}
|
oninput?.(e);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<Command.Empty>
|
<Command.Empty>
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<div class="flex w-full justify-center">
|
<div class="flex w-full justify-center">
|
||||||
|
|||||||
39
frontend/src/lib/components/form/switch-with-label.svelte
Normal file
39
frontend/src/lib/components/form/switch-with-label.svelte
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Label } from '$lib/components/ui/label';
|
||||||
|
import { Switch } from '$lib/components/ui/switch/index.js';
|
||||||
|
|
||||||
|
let {
|
||||||
|
id,
|
||||||
|
checked = $bindable(),
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
onCheckedChange
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
checked: boolean;
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onCheckedChange?: (checked: boolean) => void;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="items-top flex space-x-2">
|
||||||
|
<Switch
|
||||||
|
{id}
|
||||||
|
{disabled}
|
||||||
|
onCheckedChange={(v) => onCheckedChange && onCheckedChange(v == true)}
|
||||||
|
bind:checked
|
||||||
|
/>
|
||||||
|
<div class="grid gap-1.5 leading-none">
|
||||||
|
<Label for={id} class="mb-0 text-sm leading-none font-medium">
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
{#if description}
|
||||||
|
<p class="text-muted-foreground text-[0.8rem]">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -5,21 +5,21 @@
|
|||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import WebAuthnService from '$lib/services/webauthn-service';
|
import WebAuthnService from '$lib/services/webauthn-service';
|
||||||
import userStore from '$lib/stores/user-store';
|
import userStore from '$lib/stores/user-store';
|
||||||
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { LucideLogOut, LucideUser } from '@lucide/svelte';
|
import { LucideLogOut, LucideUser } from '@lucide/svelte';
|
||||||
|
|
||||||
const webauthnService = new WebAuthnService();
|
const webauthnService = new WebAuthnService();
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await webauthnService.logout();
|
await webauthnService.logout();
|
||||||
window.location.reload();
|
goto('/login');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
><Avatar.Root class="size-9">
|
><Avatar.Root class="size-9">
|
||||||
<Avatar.Image src={getProfilePictureUrl($userStore?.id)} />
|
<Avatar.Image src={cachedProfilePicture.getUrl($userStore!.id)} />
|
||||||
</Avatar.Root></DropdownMenu.Trigger
|
</Avatar.Root></DropdownMenu.Trigger
|
||||||
>
|
>
|
||||||
<DropdownMenu.Content class="min-w-40" align="end">
|
<DropdownMenu.Content class="min-w-40" align="end">
|
||||||
|
|||||||
@@ -5,7 +5,13 @@
|
|||||||
import Logo from '../logo.svelte';
|
import Logo from '../logo.svelte';
|
||||||
import HeaderAvatar from './header-avatar.svelte';
|
import HeaderAvatar from './header-avatar.svelte';
|
||||||
|
|
||||||
const authUrls = [/^\/authorize$/, /^\/device$/, /^\/login(?:\/.*)?$/, /^\/logout$/];
|
const authUrls = [
|
||||||
|
/^\/authorize$/,
|
||||||
|
/^\/device$/,
|
||||||
|
/^\/login(?:\/.*)?$/,
|
||||||
|
/^\/logout$/,
|
||||||
|
/^\/signup(?:\/.*)?$/
|
||||||
|
];
|
||||||
|
|
||||||
let isAuthPage = $derived(
|
let isAuthPage = $derived(
|
||||||
!page.error && authUrls.some((pattern) => pattern.test(page.url.pathname))
|
!page.error && authUrls.some((pattern) => pattern.test(page.url.pathname))
|
||||||
|
|||||||
10
frontend/src/lib/components/image-box.svelte
Normal file
10
frontend/src/lib/components/image-box.svelte
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils/style';
|
||||||
|
import type { HTMLImgAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let props: HTMLImgAttributes & {} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={'bg-muted flex items-center justify-center rounded-2xl p-3'}>
|
||||||
|
<img class={cn('size-24 object-contain', props.class)} {...props} />
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import { cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||||
import { cn } from '$lib/utils/style';
|
import { cn } from '$lib/utils/style';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { MediaQuery } from 'svelte/reactivity';
|
import { MediaQuery } from 'svelte/reactivity';
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
{#if showAlternativeSignInMethodButton}
|
{#if showAlternativeSignInMethodButton}
|
||||||
<div
|
<div
|
||||||
class="mb-4 flex items-center justify-center"
|
class="mb-4 flex items-center justify-center"
|
||||||
style={animate ? 'animation-delay: 1000ms;' : ''}
|
style={animate ? 'animation-delay: 500ms;' : ''}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={page.url.pathname == '/login'
|
href={page.url.pathname == '/login'
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
<!-- Background image with slide animation -->
|
<!-- Background image with slide animation -->
|
||||||
<div class="{cn(animate && 'animate-slide-bg-container')} absolute top-0 right-0 bottom-0 z-0">
|
<div class="{cn(animate && 'animate-slide-bg-container')} absolute top-0 right-0 bottom-0 z-0">
|
||||||
<img
|
<img
|
||||||
src="/api/application-configuration/background-image"
|
src={cachedBackgroundImage.getUrl()}
|
||||||
class="h-screen rounded-l-[60px] object-cover {animate
|
class="h-screen rounded-l-[60px] object-cover {animate
|
||||||
? 'w-full'
|
? 'w-full'
|
||||||
: 'w-[calc(100vw-650px)]'}"
|
: 'w-[calc(100vw-650px)]'}"
|
||||||
@@ -64,7 +65,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex h-screen items-center justify-center bg-[url('/api/application-configuration/background-image')] bg-cover bg-center text-center"
|
class="flex h-screen items-center justify-center bg-cover bg-center text-center"
|
||||||
|
style="background-image: url({cachedBackgroundImage.getUrl()});"
|
||||||
>
|
>
|
||||||
<Card.Root class="mx-3 w-full max-w-md" style={animate ? 'animation-delay: 200ms;' : ''}>
|
<Card.Root class="mx-3 w-full max-w-md" style={animate ? 'animation-delay: 200ms;' : ''}>
|
||||||
<Card.CardContent
|
<Card.CardContent
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import { cachedApplicationLogo } from '$lib/utils/cached-image-util';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
|
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
|
||||||
|
|
||||||
const isDarkMode = $derived(mode.current === 'dark');
|
const isLightMode = $derived(mode.current === 'light');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt={m.logo()} />
|
<img {...props} src={cachedApplicationLogo.getUrl(isLightMode)} alt={m.logo()} />
|
||||||
|
|||||||
64
frontend/src/lib/components/signup/signup-form.svelte
Normal file
64
frontend/src/lib/components/signup/signup-form.svelte
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FormInput from '$lib/components/form/form-input.svelte';
|
||||||
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import type { UserSignUp } from '$lib/types/user.type';
|
||||||
|
import { preventDefault } from '$lib/utils/event-util';
|
||||||
|
import { createForm } from '$lib/utils/form-util';
|
||||||
|
import { tryCatch } from '$lib/utils/try-catch-util';
|
||||||
|
import { z } from 'zod/v4';
|
||||||
|
|
||||||
|
let {
|
||||||
|
callback,
|
||||||
|
isLoading
|
||||||
|
}: {
|
||||||
|
callback: (user: UserSignUp) => Promise<boolean>;
|
||||||
|
isLoading: boolean;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const initialData: UserSignUp = {
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
username: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
firstName: z.string().min(1).max(50),
|
||||||
|
lastName: z.string().max(50).optional(),
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(2)
|
||||||
|
.max(30)
|
||||||
|
.regex(/^[a-z0-9_@.-]+$/, m.username_can_only_contain()),
|
||||||
|
email: z.email()
|
||||||
|
});
|
||||||
|
type FormSchema = typeof formSchema;
|
||||||
|
|
||||||
|
const { inputs, ...form } = createForm<FormSchema>(formSchema, initialData);
|
||||||
|
|
||||||
|
let userData: UserSignUp | null = $state(null);
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
const data = form.validate();
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
const result = await tryCatch(callback(data));
|
||||||
|
if (result.data) {
|
||||||
|
userData = data;
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form id="sign-up-form" onsubmit={preventDefault(onSubmit)} class="w-full">
|
||||||
|
<div class="mt-7 space-y-4">
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<FormInput label={m.first_name()} bind:input={$inputs.firstName} />
|
||||||
|
<FormInput label={m.last_name()} bind:input={$inputs.lastName} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormInput label={m.username()} bind:input={$inputs.username} />
|
||||||
|
<FormInput label={m.email()} bind:input={$inputs.email} type="email" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import AdvancedTable from '$lib/components/advanced-table.svelte';
|
||||||
|
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
|
||||||
|
import { Badge, type BadgeVariant } from '$lib/components/ui/badge';
|
||||||
|
import { Button, buttonVariants } from '$lib/components/ui/button';
|
||||||
|
import * as Dialog from '$lib/components/ui/dialog';
|
||||||
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||||
|
import * as Table from '$lib/components/ui/table';
|
||||||
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import UserService from '$lib/services/user-service';
|
||||||
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
|
import type { SignupTokenDto } from '$lib/types/signup-token.type';
|
||||||
|
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||||
|
import { Copy, Ellipsis, Trash2 } from '@lucide/svelte';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
|
let {
|
||||||
|
open = $bindable(),
|
||||||
|
signupTokens = $bindable(),
|
||||||
|
signupTokensRequestOptions,
|
||||||
|
onTokenDeleted
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
signupTokens: Paginated<SignupTokenDto>;
|
||||||
|
signupTokensRequestOptions: SearchPaginationSortRequest;
|
||||||
|
onTokenDeleted?: () => Promise<void>;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const userService = new UserService();
|
||||||
|
|
||||||
|
function formatDate(dateStr: string | undefined) {
|
||||||
|
if (!dateStr) return m.never();
|
||||||
|
return new Date(dateStr).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteToken(token: SignupTokenDto) {
|
||||||
|
openConfirmDialog({
|
||||||
|
title: m.delete_signup_token(),
|
||||||
|
message: m.are_you_sure_you_want_to_delete_this_signup_token(),
|
||||||
|
confirm: {
|
||||||
|
label: m.delete(),
|
||||||
|
destructive: true,
|
||||||
|
action: async () => {
|
||||||
|
try {
|
||||||
|
await userService.deleteSignupToken(token.id);
|
||||||
|
toast.success(m.signup_token_deleted_successfully());
|
||||||
|
|
||||||
|
// Refresh the tokens
|
||||||
|
if (onTokenDeleted) {
|
||||||
|
await onTokenDeleted();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
axiosErrorToast(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenChange(isOpen: boolean) {
|
||||||
|
open = isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTokenExpired(expiresAt: string) {
|
||||||
|
return new Date(expiresAt) < new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTokenUsedUp(token: SignupTokenDto) {
|
||||||
|
return token.usageCount >= token.usageLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenStatus(token: SignupTokenDto) {
|
||||||
|
if (isTokenExpired(token.expiresAt)) return 'expired';
|
||||||
|
if (isTokenUsedUp(token)) return 'used-up';
|
||||||
|
return 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusBadge(status: string): { variant: BadgeVariant; text: string } {
|
||||||
|
switch (status) {
|
||||||
|
case 'expired':
|
||||||
|
return { variant: 'destructive', text: m.expired() };
|
||||||
|
case 'used-up':
|
||||||
|
return { variant: 'secondary', text: m.used_up() };
|
||||||
|
default:
|
||||||
|
return { variant: 'default', text: m.active() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copySignupLink(token: SignupTokenDto) {
|
||||||
|
const signupLink = `${$page.url.origin}/st/${token.token}`;
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(signupLink)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(m.copied());
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
axiosErrorToast(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root {open} {onOpenChange}>
|
||||||
|
<Dialog.Content class="sm-min-w[500px] max-h-[90vh] min-w-[90vw] overflow-auto lg:min-w-[1000px]">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>{m.manage_signup_tokens()}</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
{m.view_and_manage_active_signup_tokens()}
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<AdvancedTable
|
||||||
|
items={signupTokens}
|
||||||
|
requestOptions={signupTokensRequestOptions}
|
||||||
|
withoutSearch={true}
|
||||||
|
onRefresh={async (options) => {
|
||||||
|
const result = await userService.listSignupTokens(options);
|
||||||
|
signupTokens = result;
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{ label: m.token() },
|
||||||
|
{ label: m.status() },
|
||||||
|
{ label: m.usage(), sortColumn: 'usageCount' },
|
||||||
|
{ label: m.expires(), sortColumn: 'expiresAt' },
|
||||||
|
{ label: m.created(), sortColumn: 'createdAt' },
|
||||||
|
{ label: m.actions(), hidden: true }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{#snippet rows({ item })}
|
||||||
|
<Table.Cell class="font-mono text-xs">
|
||||||
|
{item.token.substring(0, 2)}...{item.token.substring(item.token.length - 4)}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{@const status = getTokenStatus(item)}
|
||||||
|
{@const statusBadge = getStatusBadge(status)}
|
||||||
|
<Badge class="rounded-full" variant={statusBadge.variant}>
|
||||||
|
{statusBadge.text}
|
||||||
|
</Badge>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{`${item.usageCount} ${m.of()} ${item.usageLimit}`}
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="text-sm">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{formatDate(item.expiresAt)}
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="text-sm">
|
||||||
|
{formatDate(item.createdAt)}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger class={buttonVariants({ variant: 'ghost', size: 'icon' })}>
|
||||||
|
<Ellipsis class="size-4" />
|
||||||
|
<span class="sr-only">{m.toggle_menu()}</span>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content align="end">
|
||||||
|
<DropdownMenu.Item onclick={() => copySignupLink(item)}>
|
||||||
|
<Copy class="mr-2 size-4" />
|
||||||
|
{m.copy()}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="text-red-500 focus:!text-red-700"
|
||||||
|
onclick={() => deleteToken(item)}
|
||||||
|
>
|
||||||
|
<Trash2 class="mr-2 size-4" />
|
||||||
|
{m.delete()}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</Table.Cell>
|
||||||
|
{/snippet}
|
||||||
|
</AdvancedTable>
|
||||||
|
</div>
|
||||||
|
<Dialog.Footer class="mt-3">
|
||||||
|
<Button onclick={() => (open = false)}>
|
||||||
|
{m.close()}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
138
frontend/src/lib/components/signup/signup-token-modal.svelte
Normal file
138
frontend/src/lib/components/signup/signup-token-modal.svelte
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
|
||||||
|
import Qrcode from '$lib/components/qrcode/qrcode.svelte';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import * as Dialog from '$lib/components/ui/dialog';
|
||||||
|
import { Input } from '$lib/components/ui/input';
|
||||||
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
|
import * as Select from '$lib/components/ui/select/index.js';
|
||||||
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import UserService from '$lib/services/user-service';
|
||||||
|
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||||
|
import { mode } from 'mode-watcher';
|
||||||
|
|
||||||
|
let {
|
||||||
|
open = $bindable(),
|
||||||
|
onTokenCreated
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onTokenCreated?: () => Promise<void>;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const userService = new UserService();
|
||||||
|
|
||||||
|
let signupToken: string | null = $state(null);
|
||||||
|
let signupLink: string | null = $state(null);
|
||||||
|
let selectedExpiration: keyof typeof availableExpirations = $state(m.one_day());
|
||||||
|
let usageLimit: number = $state(1);
|
||||||
|
|
||||||
|
let availableExpirations = {
|
||||||
|
[m.one_hour()]: 60 * 60,
|
||||||
|
[m.twelve_hours()]: 60 * 60 * 12,
|
||||||
|
[m.one_day()]: 60 * 60 * 24,
|
||||||
|
[m.one_week()]: 60 * 60 * 24 * 7,
|
||||||
|
[m.one_month()]: 60 * 60 * 24 * 30
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createSignupToken() {
|
||||||
|
try {
|
||||||
|
const expiration = new Date(Date.now() + availableExpirations[selectedExpiration] * 1000);
|
||||||
|
signupToken = await userService.createSignupToken(expiration, usageLimit);
|
||||||
|
signupLink = `${page.url.origin}/st/${signupToken}`;
|
||||||
|
|
||||||
|
if (onTokenCreated) {
|
||||||
|
await onTokenCreated();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
axiosErrorToast(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenChange(isOpen: boolean) {
|
||||||
|
open = isOpen;
|
||||||
|
if (!isOpen) {
|
||||||
|
signupToken = null;
|
||||||
|
signupLink = null;
|
||||||
|
selectedExpiration = m.one_day();
|
||||||
|
usageLimit = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root {open} {onOpenChange}>
|
||||||
|
<Dialog.Content class="max-w-md">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>{m.signup_token()}</Dialog.Title>
|
||||||
|
<Dialog.Description
|
||||||
|
>{m.create_a_signup_token_to_allow_new_user_registration()}</Dialog.Description
|
||||||
|
>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
{#if signupToken === null}
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label for="expiration">{m.expiration()}</Label>
|
||||||
|
<Select.Root
|
||||||
|
type="single"
|
||||||
|
value={Object.keys(availableExpirations)[0]}
|
||||||
|
onValueChange={(v) => (selectedExpiration = v! as keyof typeof availableExpirations)}
|
||||||
|
>
|
||||||
|
<Select.Trigger id="expiration" class="h-9 w-full">
|
||||||
|
{selectedExpiration}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
{#each Object.keys(availableExpirations) as key}
|
||||||
|
<Select.Item value={key}>{key}</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label class="mb-0" for="usage-limit">{m.usage_limit()}</Label>
|
||||||
|
<p class="text-muted-foreground mt-1 mb-2 text-xs">
|
||||||
|
{m.number_of_times_token_can_be_used()}
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
id="usage-limit"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="100"
|
||||||
|
bind:value={usageLimit}
|
||||||
|
class="h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer class="mt-4">
|
||||||
|
<Button
|
||||||
|
onclick={() => createSignupToken()}
|
||||||
|
disabled={!selectedExpiration || usageLimit < 1}
|
||||||
|
>
|
||||||
|
{m.create()}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<Qrcode
|
||||||
|
class="mb-2"
|
||||||
|
value={signupLink}
|
||||||
|
size={180}
|
||||||
|
color={mode.current === 'dark' ? '#FFFFFF' : '#000000'}
|
||||||
|
backgroundColor={mode.current === 'dark' ? '#000000' : '#FFFFFF'}
|
||||||
|
/>
|
||||||
|
<CopyToClipboard value={signupLink!}>
|
||||||
|
<p data-testId="signup-token-link" class="px-2 text-center text-sm break-all">
|
||||||
|
{signupLink!}
|
||||||
|
</p>
|
||||||
|
</CopyToClipboard>
|
||||||
|
|
||||||
|
<div class="text-muted-foreground mt-2 text-center text-sm">
|
||||||
|
<p>{m.usage_limit()}: {usageLimit}</p>
|
||||||
|
<p>{m.expiration()}: {selectedExpiration}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
@@ -37,11 +37,13 @@
|
|||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
autofocus?: boolean;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
|
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
class: className,
|
class: className,
|
||||||
@@ -52,9 +54,18 @@
|
|||||||
type = 'button',
|
type = 'button',
|
||||||
disabled,
|
disabled,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
autofocus = false,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: ButtonProps = $props();
|
}: ButtonProps = $props();
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// Using autofocus can be bad for a11y, but in the case of Pocket ID is only used responsibly in places where there are not many choices, and on buttons only where there's descriptive text
|
||||||
|
if (autofocus) {
|
||||||
|
// Use setTimeout to make sure the element is showing
|
||||||
|
setTimeout(() => ref?.focus(), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if href}
|
{#if href}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn } from '$lib/utils/style.js';
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||||
|
|
||||||
|
export type DropdownButtonContentProps = DropdownMenuPrimitive.ContentProps;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
sideOffset = 4,
|
||||||
|
portalProps,
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.ContentProps & {
|
||||||
|
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
{sideOffset}
|
||||||
|
class={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md outline-none',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<DropdownMenuPrimitive.Arrow />
|
||||||
|
{@render children?.()}
|
||||||
|
</DropdownMenuPrimitive.Content>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils/style.js';
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.ItemProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
bind:ref
|
||||||
|
class={cn(
|
||||||
|
'data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize
|
||||||
|
} from '$lib/components/ui/button/button.svelte';
|
||||||
|
|
||||||
|
export type DropdownButtonMainProps = WithElementRef<HTMLButtonAttributes> & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
class: className,
|
||||||
|
variant = 'default',
|
||||||
|
size = 'default',
|
||||||
|
ref = $bindable(null),
|
||||||
|
type = 'button',
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DropdownButtonMainProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dropdown-button-main"
|
||||||
|
class={cn(buttonVariants({ variant, size }), 'rounded-r-none border-r-0', className)}
|
||||||
|
{type}
|
||||||
|
{disabled}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn } from '$lib/utils/style.js';
|
||||||
|
|
||||||
|
export type DropdownButtonSeparatorProps = DropdownMenuPrimitive.SeparatorProps;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
bind:ref
|
||||||
|
class={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize
|
||||||
|
} from '$lib/components/ui/button/button.svelte';
|
||||||
|
|
||||||
|
export type DropdownButtonTriggerProps = WithElementRef<HTMLButtonAttributes> & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
builders?: any[];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||||
|
|
||||||
|
let {
|
||||||
|
class: className,
|
||||||
|
variant = 'default',
|
||||||
|
size = 'default',
|
||||||
|
ref = $bindable(null),
|
||||||
|
type = 'button',
|
||||||
|
disabled,
|
||||||
|
builders = [],
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DropdownButtonTriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
bind:this={ref}
|
||||||
|
use:builders[0]
|
||||||
|
data-slot="dropdown-button-trigger"
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant, size }),
|
||||||
|
'border-l-background/20 rounded-l-none border-l px-2',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{type}
|
||||||
|
{disabled}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{:else}
|
||||||
|
<ChevronDown class="size-4" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
export type DropdownButtonProps = WithElementRef<HTMLAttributes<HTMLDivElement>>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
class: className,
|
||||||
|
ref = $bindable(null),
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DropdownButtonProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={ref} data-slot="dropdown-button" class={cn('flex', className)} {...restProps}>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
30
frontend/src/lib/components/ui/dropdown-button/index.ts
Normal file
30
frontend/src/lib/components/ui/dropdown-button/index.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||||
|
import Root from './dropdown-button.svelte';
|
||||||
|
import Main from './dropdown-button-main.svelte';
|
||||||
|
import Trigger from './dropdown-button-trigger.svelte';
|
||||||
|
import Content from './dropdown-button-content.svelte';
|
||||||
|
import Item from './dropdown-button-item.svelte';
|
||||||
|
import Separator from './dropdown-button-separator.svelte';
|
||||||
|
|
||||||
|
const DropdownRoot = DropdownMenuPrimitive.Root;
|
||||||
|
const DropdownTrigger = DropdownMenuPrimitive.Trigger;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Main,
|
||||||
|
Trigger,
|
||||||
|
Content,
|
||||||
|
Item,
|
||||||
|
Separator,
|
||||||
|
DropdownRoot,
|
||||||
|
DropdownTrigger,
|
||||||
|
//
|
||||||
|
Root as DropdownButton,
|
||||||
|
Main as DropdownButtonMain,
|
||||||
|
Trigger as DropdownButtonTrigger,
|
||||||
|
Content as DropdownButtonContent,
|
||||||
|
Item as DropdownButtonItem,
|
||||||
|
Separator as DropdownButtonSeparator,
|
||||||
|
DropdownRoot as DropdownButtonRoot,
|
||||||
|
DropdownTrigger as DropdownButtonPrimitiveTrigger
|
||||||
|
};
|
||||||
10
frontend/src/lib/components/ui/radio-group/index.ts
Normal file
10
frontend/src/lib/components/ui/radio-group/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Root from './radio-group.svelte';
|
||||||
|
import Item from './radio-group-item.svelte';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Item,
|
||||||
|
//
|
||||||
|
Root as RadioGroup,
|
||||||
|
Item as RadioGroupItem
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||||
|
import CircleIcon from '@lucide/svelte/icons/circle';
|
||||||
|
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
bind:ref
|
||||||
|
data-slot="radio-group-item"
|
||||||
|
class={cn(
|
||||||
|
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{#snippet children({ checked })}
|
||||||
|
<div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
|
||||||
|
{#if checked}
|
||||||
|
<CircleIcon
|
||||||
|
class="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||||
|
import { cn } from '$lib/utils/style.js';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
value = $bindable(''),
|
||||||
|
...restProps
|
||||||
|
}: RadioGroupPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
bind:value
|
||||||
|
data-slot="radio-group"
|
||||||
|
class={cn('grid gap-3', className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
frontend/src/lib/components/ui/switch/index.ts
Normal file
7
frontend/src/lib/components/ui/switch/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Root from './switch.svelte';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Switch
|
||||||
|
};
|
||||||
29
frontend/src/lib/components/ui/switch/switch.svelte
Normal file
29
frontend/src/lib/components/ui/switch/switch.svelte
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Switch as SwitchPrimitive } from 'bits-ui';
|
||||||
|
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
checked = $bindable(false),
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SwitchPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
bind:checked
|
||||||
|
data-slot="switch"
|
||||||
|
class={cn(
|
||||||
|
'data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<SwitchPrimitive.Thumb
|
||||||
|
data-slot="switch-thumb"
|
||||||
|
class={cn(
|
||||||
|
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitive.Root>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
|
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
|
||||||
|
import { cachedApplicationLogo, cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
export default class AppConfigService extends APIService {
|
export default class AppConfigService extends APIService {
|
||||||
@@ -36,6 +37,7 @@ export default class AppConfigService extends APIService {
|
|||||||
await this.api.put(`/application-configuration/logo`, formData, {
|
await this.api.put(`/application-configuration/logo`, formData, {
|
||||||
params: { light }
|
params: { light }
|
||||||
});
|
});
|
||||||
|
cachedApplicationLogo.bustCache(light);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateBackgroundImage(backgroundImage: File) {
|
async updateBackgroundImage(backgroundImage: File) {
|
||||||
@@ -43,6 +45,7 @@ export default class AppConfigService extends APIService {
|
|||||||
formData.append('file', backgroundImage!);
|
formData.append('file', backgroundImage!);
|
||||||
|
|
||||||
await this.api.put(`/application-configuration/background-image`, formData);
|
await this.api.put(`/application-configuration/background-image`, formData);
|
||||||
|
cachedBackgroundImage.bustCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTestEmail() {
|
async sendTestEmail() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
OidcDeviceCodeInfo
|
OidcDeviceCodeInfo
|
||||||
} from '$lib/types/oidc.type';
|
} from '$lib/types/oidc.type';
|
||||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
class OidcService extends APIService {
|
class OidcService extends APIService {
|
||||||
@@ -80,10 +81,12 @@ class OidcService extends APIService {
|
|||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
await this.api.post(`/oidc/clients/${client.id}/logo`, formData);
|
await this.api.post(`/oidc/clients/${client.id}/logo`, formData);
|
||||||
|
cachedOidcClientLogo.bustCache(client.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeClientLogo(id: string) {
|
async removeClientLogo(id: string) {
|
||||||
await this.api.delete(`/oidc/clients/${id}/logo`);
|
await this.api.delete(`/oidc/clients/${id}/logo`);
|
||||||
|
cachedOidcClientLogo.bustCache(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createClientSecret(id: string) {
|
async createClientSecret(id: string) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import userStore from '$lib/stores/user-store';
|
import userStore from '$lib/stores/user-store';
|
||||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
|
import type { SignupTokenDto } from '$lib/types/signup-token.type';
|
||||||
import type { UserGroup } from '$lib/types/user-group.type';
|
import type { UserGroup } from '$lib/types/user-group.type';
|
||||||
import type { User, UserCreate } from '$lib/types/user.type';
|
import type { User, UserCreate, UserSignUp } from '$lib/types/user.type';
|
||||||
import { bustProfilePictureCache } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
@@ -52,26 +53,26 @@ export default class UserService extends APIService {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
bustProfilePictureCache(userId);
|
|
||||||
await this.api.put(`/users/${userId}/profile-picture`, formData);
|
await this.api.put(`/users/${userId}/profile-picture`, formData);
|
||||||
|
cachedProfilePicture.bustCache(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCurrentUsersProfilePicture(image: File) {
|
async updateCurrentUsersProfilePicture(image: File) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
bustProfilePictureCache(get(userStore)!.id);
|
|
||||||
await this.api.put('/users/me/profile-picture', formData);
|
await this.api.put('/users/me/profile-picture', formData);
|
||||||
|
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetCurrentUserProfilePicture() {
|
async resetCurrentUserProfilePicture() {
|
||||||
bustProfilePictureCache(get(userStore)!.id);
|
|
||||||
await this.api.delete(`/users/me/profile-picture`);
|
await this.api.delete(`/users/me/profile-picture`);
|
||||||
|
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetProfilePicture(userId: string) {
|
async resetProfilePicture(userId: string) {
|
||||||
bustProfilePictureCache(userId);
|
|
||||||
await this.api.delete(`/users/${userId}/profile-picture`);
|
await this.api.delete(`/users/${userId}/profile-picture`);
|
||||||
|
cachedProfilePicture.bustCache(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOneTimeAccessToken(expiresAt: Date, userId: string) {
|
async createOneTimeAccessToken(expiresAt: Date, userId: string) {
|
||||||
@@ -82,6 +83,14 @@ export default class UserService extends APIService {
|
|||||||
return res.data.token;
|
return res.data.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createSignupToken(expiresAt: Date, usageLimit: number) {
|
||||||
|
const res = await this.api.post(`/signup-tokens`, {
|
||||||
|
expiresAt,
|
||||||
|
usageLimit
|
||||||
|
});
|
||||||
|
return res.data.token;
|
||||||
|
}
|
||||||
|
|
||||||
async exchangeOneTimeAccessToken(token: string) {
|
async exchangeOneTimeAccessToken(token: string) {
|
||||||
const res = await this.api.post(`/one-time-access-token/${token}`);
|
const res = await this.api.post(`/one-time-access-token/${token}`);
|
||||||
return res.data as User;
|
return res.data as User;
|
||||||
@@ -99,4 +108,25 @@ export default class UserService extends APIService {
|
|||||||
const res = await this.api.put(`/users/${id}/user-groups`, { userGroupIds });
|
const res = await this.api.put(`/users/${id}/user-groups`, { userGroupIds });
|
||||||
return res.data as User;
|
return res.data as User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signup(data: UserSignUp) {
|
||||||
|
const res = await this.api.post(`/signup`, data);
|
||||||
|
return res.data as User;
|
||||||
|
}
|
||||||
|
|
||||||
|
async signupInitialUser(data: UserSignUp) {
|
||||||
|
const res = await this.api.post(`/signup/setup`, data);
|
||||||
|
return res.data as User;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listSignupTokens(options?: SearchPaginationSortRequest) {
|
||||||
|
const res = await this.api.get('/signup-tokens', {
|
||||||
|
params: options
|
||||||
|
});
|
||||||
|
return res.data as Paginated<SignupTokenDto>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSignupToken(tokenId: string) {
|
||||||
|
await this.api.delete(`/signup-tokens/${tokenId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import AppConfigService from '$lib/services/app-config-service';
|
import AppConfigService from '$lib/services/app-config-service';
|
||||||
import type { AppConfig } from '$lib/types/application-configuration';
|
import type { AppConfig } from '$lib/types/application-configuration';
|
||||||
|
import { applyAccentColor } from '$lib/utils/accent-color-util';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
const appConfigStore = writable<AppConfig>();
|
const appConfigStore = writable<AppConfig>();
|
||||||
@@ -8,10 +9,11 @@ const appConfigService = new AppConfigService();
|
|||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
const appConfig = await appConfigService.list();
|
const appConfig = await appConfigService.list();
|
||||||
appConfigStore.set(appConfig);
|
set(appConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
const set = (appConfig: AppConfig) => {
|
const set = (appConfig: AppConfig) => {
|
||||||
|
applyAccentColor(appConfig.accentColor);
|
||||||
appConfigStore.set(appConfig);
|
appConfigStore.set(appConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
export type AppConfig = {
|
export type AppConfig = {
|
||||||
appName: string;
|
appName: string;
|
||||||
allowOwnAccountEdit: boolean;
|
allowOwnAccountEdit: boolean;
|
||||||
|
allowUserSignups: 'disabled' | 'withToken' | 'open';
|
||||||
emailOneTimeAccessAsUnauthenticatedEnabled: boolean;
|
emailOneTimeAccessAsUnauthenticatedEnabled: boolean;
|
||||||
emailOneTimeAccessAsAdminEnabled: boolean;
|
emailOneTimeAccessAsAdminEnabled: boolean;
|
||||||
ldapEnabled: boolean;
|
ldapEnabled: boolean;
|
||||||
disableAnimations: boolean;
|
disableAnimations: boolean;
|
||||||
uiConfigDisabled: boolean;
|
uiConfigDisabled: boolean;
|
||||||
|
accentColor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AllAppConfig = AppConfig & {
|
export type AllAppConfig = AppConfig & {
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ export type AuditLog = {
|
|||||||
export type AuditLogFilter = {
|
export type AuditLogFilter = {
|
||||||
userId: string;
|
userId: string;
|
||||||
event: string;
|
event: string;
|
||||||
|
location: string;
|
||||||
clientName: string;
|
clientName: string;
|
||||||
};
|
};
|
||||||
|
|||||||
8
frontend/src/lib/types/signup-token.type.ts
Normal file
8
frontend/src/lib/types/signup-token.type.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SignupTokenDto {
|
||||||
|
id: string;
|
||||||
|
token: string;
|
||||||
|
expiresAt: string;
|
||||||
|
usageLimit: number;
|
||||||
|
usageCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
@@ -17,3 +17,7 @@ export type User = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UserCreate = Omit<User, 'id' | 'customClaims' | 'ldapId' | 'userGroups'>;
|
export type UserCreate = Omit<User, 'id' | 'customClaims' | 'ldapId' | 'userGroups'>;
|
||||||
|
|
||||||
|
export type UserSignUp = Omit<UserCreate, 'isAdmin' | 'disabled'> & {
|
||||||
|
token?: string;
|
||||||
|
};
|
||||||
|
|||||||
58
frontend/src/lib/utils/accent-color-util.ts
Normal file
58
frontend/src/lib/utils/accent-color-util.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
export function applyAccentColor(accentValue: string) {
|
||||||
|
if (accentValue === 'default') {
|
||||||
|
document.documentElement.style.removeProperty('--primary');
|
||||||
|
document.documentElement.style.removeProperty('--primary-foreground');
|
||||||
|
document.documentElement.style.removeProperty('--ring');
|
||||||
|
document.documentElement.style.removeProperty('--sidebar-ring');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--primary', accentValue);
|
||||||
|
|
||||||
|
// Smart foreground color selection based on brightness
|
||||||
|
const foregroundColor = getContrastingForeground(accentValue);
|
||||||
|
document.documentElement.style.setProperty('--primary-foreground', foregroundColor);
|
||||||
|
|
||||||
|
// Create proper ring colors based on input format
|
||||||
|
const ringColor = `color-mix(in srgb, ${accentValue} 50%, transparent)`;
|
||||||
|
document.documentElement.style.setProperty('--ring', ringColor);
|
||||||
|
document.documentElement.style.setProperty('--sidebar-ring', ringColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContrastingForeground(color: string): string {
|
||||||
|
const brightness = getColorBrightness(color);
|
||||||
|
|
||||||
|
// Use white text for dark colors, black text for light colors
|
||||||
|
return brightness < 0.55 ? 'oklch(0.98 0 0)' : 'oklch(0.09 0 0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorBrightness(color: string): number {
|
||||||
|
// Create a temporary element to get computed color
|
||||||
|
const tempElement = document.createElement('div');
|
||||||
|
tempElement.style.color = color;
|
||||||
|
document.body.appendChild(tempElement);
|
||||||
|
|
||||||
|
const computedColor = window.getComputedStyle(tempElement).color;
|
||||||
|
document.body.removeChild(tempElement);
|
||||||
|
|
||||||
|
// Parse RGB values from computed color
|
||||||
|
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
||||||
|
if (!rgbMatch) {
|
||||||
|
// Fallback: assume medium brightness
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, r, g, b] = rgbMatch.map(Number);
|
||||||
|
|
||||||
|
// Calculate relative luminance using the standard formula
|
||||||
|
// https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
|
||||||
|
const sR = r / 255;
|
||||||
|
const sG = g / 255;
|
||||||
|
const sB = b / 255;
|
||||||
|
|
||||||
|
const rLinear = sR <= 0.03928 ? sR / 12.92 : Math.pow((sR + 0.055) / 1.055, 2.4);
|
||||||
|
const gLinear = sG <= 0.03928 ? sG / 12.92 : Math.pow((sG + 0.055) / 1.055, 2.4);
|
||||||
|
const bLinear = sB <= 0.03928 ? sB / 12.92 : Math.pow((sB + 0.055) / 1.055, 2.4);
|
||||||
|
|
||||||
|
return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
|
||||||
|
}
|
||||||
89
frontend/src/lib/utils/cached-image-util.ts
Normal file
89
frontend/src/lib/utils/cached-image-util.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
type SkipCacheUntil = {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CachableImage = {
|
||||||
|
getUrl: (...props: any[]) => string;
|
||||||
|
bustCache: (...props: any[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedApplicationLogo: CachableImage = {
|
||||||
|
getUrl: (light = true) => {
|
||||||
|
let url = '/api/application-configuration/logo';
|
||||||
|
if (!light) {
|
||||||
|
url += '?light=false';
|
||||||
|
}
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (light = true) => {
|
||||||
|
let url = '/api/application-configuration/logo';
|
||||||
|
if (!light) {
|
||||||
|
url += '?light=false';
|
||||||
|
}
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedBackgroundImage: CachableImage = {
|
||||||
|
getUrl: () => getCachedImageUrl('/api/application-configuration/background-image'),
|
||||||
|
bustCache: () => bustImageCache('/api/application-configuration/background-image')
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedProfilePicture: CachableImage = {
|
||||||
|
getUrl: (userId: string) => {
|
||||||
|
const url = `/api/users/${userId}/profile-picture.png`;
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (userId: string) => {
|
||||||
|
const url = `/api/users/${userId}/profile-picture.png`;
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedOidcClientLogo: CachableImage = {
|
||||||
|
getUrl: (clientId: string) => {
|
||||||
|
const url = `/api/oidc/clients/${clientId}/logo`;
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (clientId: string) => {
|
||||||
|
const url = `/api/oidc/clients/${clientId}/logo`;
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCachedImageUrl(url: string) {
|
||||||
|
const skipCacheUntil = getSkipCacheUntil(url);
|
||||||
|
const skipCache = skipCacheUntil > Date.now();
|
||||||
|
if (skipCache) {
|
||||||
|
const skipCacheParam = new URLSearchParams();
|
||||||
|
skipCacheParam.append('skip-cache', skipCacheUntil.toString());
|
||||||
|
url += '?' + skipCacheParam.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bustImageCache(url: string) {
|
||||||
|
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
||||||
|
localStorage.getItem('skip-cache-until') ?? '{}'
|
||||||
|
);
|
||||||
|
skipCacheUntil[hashKey(url)] = Date.now() + 1000 * 60 * 15; // 15 minutes
|
||||||
|
localStorage.setItem('skip-cache-until', JSON.stringify(skipCacheUntil));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipCacheUntil(url: string) {
|
||||||
|
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
||||||
|
localStorage.getItem('skip-cache-until') ?? '{}'
|
||||||
|
);
|
||||||
|
return skipCacheUntil[hashKey(url)] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashKey(key: string): string {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < key.length; i++) {
|
||||||
|
const char = key.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + char;
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
return Math.abs(hash).toString(36);
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export async function createSHA256hash(input: string) {
|
|
||||||
const msgUint8 = new TextEncoder().encode(input); // encode as (utf-8) Uint8Array
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
|
||||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
|
|
||||||
return hashHex;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
type SkipCacheUntil = {
|
|
||||||
[key: string]: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getProfilePictureUrl(userId?: string) {
|
|
||||||
if (!userId) return '';
|
|
||||||
|
|
||||||
let url = `/api/users/${userId}/profile-picture.png`;
|
|
||||||
|
|
||||||
const skipCacheUntil = getSkipCacheUntil(userId);
|
|
||||||
const skipCache = skipCacheUntil > Date.now();
|
|
||||||
if (skipCache) {
|
|
||||||
const skipCacheParam = new URLSearchParams();
|
|
||||||
skipCacheParam.append('skip-cache', skipCacheUntil.toString());
|
|
||||||
url += '?' + skipCacheParam.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSkipCacheUntil(userId: string) {
|
|
||||||
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
|
||||||
localStorage.getItem('skip-cache-until') ?? '{}'
|
|
||||||
);
|
|
||||||
return skipCacheUntil[userId] ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function bustProfilePictureCache(userId: string) {
|
|
||||||
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
|
||||||
localStorage.getItem('skip-cache-until') ?? '{}'
|
|
||||||
);
|
|
||||||
skipCacheUntil[userId] = Date.now() + 1000 * 60 * 15; // 15 minutes
|
|
||||||
localStorage.setItem('skip-cache-until', JSON.stringify(skipCacheUntil));
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,13 @@ export function getAuthRedirectPath(path: string, user: User | null) {
|
|||||||
const isAdmin = user?.isAdmin;
|
const isAdmin = user?.isAdmin;
|
||||||
|
|
||||||
const isUnauthenticatedOnlyPath =
|
const isUnauthenticatedOnlyPath =
|
||||||
path == '/login' || path.startsWith('/login/') || path == '/lc' || path.startsWith('/lc/');
|
path == '/login' ||
|
||||||
|
path.startsWith('/login/') ||
|
||||||
|
path == '/lc' ||
|
||||||
|
path.startsWith('/lc/') ||
|
||||||
|
path == '/signup' ||
|
||||||
|
path == '/signup/setup' ||
|
||||||
|
path.startsWith('/st/');
|
||||||
const isPublicPath = ['/authorize', '/device', '/health', '/healthz'].includes(path);
|
const isPublicPath = ['/authorize', '/device', '/health', '/healthz'].includes(path);
|
||||||
const isAdminPath = path == '/settings/admin' || path.startsWith('/settings/admin/');
|
const isAdminPath = path == '/settings/admin' || path.startsWith('/settings/admin/');
|
||||||
|
|
||||||
|
|||||||
20
frontend/src/lib/utils/try-catch-util.ts
Normal file
20
frontend/src/lib/utils/try-catch-util.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
type Success<T> = {
|
||||||
|
data: T;
|
||||||
|
error: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Failure<E> = {
|
||||||
|
data: null;
|
||||||
|
error: E;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result<T, E = Error> = Success<T> | Failure<E>;
|
||||||
|
|
||||||
|
export async function tryCatch<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> {
|
||||||
|
try {
|
||||||
|
const data = await promise;
|
||||||
|
return { data, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { data: null, error: error as E };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,17 @@
|
|||||||
<Header />
|
<Header />
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
<Toaster />
|
<Toaster
|
||||||
|
toastOptions={{
|
||||||
|
classes: {
|
||||||
|
toast: 'border border-primary/30!',
|
||||||
|
title: 'text-foreground',
|
||||||
|
description: 'text-muted-foreground',
|
||||||
|
actionButton: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||||
|
cancelButton: 'bg-muted text-muted-foreground hover:bg-muted/80',
|
||||||
|
closeButton: 'text-muted-foreground hover:text-foreground'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
<ModeWatcher />
|
<ModeWatcher />
|
||||||
|
|||||||
@@ -138,14 +138,20 @@
|
|||||||
</Card.Root>
|
</Card.Root>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex w-full max-w-[450px] gap-2">
|
<!-- Flex flow is reversed so the sign in button, which has auto-focus, is the first one in the DOM, for a11y -->
|
||||||
<Button onclick={() => history.back()} class="flex-1" variant="secondary">{m.cancel()}</Button
|
<div class="flex w-full max-w-[450px] flex-row-reverse gap-2">
|
||||||
>
|
|
||||||
{#if !errorMessage}
|
{#if !errorMessage}
|
||||||
<Button class="flex-1" {isLoading} onclick={authorize}>{m.sign_in()}</Button>
|
<Button class="flex-1" {isLoading} onclick={authorize} autofocus={true}>
|
||||||
|
{m.sign_in()}
|
||||||
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button class="flex-1" onclick={() => (errorMessage = null)}>{m.try_again()}</Button>
|
<Button class="flex-1" onclick={() => (errorMessage = null)}>
|
||||||
|
{m.try_again()}
|
||||||
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
<Button onclick={() => history.back()} class="flex-1" variant="secondary">
|
||||||
|
{m.cancel()}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</SignInWrapper>
|
</SignInWrapper>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import CrossAnimated from '$lib/icons/cross-animated.svelte';
|
import CrossAnimated from '$lib/icons/cross-animated.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import type { OidcClientMetaData } from '$lib/types/oidc.type';
|
import type { OidcClientMetaData } from '$lib/types/oidc.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
success,
|
success,
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
{:else if client.hasLogo}
|
{:else if client.hasLogo}
|
||||||
<img
|
<img
|
||||||
class="size-10"
|
class="size-10"
|
||||||
src="/api/oidc/clients/{client.id}/logo"
|
src={cachedOidcClientLogo.getUrl(client.id)}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
alt={m.client_logo()}
|
alt={m.client_logo()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import SignInWrapper from '$lib/components/login-wrapper.svelte';
|
import SignInWrapper from '$lib/components/login-wrapper.svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import { m } from '$lib/paraglide/messages';
|
||||||
import WebAuthnService from '$lib/services/webauthn-service';
|
import WebAuthnService from '$lib/services/webauthn-service';
|
||||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||||
import userStore from '$lib/stores/user-store';
|
import userStore from '$lib/stores/user-store';
|
||||||
@@ -9,7 +10,6 @@
|
|||||||
import { startAuthentication } from '@simplewebauthn/browser';
|
import { startAuthentication } from '@simplewebauthn/browser';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoginLogoErrorSuccessIndicator from './components/login-logo-error-success-indicator.svelte';
|
import LoginLogoErrorSuccessIndicator from './components/login-logo-error-success-indicator.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
|
||||||
const webauthnService = new WebAuthnService();
|
const webauthnService = new WebAuthnService();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
@@ -49,10 +49,17 @@
|
|||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-muted-foreground mt-2" in:fade>
|
<p class="text-muted-foreground mt-2" in:fade>
|
||||||
{m.authenticate_yourself_with_your_passkey_to_access_the_admin_panel()}
|
{m.authenticate_with_passkey_to_access_account()}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<Button class="mt-10" {isLoading} onclick={authenticate}
|
<div class="mt-10 flex justify-center gap-3">
|
||||||
>{error ? m.try_again() : m.authenticate()}</Button
|
{#if $appConfigStore.allowUserSignups === 'open'}
|
||||||
>
|
<Button variant="secondary" href="/signup">
|
||||||
|
{m.signup()}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<Button {isLoading} onclick={authenticate} autofocus={true}>
|
||||||
|
{error ? m.try_again() : m.authenticate()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</SignInWrapper>
|
</SignInWrapper>
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import SignInWrapper from '$lib/components/login-wrapper.svelte';
|
|
||||||
import { Button } from '$lib/components/ui/button';
|
|
||||||
import { m } from '$lib/paraglide/messages';
|
|
||||||
import UserService from '$lib/services/user-service';
|
|
||||||
import appConfigStore from '$lib/stores/application-configuration-store.js';
|
|
||||||
import userStore from '$lib/stores/user-store.js';
|
|
||||||
import { getAxiosErrorMessage } from '$lib/utils/error-util';
|
|
||||||
import LoginLogoErrorSuccessIndicator from '../components/login-logo-error-success-indicator.svelte';
|
|
||||||
|
|
||||||
let isLoading = $state(false);
|
|
||||||
let error: string | undefined = $state();
|
|
||||||
|
|
||||||
const userService = new UserService();
|
|
||||||
|
|
||||||
async function authenticate() {
|
|
||||||
isLoading = true;
|
|
||||||
try {
|
|
||||||
const user = await userService.exchangeOneTimeAccessToken('setup');
|
|
||||||
userStore.setUser(user);
|
|
||||||
|
|
||||||
goto('/settings');
|
|
||||||
} catch (e) {
|
|
||||||
error = getAxiosErrorMessage(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SignInWrapper animate={!$appConfigStore.disableAnimations}>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<LoginLogoErrorSuccessIndicator error={!!error} />
|
|
||||||
</div>
|
|
||||||
<h1 class="font-playfair mt-5 text-4xl font-bold">
|
|
||||||
{m.appname_setup({ appName: $appConfigStore.appName })}
|
|
||||||
</h1>
|
|
||||||
{#if error}
|
|
||||||
<p class="text-muted-foreground mt-2">
|
|
||||||
{error}. {m.please_try_again()}
|
|
||||||
</p>
|
|
||||||
{:else}
|
|
||||||
<p class="text-muted-foreground mt-2">
|
|
||||||
{m.you_are_about_to_sign_in_to_the_initial_admin_account()}
|
|
||||||
</p>
|
|
||||||
<Button class="mt-5" {isLoading} onclick={authenticate}>{m.continue()}</Button>
|
|
||||||
{/if}
|
|
||||||
</SignInWrapper>
|
|
||||||
@@ -26,10 +26,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z.string().min(3).max(50),
|
||||||
.string()
|
|
||||||
.min(3)
|
|
||||||
.max(50),
|
|
||||||
description: z.string().default(''),
|
description: z.string().default(''),
|
||||||
expiresAt: z.date().min(new Date(), m.expiration_date_must_be_in_the_future())
|
expiresAt: z.date().min(new Date(), m.expiration_date_must_be_in_the_future())
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user