Compare commits

...

33 Commits

Author SHA1 Message Date
Elias Schneider
f125cf0dad release: 1.15.0 2025-11-06 15:49:39 +01:00
Elias Schneider
6a038fcf9a fix: remove redundant indexes in Postgres 2025-11-06 15:02:39 +01:00
Elias Schneider
76e0192cee fix: disabled property gets ignored when creating an user 2025-11-06 12:28:07 +01:00
Elias Schneider
3ebf94dd84 chore(translations): update translations via Crowdin (#1059) 2025-11-05 11:28:14 +01:00
dai
7ec57437ac fix: replace %lang% placeholder in html lang (#1071)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
2025-11-04 13:01:12 +00:00
Elias Schneider
ed2c7b2303 feat: add ability to set default profile picture (#1061)
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
2025-11-04 13:40:00 +01:00
Elias Schneider
e03270eb9d fix: sorting by PKCE and re-auth of OIDC clients 2025-11-04 13:27:05 +01:00
Elias Schneider
d683d18d91 chore: add support for OpenBSD binaries 2025-11-01 16:26:43 +01:00
Elias Schneider
f184120890 feat: open edit page on table row click 2025-10-29 10:46:03 +01:00
Elias Schneider
04d8500910 release: 1.14.2 2025-10-29 09:25:28 +01:00
Mufeed Ali
93639dddb2 fix: dark oidc client icons not saved on client creation (#1057) 2025-10-28 12:35:56 +01:00
Elias Schneider
a190529117 chore(translations): add Turkish language files 2025-10-28 09:31:32 +01:00
Elias Schneider
73392b5837 release: 1.14.1 2025-10-27 14:07:34 +01:00
Elias Schneider
65616f65e5 fix: ignore trailing slashes in APP_URL 2025-10-27 10:48:27 +01:00
Elias Schneider
98a99fbb0a chore(translations): update translations via Crowdin (#1048) 2025-10-27 09:48:39 +01:00
Quentin L'Hours
3f3b6b88fd fix: use credProps to save passkey on firefox android (#1055) 2025-10-27 09:48:24 +01:00
Mufeed Ali
8f98d8c0b4 fix: Prevent blinding FOUC in dark mode (#1054) 2025-10-26 20:40:25 +01:00
Elias Schneider
c9308472a9 release: 1.14.0 2025-10-24 13:47:58 +02:00
Elias Schneider
6362ff9861 chore: upgrade dependencies 2025-10-24 12:18:38 +02:00
Elias Schneider
10d640385f fix: prevent page flickering on redirection based on auth state 2025-10-24 12:12:42 +02:00
Elias Schneider
47927d1574 fix: make pkce requirement visible in the oidc form if client is public 2025-10-24 10:57:44 +02:00
Elias Schneider
b356cef766 fix: only animate login background on initial page load 2025-10-24 10:53:51 +02:00
Elias Schneider
9fc45930a8 chore(translations): update translations via Crowdin (#1033) 2025-10-24 09:58:00 +02:00
Kyle Mendell
028d1c858e feat: add support for dark mode oidc client icons (#1039)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
2025-10-24 09:57:12 +02:00
Alessandro (Ale) Segala
eb3963d0fc fix: use constant time comparisons when validating PKCE challenges (#1047) 2025-10-24 08:30:50 +02:00
dependabot[bot]
35d913f905 chore(deps-dev): bump vite from 7.0.7 to 7.0.8 in the npm_and_yarn group across 1 directory (#1042)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 18:04:28 -05:00
github-actions[bot]
32485f4c7c chore: update AAGUIDs (#1041)
Co-authored-by: stonith404 <58886915+stonith404@users.noreply.github.com>
2025-10-20 08:43:27 +02:00
Elias Schneider
ceb38b0825 chore(translations): update translations via Crowdin (#1025) 2025-10-16 08:28:13 +02:00
dependabot[bot]
c0b6ede5be chore(deps): bump sveltekit-superforms from 2.27.1 to 2.27.4 in the npm_and_yarn group across 1 directory (#1031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 08:27:52 +02:00
Elias Schneider
c20e93b55c feat: add various improvements to the table component (#961)
Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
2025-10-13 09:12:55 +00:00
Elias Schneider
24ca6a106d chore(translations): update translations via Crowdin (#1014) 2025-10-12 09:58:22 -05:00
Elias Schneider
9f0aa55be6 fix: ignore trailing slash in URL 2025-10-09 20:27:15 +02:00
Kyle Mendell
068fcc65a6 chore(translations): add Japanese files 2025-10-07 18:29:13 -05:00
151 changed files with 5866 additions and 3733 deletions

4
.gitignore vendored
View File

@@ -1,8 +1,12 @@
# JetBrains # JetBrains
**/.idea **/.idea
# Node
node_modules node_modules
# PNPM
.pnpm-store/
# Output # Output
.output .output
.vercel .vercel

View File

@@ -1 +1 @@
1.13.1 1.15.0

View File

@@ -1,3 +1,70 @@
## v1.15.0
### Bug Fixes
- sorting by PKCE and re-auth of OIDC clients ([e03270e](https://github.com/pocket-id/pocket-id/commit/e03270eb9d474735ff4a1b4d8c90f1857b8cd52b) by @stonith404)
- replace %lang% placeholder in html lang ([#1071](https://github.com/pocket-id/pocket-id/pull/1071) by @daimond113)
- disabled property gets ignored when creating an user ([76e0192](https://github.com/pocket-id/pocket-id/commit/76e0192ceec339b6ddb4ad3424057d2bb48fae8f) by @stonith404)
- remove redundant indexes in Postgres ([6a038fc](https://github.com/pocket-id/pocket-id/commit/6a038fcf9afabbf00c45e42071e9bbe62ecab403) by @stonith404)
### Features
- open edit page on table row click ([f184120](https://github.com/pocket-id/pocket-id/commit/f184120890c32f1e75a918c171084878a10e8b42) by @stonith404)
- add ability to set default profile picture ([#1061](https://github.com/pocket-id/pocket-id/pull/1061) by @stonith404)
### Other
- add support for OpenBSD binaries ([d683d18](https://github.com/pocket-id/pocket-id/commit/d683d18d9109ca2850e278b78f7bf3e5aca1d34d) by @stonith404)
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v1.14.2...v1.15.0
## v1.14.2
### Bug Fixes
- dark oidc client icons not saved on client creation ([#1057](https://github.com/pocket-id/pocket-id/pull/1057) by @mufeedali)
### Other
- add Turkish language files ([a190529](https://github.com/pocket-id/pocket-id/commit/a190529117fe20b5b836d452b382da69abba9458) by @stonith404)
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v1.14.1...v1.14.2
## v1.14.1
### Bug Fixes
- Prevent blinding FOUC in dark mode ([#1054](https://github.com/pocket-id/pocket-id/pull/1054) by @mufeedali)
- use credProps to save passkey on firefox android ([#1055](https://github.com/pocket-id/pocket-id/pull/1055) by @lhoursquentin)
- ignore trailing slashes in `APP_URL` ([65616f6](https://github.com/pocket-id/pocket-id/commit/65616f65e53f3e62d18a8209929e68ddc8d2b9b8) by @stonith404)
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v1.14.0...v1.14.1
## v1.14.0
### Bug Fixes
- ignore trailing slash in URL ([9f0aa55](https://github.com/pocket-id/pocket-id/commit/9f0aa55be67b7a09810569250563bb388b40590a) by @stonith404)
- use constant time comparisons when validating PKCE challenges ([#1047](https://github.com/pocket-id/pocket-id/pull/1047) by @ItalyPaleAle)
- only animate login background on initial page load ([b356cef](https://github.com/pocket-id/pocket-id/commit/b356cef766697c621157235ae1d2743f3fe6720d) by @stonith404)
- make pkce requirement visible in the oidc form if client is public ([47927d1](https://github.com/pocket-id/pocket-id/commit/47927d157470daa5b5a5b30e61a2ba69110eeff9) by @stonith404)
- prevent page flickering on redirection based on auth state ([10d6403](https://github.com/pocket-id/pocket-id/commit/10d640385ff2078299a07f05e5ca3f0d392eecf7) by @stonith404)
### Features
- add various improvements to the table component ([#961](https://github.com/pocket-id/pocket-id/pull/961) by @stonith404)
- add support for dark mode oidc client icons ([#1039](https://github.com/pocket-id/pocket-id/pull/1039) by @kmendell)
### Other
- add Japanese files ([068fcc6](https://github.com/pocket-id/pocket-id/commit/068fcc65a62c76f55c9636f830fc769bd59220c4) by @kmendell)
- bump sveltekit-superforms from 2.27.1 to 2.27.4 in the npm_and_yarn group across 1 directory ([#1031](https://github.com/pocket-id/pocket-id/pull/1031) by @dependabot[bot])
- update AAGUIDs ([#1041](https://github.com/pocket-id/pocket-id/pull/1041) by @github-actions[bot])
- bump vite from 7.0.7 to 7.0.8 in the npm_and_yarn group across 1 directory ([#1042](https://github.com/pocket-id/pocket-id/pull/1042) by @dependabot[bot])
- upgrade dependencies ([6362ff9](https://github.com/pocket-id/pocket-id/commit/6362ff986124d056cc07d214855f198eab9cb97d) by @stonith404)
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v1.13.1...v1.14.0
## v1.13.1 ## v1.13.1
### Bug Fixes ### Bug Fixes

View File

@@ -32,10 +32,6 @@ func init() {
panic(fmt.Errorf("failed to read index.html: %w", iErr)) panic(fmt.Errorf("failed to read index.html: %w", iErr))
} }
// Get the position of the first <script> tag
idx := bytes.Index(index, []byte(scriptTag))
// Create writeIndexFn, which adds the CSP tag to the script tag if needed
writeIndexFn = func(w io.Writer, nonce string) (err error) { writeIndexFn = func(w io.Writer, nonce string) (err error) {
// If there's no nonce, write the index as-is // If there's no nonce, write the index as-is
if nonce == "" { if nonce == "" {
@@ -43,23 +39,16 @@ func init() {
return err return err
} }
// We have a nonce, so first write the index until the <script> tag // Add nonce to all <script> tags
// Then we write the modified script tag // We replace "<script" with `<script nonce="..."` everywhere it appears
// Finally, the rest of the index modified := bytes.ReplaceAll(
_, err = w.Write(index[0:idx]) index,
if err != nil { []byte(scriptTag),
return err []byte(`<script nonce="`+nonce+`">`),
} )
_, err = w.Write([]byte(`<script nonce="` + nonce + `">`))
if err != nil {
return err
}
_, err = w.Write(index[(idx + len(scriptTag)):])
if err != nil {
return err
}
return nil _, err = w.Write(modified)
return err
} }
} }
@@ -75,6 +64,11 @@ func RegisterFrontend(router *gin.Engine) error {
router.NoRoute(func(c *gin.Context) { router.NoRoute(func(c *gin.Context) {
path := strings.TrimPrefix(c.Request.URL.Path, "/") path := strings.TrimPrefix(c.Request.URL.Path, "/")
if strings.HasSuffix(path, "/") {
c.Redirect(http.StatusMovedPermanently, strings.TrimRight(c.Request.URL.String(), "/"))
return
}
if strings.HasPrefix(path, "api/") { if strings.HasPrefix(path, "api/") {
c.JSON(http.StatusNotFound, gin.H{"error": "API endpoint not found"}) c.JSON(http.StatusNotFound, gin.H{"error": "API endpoint not found"})
return return
@@ -94,13 +88,9 @@ func RegisterFrontend(router *gin.Engine) error {
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.Header("Cache-Control", "no-store") c.Header("Cache-Control", "no-store")
c.Status(http.StatusOK) c.Status(http.StatusOK)
if err := writeIndexFn(c.Writer, nonce); err != nil {
err = writeIndexFn(c.Writer, nonce)
if err != nil {
_ = c.Error(fmt.Errorf("failed to write index.html file: %w", err)) _ = c.Error(fmt.Errorf("failed to write index.html file: %w", err))
return
} }
return return
} }

View File

@@ -7,84 +7,87 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 github.com/cenkalti/backoff/v5 v5.0.3
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
github.com/emersion/go-smtp v0.21.3 github.com/emersion/go-smtp v0.24.0
github.com/fxamacker/cbor/v2 v2.9.0 github.com/fxamacker/cbor/v2 v2.9.0
github.com/gin-contrib/slog v1.1.0 github.com/gin-contrib/slog v1.1.0
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.11.0
github.com/glebarez/go-sqlite v1.22.0 github.com/glebarez/go-sqlite v1.22.0
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/go-co-op/gocron/v2 v2.16.3 github.com/go-co-op/gocron/v2 v2.17.0
github.com/go-ldap/ldap/v3 v3.4.10 github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-playground/validator/v10 v10.27.0 github.com/go-playground/validator/v10 v10.28.0
github.com/go-webauthn/webauthn v0.11.2 github.com/go-webauthn/webauthn v0.14.0
github.com/golang-migrate/migrate/v4 v4.18.3 github.com/golang-migrate/migrate/v4 v4.19.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-uuid v1.0.3
github.com/jinzhu/copier v0.4.0 github.com/jinzhu/copier v0.4.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/lestrrat-go/httprc/v3 v3.0.0 github.com/lestrrat-go/httprc/v3 v3.0.1
github.com/lestrrat-go/jwx/v3 v3.0.10 github.com/lestrrat-go/jwx/v3 v3.0.12
github.com/lmittmann/tint v1.1.2 github.com/lmittmann/tint v1.1.2
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mileusna/useragent v1.3.5 github.com/mileusna/useragent v1.3.5
github.com/orandin/slog-gorm v1.4.0 github.com/orandin/slog-gorm v1.4.0
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.8 github.com/oschwald/maxminddb-golang/v2 v2.0.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 go.opentelemetry.io/contrib/bridges/otelslog v0.13.0
go.opentelemetry.io/contrib/exporters/autoexport v0.59.0 go.opentelemetry.io/contrib/exporters/autoexport v0.63.0
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/log v0.13.0 go.opentelemetry.io/otel/log v0.14.0
go.opentelemetry.io/otel/metric v1.37.0 go.opentelemetry.io/otel/metric v1.38.0
go.opentelemetry.io/otel/sdk v1.35.0 go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/sdk/log v0.10.0 go.opentelemetry.io/otel/sdk/log v0.14.0
go.opentelemetry.io/otel/sdk/metric v1.35.0 go.opentelemetry.io/otel/sdk/metric v1.38.0
go.opentelemetry.io/otel/trace v1.37.0 go.opentelemetry.io/otel/trace v1.38.0
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.43.0
golang.org/x/image v0.30.0 golang.org/x/image v0.32.0
golang.org/x/sync v0.16.0 golang.org/x/sync v0.17.0
golang.org/x/text v0.28.0 golang.org/x/text v0.30.0
golang.org/x/time v0.12.0 golang.org/x/time v0.14.0
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.30.1 gorm.io/gorm v1.31.0
) )
require ( require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/disintegration/gift v1.1.2 // indirect github.com/disintegration/gift v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-webauthn/x v0.1.23 // indirect github.com/go-webauthn/x v0.1.25 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-github/v39 v39.2.0 // indirect github.com/google/go-github/v39 v39.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect github.com/google/go-tpm v0.9.6 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
@@ -93,57 +96,66 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/dsig v1.0.0 // indirect
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect
github.com/lib/pq v1.10.9 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.18.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.55.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.59.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.10.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.opentelemetry.io/proto/otlp v1.8.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
golang.org/x/arch v0.20.0 // indirect go.uber.org/mock v0.6.0 // indirect
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/arch v0.22.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/mod v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect golang.org/x/net v0.46.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect golang.org/x/oauth2 v0.32.0 // indirect
google.golang.org/grpc v1.71.0 // indirect golang.org/x/sys v0.37.0 // indirect
google.golang.org/protobuf v1.36.7 // indirect golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.66.7 // indirect modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.2 // indirect modernc.org/sqlite v1.39.1 // indirect
) )

View File

@@ -6,10 +6,15 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
@@ -30,8 +35,11 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM=
github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo=
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
github.com/disintegration/gift v1.1.2 h1:9ZyHJr+kPamiH10FX3Pynt1AxFUob812bU9Wt4GMzhs= github.com/disintegration/gift v1.1.2 h1:9ZyHJr+kPamiH10FX3Pynt1AxFUob812bU9Wt4GMzhs=
github.com/disintegration/gift v1.1.2/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/disintegration/gift v1.1.2/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec h1:YrB6aVr9touOt75I9O1SiancmR2GMg45U9UYf0gtgWg= github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec h1:YrB6aVr9touOt75I9O1SiancmR2GMg45U9UYf0gtgWg=
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec/go.mod h1:K0KBFIr1gWu/C1Gp10nFAcAE4hsB7JxE6OgLijrJ8Sk= github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec/go.mod h1:K0KBFIr1gWu/C1Gp10nFAcAE4hsB7JxE6OgLijrJ8Sk=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
@@ -40,6 +48,7 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -48,30 +57,44 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY= github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/slog v1.1.0 h1:K9MVNrETT6r/C3u2Aheer/gxwVeVqrGL0hXlsmv3fm4= github.com/gin-contrib/slog v1.1.0 h1:K9MVNrETT6r/C3u2Aheer/gxwVeVqrGL0hXlsmv3fm4=
github.com/gin-contrib/slog v1.1.0/go.mod h1:PvNXQVXcVOAaaiJR84LV1/xlQHIaXi9ygEXyBkmjdkY= github.com/gin-contrib/slog v1.1.0/go.mod h1:PvNXQVXcVOAaaiJR84LV1/xlQHIaXi9ygEXyBkmjdkY=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-co-op/gocron/v2 v2.16.3 h1:kYqukZqBa8RC2+AFAHnunmKcs9GRTjwBo8WRF3I6cbI= github.com/go-co-op/gocron/v2 v2.16.3 h1:kYqukZqBa8RC2+AFAHnunmKcs9GRTjwBo8WRF3I6cbI=
github.com/go-co-op/gocron/v2 v2.16.3/go.mod h1:aTf7/+5Jo2E+cyAqq625UQ6DzpkV96b22VHIUAt6l3c= github.com/go-co-op/gocron/v2 v2.16.3/go.mod h1:aTf7/+5Jo2E+cyAqq625UQ6DzpkV96b22VHIUAt6l3c=
github.com/go-co-op/gocron/v2 v2.17.0 h1:e/oj6fcAM8vOOKZxv2Cgfmjo+s8AXC46po5ZPtaSea4=
github.com/go-co-op/gocron/v2 v2.17.0/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -85,18 +108,30 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0=
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
github.com/go-webauthn/x v0.1.23 h1:9lEO0s+g8iTyz5Vszlg/rXTGrx3CjcD0RZQ1GPZCaxI= github.com/go-webauthn/x v0.1.23 h1:9lEO0s+g8iTyz5Vszlg/rXTGrx3CjcD0RZQ1GPZCaxI=
github.com/go-webauthn/x v0.1.23/go.mod h1:AJd3hI7NfEp/4fI6T4CHD753u91l510lglU7/NMN6+E= github.com/go-webauthn/x v0.1.23/go.mod h1:AJd3hI7NfEp/4fI6T4CHD753u91l510lglU7/NMN6+E=
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE=
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -112,6 +147,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
@@ -119,8 +156,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -137,6 +178,8 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -177,12 +220,20 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.0 h1:nZUx/zFg5uc2rhlu1L1DidGr5Sj02JbXvGSpnY4LMrc= github.com/lestrrat-go/httprc/v3 v3.0.0 h1:nZUx/zFg5uc2rhlu1L1DidGr5Sj02JbXvGSpnY4LMrc=
github.com/lestrrat-go/httprc/v3 v3.0.0/go.mod h1:k2U1QIiyVqAKtkffbg+cUmsyiPGQsb9aAfNQiNFuQ9Q= github.com/lestrrat-go/httprc/v3 v3.0.0/go.mod h1:k2U1QIiyVqAKtkffbg+cUmsyiPGQsb9aAfNQiNFuQ9Q=
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw= github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw=
github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk= github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk=
github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=
github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
@@ -195,6 +246,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws= github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -214,6 +267,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@@ -222,6 +277,8 @@ github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsT
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA= github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.8 h1:aM1/rO6p+XV+l+seD7UCtFZgsOefDTrFVLvPoZWjXZs= github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.8 h1:aM1/rO6p+XV+l+seD7UCtFZgsOefDTrFVLvPoZWjXZs=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.8/go.mod h1:Jts8ztuE0PkUwY7VCJyp6B68ujQfr6G9P5Dn3Yx9u6w= github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.8/go.mod h1:Jts8ztuE0PkUwY7VCJyp6B68ujQfr6G9P5Dn3Yx9u6w=
github.com/oschwald/maxminddb-golang/v2 v2.0.0 h1:Gyljxck1kHbBxDgLM++NfDWBqvu1pWWfT8XbosSo0bo=
github.com/oschwald/maxminddb-golang/v2 v2.0.0/go.mod h1:gG4V88LsawPEqtbL1Veh1WRh+nVSYwXzJ1P5Fcn77g0=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -230,25 +287,47 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/procfs v0.18.0 h1:2QTA9cKdznfYJz7EDaa7IiJobHuV7E1WzeBwcrhk0ao=
github.com/prometheus/procfs v0.18.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -261,6 +340,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
@@ -272,60 +353,116 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g= go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g=
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A= go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A=
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
go.opentelemetry.io/contrib/bridges/prometheus v0.59.0 h1:HY2hJ7yn3KuEBBBsKxvF3ViSmzLwsgeNvD+0utRMgzc= go.opentelemetry.io/contrib/bridges/prometheus v0.59.0 h1:HY2hJ7yn3KuEBBBsKxvF3ViSmzLwsgeNvD+0utRMgzc=
go.opentelemetry.io/contrib/bridges/prometheus v0.59.0/go.mod h1:H4H7vs8766kwFnOZVEGMJFVF+phpBSmTckvvNRdJeDI= go.opentelemetry.io/contrib/bridges/prometheus v0.59.0/go.mod h1:H4H7vs8766kwFnOZVEGMJFVF+phpBSmTckvvNRdJeDI=
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 h1:/Rij/t18Y7rUayNg7Id6rPrEnHgorxYabm2E6wUdPP4=
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0/go.mod h1:AdyDPn6pkbkt2w01n3BubRVk7xAsCRq1Yg1mpfyA/0E=
go.opentelemetry.io/contrib/exporters/autoexport v0.59.0 h1:dKhAFwh7SSoOw+gwMtSv+XLkUGTFAwAGMT3X3XSE4FA= go.opentelemetry.io/contrib/exporters/autoexport v0.59.0 h1:dKhAFwh7SSoOw+gwMtSv+XLkUGTFAwAGMT3X3XSE4FA=
go.opentelemetry.io/contrib/exporters/autoexport v0.59.0/go.mod h1:fPl+qlrhRdRntIpPs9JoQ0iBKAsnH5VkgppU1f9kyF4= go.opentelemetry.io/contrib/exporters/autoexport v0.59.0/go.mod h1:fPl+qlrhRdRntIpPs9JoQ0iBKAsnH5VkgppU1f9kyF4=
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 h1:NLnZybb9KkfMXPwZhd5diBYJoVxiO9Qa06dacEA7ySY=
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0/go.mod h1:OvRg7gm5WRSCtxzGSsrFHbDLToYlStHNZQ+iPNIyD6g=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0 h1:5kSIJ0y8ckZZKoDhZHdVtcyjVi6rXyAwyaR8mp4zLbg=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0/go.mod h1:i+fIMHvcSQtsIY82/xgiVWRklrNt/O6QriHLjzGeY+s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0 h1:5dTKu4I5Dn4P2hxyW3l3jTaZx9ACgg0ECos1eAVrheY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0 h1:5dTKu4I5Dn4P2hxyW3l3jTaZx9ACgg0ECos1eAVrheY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0/go.mod h1:P5HcUI8obLrCCmM3sbVBohZFH34iszk/+CPWuakZWL8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0/go.mod h1:P5HcUI8obLrCCmM3sbVBohZFH34iszk/+CPWuakZWL8=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0 h1:q/heq5Zh8xV1+7GoMGJpTxM2Lhq5+bFxB29tshuRuw0= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0 h1:q/heq5Zh8xV1+7GoMGJpTxM2Lhq5+bFxB29tshuRuw0=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0/go.mod h1:leO2CSTg0Y+LyvmR7Wm4pUxE8KAmaM2GCVx7O+RATLA= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0/go.mod h1:leO2CSTg0Y+LyvmR7Wm4pUxE8KAmaM2GCVx7O+RATLA=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c=
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.10.0 h1:GKCEAZLEpEf78cUvudQdTg0aET2ObOZRB2HtXA0qPAI= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.10.0 h1:GKCEAZLEpEf78cUvudQdTg0aET2ObOZRB2HtXA0qPAI=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.10.0/go.mod h1:9/zqSWLCmHT/9Jo6fYeUDRRogOLL60ABLsHWS99lF8s= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.10.0/go.mod h1:9/zqSWLCmHT/9Jo6fYeUDRRogOLL60ABLsHWS99lF8s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls= go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls=
go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E= go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E=
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/log v0.10.0 h1:lR4teQGWfeDVGoute6l0Ou+RpFqQ9vaPdrNJlST0bvw= go.opentelemetry.io/otel/sdk/log v0.10.0 h1:lR4teQGWfeDVGoute6l0Ou+RpFqQ9vaPdrNJlST0bvw=
go.opentelemetry.io/otel/sdk/log v0.10.0/go.mod h1:A+V1UTWREhWAittaQEG4bYm4gAZa6xnvVu+xKrIRkzo= go.opentelemetry.io/otel/sdk/log v0.10.0/go.mod h1:A+V1UTWREhWAittaQEG4bYm4gAZa6xnvVu+xKrIRkzo=
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -336,11 +473,17 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -348,6 +491,8 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -362,9 +507,13 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -374,6 +523,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -388,6 +539,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -409,8 +562,12 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -419,17 +576,27 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -441,18 +608,25 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I= modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
modernc.org/fileutil v1.3.15 h1:rJAXTP6ilMW/1+kzDiqmBlHLWszheUFXIyGQIAvjJpY= modernc.org/fileutil v1.3.15 h1:rJAXTP6ilMW/1+kzDiqmBlHLWszheUFXIyGQIAvjJpY=
modernc.org/fileutil v1.3.15/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.3.15/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.7 h1:rjhZ8OSCybKWxS1CJr0hikpEi6Vg+944Ouyrd+bQsoY= modernc.org/libc v1.66.7 h1:rjhZ8OSCybKWxS1CJr0hikpEi6Vg+944Ouyrd+bQsoY=
modernc.org/libc v1.66.7/go.mod h1:ln6tbWX0NH+mzApEoDRvilBvAWFt1HX7AUA4VDdVDPM= modernc.org/libc v1.66.7/go.mod h1:ln6tbWX0NH+mzApEoDRvilBvAWFt1HX7AUA4VDdVDPM=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -463,6 +637,8 @@ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -62,7 +62,7 @@ func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, ima
} }
svc.userGroupService = service.NewUserGroupService(db, svc.appConfigService) svc.userGroupService = service.NewUserGroupService(db, svc.appConfigService)
svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService, svc.customClaimService) svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService, svc.customClaimService, svc.appImagesService)
svc.ldapService = service.NewLdapService(db, httpClient, svc.appConfigService, svc.userService, svc.userGroupService) svc.ldapService = service.NewLdapService(db, httpClient, svc.appConfigService, svc.userService, svc.userGroupService)
svc.apiKeyService = service.NewApiKeyService(db, svc.emailService) svc.apiKeyService = service.NewApiKeyService(db, svc.emailService)

View File

@@ -35,7 +35,7 @@ const (
type EnvConfigSchema struct { type EnvConfigSchema struct {
AppEnv string `env:"APP_ENV" options:"toLower"` AppEnv string `env:"APP_ENV" options:"toLower"`
LogLevel string `env:"LOG_LEVEL" options:"toLower"` LogLevel string `env:"LOG_LEVEL" options:"toLower"`
AppURL string `env:"APP_URL" options:"toLower"` AppURL string `env:"APP_URL" options:"toLower,trimTrailingSlash"`
DbProvider DbProvider `env:"DB_PROVIDER" options:"toLower"` DbProvider DbProvider `env:"DB_PROVIDER" options:"toLower"`
DbConnectionString string `env:"DB_CONNECTION_STRING" options:"file"` DbConnectionString string `env:"DB_CONNECTION_STRING" options:"file"`
UploadPath string `env:"UPLOAD_PATH"` UploadPath string `env:"UPLOAD_PATH"`
@@ -227,6 +227,10 @@ func prepareEnvConfig(config *EnvConfigSchema) error {
if err != nil { if err != nil {
return err return err
} }
case "trimTrailingSlash":
if field.Kind() == reflect.String {
field.SetString(strings.TrimRight(field.String(), "/"))
}
} }
} }
} }

View File

@@ -388,3 +388,13 @@ func (e *UserEmailNotSetError) Error() string {
func (e *UserEmailNotSetError) HttpStatusCode() int { func (e *UserEmailNotSetError) HttpStatusCode() int {
return http.StatusBadRequest return http.StatusBadRequest
} }
type ImageNotFoundError struct{}
func (e *ImageNotFoundError) Error() string {
return "Image not found"
}
func (e *ImageNotFoundError) HttpStatusCode() int {
return http.StatusNotFound
}

View File

@@ -45,15 +45,11 @@ func NewApiKeyController(group *gin.RouterGroup, authMiddleware *middleware.Auth
// @Success 200 {object} dto.Paginated[dto.ApiKeyDto] // @Success 200 {object} dto.Paginated[dto.ApiKeyDto]
// @Router /api/api-keys [get] // @Router /api/api-keys [get]
func (c *ApiKeyController) listApiKeysHandler(ctx *gin.Context) { func (c *ApiKeyController) listApiKeysHandler(ctx *gin.Context) {
listRequestOptions := utils.ParseListRequestOptions(ctx)
userID := ctx.GetString("userID") userID := ctx.GetString("userID")
var sortedPaginationRequest utils.SortedPaginationRequest apiKeys, pagination, err := c.apiKeyService.ListApiKeys(ctx.Request.Context(), userID, listRequestOptions)
if err := ctx.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = ctx.Error(err)
return
}
apiKeys, pagination, err := c.apiKeyService.ListApiKeys(ctx.Request.Context(), userID, sortedPaginationRequest)
if err != nil { if err != nil {
_ = ctx.Error(err) _ = ctx.Error(err)
return return

View File

@@ -25,10 +25,14 @@ func NewAppImagesController(
group.GET("/application-images/logo", controller.getLogoHandler) group.GET("/application-images/logo", controller.getLogoHandler)
group.GET("/application-images/background", controller.getBackgroundImageHandler) group.GET("/application-images/background", controller.getBackgroundImageHandler)
group.GET("/application-images/favicon", controller.getFaviconHandler) group.GET("/application-images/favicon", controller.getFaviconHandler)
group.GET("/application-images/default-profile-picture", authMiddleware.Add(), controller.getDefaultProfilePicture)
group.PUT("/application-images/logo", authMiddleware.Add(), controller.updateLogoHandler) group.PUT("/application-images/logo", authMiddleware.Add(), controller.updateLogoHandler)
group.PUT("/application-images/background", authMiddleware.Add(), controller.updateBackgroundImageHandler) group.PUT("/application-images/background", authMiddleware.Add(), controller.updateBackgroundImageHandler)
group.PUT("/application-images/favicon", authMiddleware.Add(), controller.updateFaviconHandler) group.PUT("/application-images/favicon", authMiddleware.Add(), controller.updateFaviconHandler)
group.PUT("/application-images/default-profile-picture", authMiddleware.Add(), controller.updateDefaultProfilePicture)
group.DELETE("/application-images/default-profile-picture", authMiddleware.Add(), controller.deleteDefaultProfilePicture)
} }
type AppImagesController struct { type AppImagesController struct {
@@ -78,6 +82,18 @@ func (c *AppImagesController) getFaviconHandler(ctx *gin.Context) {
c.getImage(ctx, "favicon") c.getImage(ctx, "favicon")
} }
// getDefaultProfilePicture godoc
// @Summary Get default profile picture image
// @Description Get the default profile picture image for the application
// @Tags Application Images
// @Produce image/png
// @Produce image/jpeg
// @Success 200 {file} binary "Default profile picture image"
// @Router /api/application-images/default-profile-picture [get]
func (c *AppImagesController) getDefaultProfilePicture(ctx *gin.Context) {
c.getImage(ctx, "default-profile-picture")
}
// updateLogoHandler godoc // updateLogoHandler godoc
// @Summary Update logo // @Summary Update logo
// @Description Update the application logo // @Description Update the application logo
@@ -171,3 +187,41 @@ func (c *AppImagesController) getImage(ctx *gin.Context, name string) {
utils.SetCacheControlHeader(ctx, 15*time.Minute, 24*time.Hour) utils.SetCacheControlHeader(ctx, 15*time.Minute, 24*time.Hour)
ctx.File(imagePath) ctx.File(imagePath)
} }
// updateDefaultProfilePicture godoc
// @Summary Update default profile picture image
// @Description Update the default profile picture image
// @Tags Application Images
// @Accept multipart/form-data
// @Param file formData file true "Profile picture image file"
// @Success 204 "No Content"
// @Router /api/application-images/default-profile-picture [put]
func (c *AppImagesController) updateDefaultProfilePicture(ctx *gin.Context) {
file, err := ctx.FormFile("file")
if err != nil {
_ = ctx.Error(err)
return
}
if err := c.appImagesService.UpdateImage(file, "default-profile-picture"); err != nil {
_ = ctx.Error(err)
return
}
ctx.Status(http.StatusNoContent)
}
// deleteDefaultProfilePicture godoc
// @Summary Delete default profile picture image
// @Description Delete the default profile picture image
// @Tags Application Images
// @Success 204 "No Content"
// @Router /api/application-images/default-profile-picture [delete]
func (c *AppImagesController) deleteDefaultProfilePicture(ctx *gin.Context) {
if err := c.appImagesService.DeleteImage("default-profile-picture"); err != nil {
_ = ctx.Error(err)
return
}
ctx.Status(http.StatusNoContent)
}

View File

@@ -41,18 +41,12 @@ type AuditLogController struct {
// @Success 200 {object} dto.Paginated[dto.AuditLogDto] // @Success 200 {object} dto.Paginated[dto.AuditLogDto]
// @Router /api/audit-logs [get] // @Router /api/audit-logs [get]
func (alc *AuditLogController) listAuditLogsForUserHandler(c *gin.Context) { func (alc *AuditLogController) listAuditLogsForUserHandler(c *gin.Context) {
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
err := c.ShouldBindQuery(&sortedPaginationRequest)
if err != nil {
_ = c.Error(err)
return
}
userID := c.GetString("userID") userID := c.GetString("userID")
// Fetch audit logs for the user // Fetch audit logs for the user
logs, pagination, err := alc.auditLogService.ListAuditLogsForUser(c.Request.Context(), userID, sortedPaginationRequest) logs, pagination, err := alc.auditLogService.ListAuditLogsForUser(c.Request.Context(), userID, listRequestOptions)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -86,26 +80,12 @@ func (alc *AuditLogController) listAuditLogsForUserHandler(c *gin.Context) {
// @Param pagination[limit] query int false "Number of items per page" default(20) // @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[column] query string false "Column to sort by"
// @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc") // @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc")
// @Param filters[userId] query string false "Filter by user ID"
// @Param filters[event] query string false "Filter by event type"
// @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) {
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
var filters dto.AuditLogFilterDto logs, pagination, err := alc.auditLogService.ListAllAuditLogs(c.Request.Context(), listRequestOptions)
if err := c.ShouldBindQuery(&filters); err != nil {
_ = c.Error(err)
return
}
logs, pagination, err := alc.auditLogService.ListAllAuditLogs(c.Request.Context(), sortedPaginationRequest, filters)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return

View File

@@ -5,6 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@@ -357,6 +358,7 @@ func (oc *OidcController) getClientMetaDataHandler(c *gin.Context) {
clientDto := dto.OidcClientMetaDataDto{} clientDto := dto.OidcClientMetaDataDto{}
err = dto.MapStruct(client, &clientDto) err = dto.MapStruct(client, &clientDto)
if err == nil { if err == nil {
clientDto.HasDarkLogo = client.HasDarkLogo()
c.JSON(http.StatusOK, clientDto) c.JSON(http.StatusOK, clientDto)
return return
} }
@@ -403,13 +405,9 @@ func (oc *OidcController) getClientHandler(c *gin.Context) {
// @Router /api/oidc/clients [get] // @Router /api/oidc/clients [get]
func (oc *OidcController) listClientsHandler(c *gin.Context) { func (oc *OidcController) listClientsHandler(c *gin.Context) {
searchTerm := c.Query("search") searchTerm := c.Query("search")
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
clients, pagination, err := oc.oidcService.ListClients(c.Request.Context(), searchTerm, sortedPaginationRequest) clients, pagination, err := oc.oidcService.ListClients(c.Request.Context(), searchTerm, listRequestOptions)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -423,6 +421,7 @@ func (oc *OidcController) listClientsHandler(c *gin.Context) {
_ = c.Error(err) _ = c.Error(err)
return return
} }
clientDto.HasDarkLogo = client.HasDarkLogo()
clientDto.AllowedUserGroupsCount, err = oc.oidcService.GetAllowedGroupsCountOfClient(c, client.ID) clientDto.AllowedUserGroupsCount, err = oc.oidcService.GetAllowedGroupsCountOfClient(c, client.ID)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
@@ -543,10 +542,13 @@ func (oc *OidcController) createClientSecretHandler(c *gin.Context) {
// @Produce image/jpeg // @Produce image/jpeg
// @Produce image/svg+xml // @Produce image/svg+xml
// @Param id path string true "Client ID" // @Param id path string true "Client ID"
// @Param light query boolean false "Light mode logo (true) or dark mode logo (false)"
// @Success 200 {file} binary "Logo image" // @Success 200 {file} binary "Logo image"
// @Router /api/oidc/clients/{id}/logo [get] // @Router /api/oidc/clients/{id}/logo [get]
func (oc *OidcController) getClientLogoHandler(c *gin.Context) { func (oc *OidcController) getClientLogoHandler(c *gin.Context) {
imagePath, mimeType, err := oc.oidcService.GetClientLogo(c.Request.Context(), c.Param("id")) lightLogo, _ := strconv.ParseBool(c.DefaultQuery("light", "true"))
imagePath, mimeType, err := oc.oidcService.GetClientLogo(c.Request.Context(), c.Param("id"), lightLogo)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -565,6 +567,7 @@ func (oc *OidcController) getClientLogoHandler(c *gin.Context) {
// @Accept multipart/form-data // @Accept multipart/form-data
// @Param id path string true "Client ID" // @Param id path string true "Client ID"
// @Param file formData file true "Logo image file (PNG, JPG, or SVG)" // @Param file formData file true "Logo image file (PNG, JPG, or SVG)"
// @Param light query boolean false "Light mode logo (true) or dark mode logo (false)"
// @Success 204 "No Content" // @Success 204 "No Content"
// @Router /api/oidc/clients/{id}/logo [post] // @Router /api/oidc/clients/{id}/logo [post]
func (oc *OidcController) updateClientLogoHandler(c *gin.Context) { func (oc *OidcController) updateClientLogoHandler(c *gin.Context) {
@@ -574,13 +577,16 @@ func (oc *OidcController) updateClientLogoHandler(c *gin.Context) {
return return
} }
err = oc.oidcService.UpdateClientLogo(c.Request.Context(), c.Param("id"), file) lightLogo, _ := strconv.ParseBool(c.DefaultQuery("light", "true"))
err = oc.oidcService.UpdateClientLogo(c.Request.Context(), c.Param("id"), file, lightLogo)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
} }
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
} }
// deleteClientLogoHandler godoc // deleteClientLogoHandler godoc
@@ -588,16 +594,26 @@ func (oc *OidcController) updateClientLogoHandler(c *gin.Context) {
// @Description Delete the logo for an OIDC client // @Description Delete the logo for an OIDC client
// @Tags OIDC // @Tags OIDC
// @Param id path string true "Client ID" // @Param id path string true "Client ID"
// @Param light query boolean false "Light mode logo (true) or dark mode logo (false)"
// @Success 204 "No Content" // @Success 204 "No Content"
// @Router /api/oidc/clients/{id}/logo [delete] // @Router /api/oidc/clients/{id}/logo [delete]
func (oc *OidcController) deleteClientLogoHandler(c *gin.Context) { func (oc *OidcController) deleteClientLogoHandler(c *gin.Context) {
err := oc.oidcService.DeleteClientLogo(c.Request.Context(), c.Param("id")) var err error
lightLogo, _ := strconv.ParseBool(c.DefaultQuery("light", "true"))
if lightLogo {
err = oc.oidcService.DeleteClientLogo(c.Request.Context(), c.Param("id"))
} else {
err = oc.oidcService.DeleteClientDarkLogo(c.Request.Context(), c.Param("id"))
}
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
} }
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
} }
// updateAllowedUserGroupsHandler godoc // updateAllowedUserGroupsHandler godoc
@@ -628,6 +644,7 @@ func (oc *OidcController) updateAllowedUserGroupsHandler(c *gin.Context) {
_ = c.Error(err) _ = c.Error(err)
return return
} }
oidcClientDto.HasDarkLogo = oidcClient.HasDarkLogo()
c.JSON(http.StatusOK, oidcClientDto) c.JSON(http.StatusOK, oidcClientDto)
} }
@@ -685,12 +702,9 @@ func (oc *OidcController) listAuthorizedClientsHandler(c *gin.Context) {
} }
func (oc *OidcController) listAuthorizedClients(c *gin.Context, userID string) { func (oc *OidcController) listAuthorizedClients(c *gin.Context, userID string) {
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err) authorizedClients, pagination, err := oc.oidcService.ListAuthorizedClients(c.Request.Context(), userID, listRequestOptions)
return
}
authorizedClients, pagination, err := oc.oidcService.ListAuthorizedClients(c.Request.Context(), userID, sortedPaginationRequest)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -741,15 +755,11 @@ func (oc *OidcController) revokeOwnClientAuthorizationHandler(c *gin.Context) {
// @Success 200 {object} dto.Paginated[dto.AccessibleOidcClientDto] // @Success 200 {object} dto.Paginated[dto.AccessibleOidcClientDto]
// @Router /api/oidc/users/me/clients [get] // @Router /api/oidc/users/me/clients [get]
func (oc *OidcController) listOwnAccessibleClientsHandler(c *gin.Context) { func (oc *OidcController) listOwnAccessibleClientsHandler(c *gin.Context) {
listRequestOptions := utils.ParseListRequestOptions(c)
userID := c.GetString("userID") userID := c.GetString("userID")
var sortedPaginationRequest utils.SortedPaginationRequest clients, pagination, err := oc.oidcService.ListAccessibleOidcClients(c.Request.Context(), userID, listRequestOptions)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
clients, pagination, err := oc.oidcService.ListAccessibleOidcClients(c.Request.Context(), userID, sortedPaginationRequest)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return

View File

@@ -104,13 +104,9 @@ func (uc *UserController) getUserGroupsHandler(c *gin.Context) {
// @Router /api/users [get] // @Router /api/users [get]
func (uc *UserController) listUsersHandler(c *gin.Context) { func (uc *UserController) listUsersHandler(c *gin.Context) {
searchTerm := c.Query("search") searchTerm := c.Query("search")
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
users, pagination, err := uc.userService.ListUsers(c.Request.Context(), searchTerm, sortedPaginationRequest) users, pagination, err := uc.userService.ListUsers(c.Request.Context(), searchTerm, listRequestOptions)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -574,13 +570,9 @@ func (uc *UserController) createSignupTokenHandler(c *gin.Context) {
// @Success 200 {object} dto.Paginated[dto.SignupTokenDto] // @Success 200 {object} dto.Paginated[dto.SignupTokenDto]
// @Router /api/signup-tokens [get] // @Router /api/signup-tokens [get]
func (uc *UserController) listSignupTokensHandler(c *gin.Context) { func (uc *UserController) listSignupTokensHandler(c *gin.Context) {
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
tokens, pagination, err := uc.userService.ListSignupTokens(c.Request.Context(), sortedPaginationRequest) tokens, pagination, err := uc.userService.ListSignupTokens(c.Request.Context(), listRequestOptions)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return

View File

@@ -47,16 +47,10 @@ type UserGroupController struct {
// @Success 200 {object} dto.Paginated[dto.UserGroupDtoWithUserCount] // @Success 200 {object} dto.Paginated[dto.UserGroupDtoWithUserCount]
// @Router /api/user-groups [get] // @Router /api/user-groups [get]
func (ugc *UserGroupController) list(c *gin.Context) { func (ugc *UserGroupController) list(c *gin.Context) {
ctx := c.Request.Context()
searchTerm := c.Query("search") searchTerm := c.Query("search")
var sortedPaginationRequest utils.SortedPaginationRequest listRequestOptions := utils.ParseListRequestOptions(c)
if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil {
_ = c.Error(err)
return
}
groups, pagination, err := ugc.UserGroupService.List(ctx, searchTerm, sortedPaginationRequest) groups, pagination, err := ugc.UserGroupService.List(c, searchTerm, listRequestOptions)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
@@ -70,7 +64,7 @@ func (ugc *UserGroupController) list(c *gin.Context) {
_ = c.Error(err) _ = c.Error(err)
return return
} }
groupDto.UserCount, err = ugc.UserGroupService.GetUserCountOfGroup(ctx, group.ID) groupDto.UserCount, err = ugc.UserGroupService.GetUserCountOfGroup(c.Request.Context(), group.ID)
if err != nil { if err != nil {
_ = c.Error(err) _ = c.Error(err)
return return

View File

@@ -17,10 +17,3 @@ type AuditLogDto struct {
Username string `json:"username"` Username string `json:"username"`
Data map[string]string `json:"data"` Data map[string]string `json:"data"`
} }
type AuditLogFilterDto struct {
UserID string `form:"filters[userId]"`
Event string `form:"filters[event]"`
ClientName string `form:"filters[clientName]"`
Location string `form:"filters[location]"`
}

View File

@@ -6,6 +6,7 @@ type OidcClientMetaDataDto struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
HasLogo bool `json:"hasLogo"` HasLogo bool `json:"hasLogo"`
HasDarkLogo bool `json:"hasDarkLogo"`
LaunchURL *string `json:"launchURL"` LaunchURL *string `json:"launchURL"`
RequiresReauthentication bool `json:"requiresReauthentication"` RequiresReauthentication bool `json:"requiresReauthentication"`
} }
@@ -39,7 +40,9 @@ type OidcClientUpdateDto struct {
Credentials OidcClientCredentialsDto `json:"credentials"` Credentials OidcClientCredentialsDto `json:"credentials"`
LaunchURL *string `json:"launchURL" binding:"omitempty,url"` LaunchURL *string `json:"launchURL" binding:"omitempty,url"`
HasLogo bool `json:"hasLogo"` HasLogo bool `json:"hasLogo"`
HasDarkLogo bool `json:"hasDarkLogo"`
LogoURL *string `json:"logoUrl"` LogoURL *string `json:"logoUrl"`
DarkLogoURL *string `json:"darkLogoUrl"`
} }
type OidcClientCreateDto struct { type OidcClientCreateDto struct {

View File

@@ -3,13 +3,14 @@ package model
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"fmt"
"github.com/pocket-id/pocket-id/backend/internal/utils"
) )
type AuditLog struct { type AuditLog struct {
Base Base
Event AuditLogEvent `sortable:"true"` Event AuditLogEvent `sortable:"true" filterable:"true"`
IpAddress *string `sortable:"true"` IpAddress *string `sortable:"true"`
Country string `sortable:"true"` Country string `sortable:"true"`
City string `sortable:"true"` City string `sortable:"true"`
@@ -17,7 +18,7 @@ type AuditLog struct {
Username string `gorm:"-"` Username string `gorm:"-"`
Data AuditLogData Data AuditLogData
UserID string UserID string `filterable:"true"`
User User User User
} }
@@ -47,14 +48,7 @@ func (e AuditLogEvent) Value() (driver.Value, error) {
} }
func (d *AuditLogData) Scan(value any) error { func (d *AuditLogData) Scan(value any) error {
switch v := value.(type) { return utils.UnmarshalJSONFromDatabase(d, value)
case []byte:
return json.Unmarshal(v, d)
case string:
return json.Unmarshal([]byte(v), d)
default:
return fmt.Errorf("unsupported type: %T", value)
}
} }
func (d AuditLogData) Value() (driver.Value, error) { func (d AuditLogData) Value() (driver.Value, error) {

View File

@@ -3,10 +3,10 @@ package model
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
) )
type UserAuthorizedOidcClient struct { type UserAuthorizedOidcClient struct {
@@ -52,9 +52,10 @@ type OidcClient struct {
CallbackURLs UrlList CallbackURLs UrlList
LogoutCallbackURLs UrlList LogoutCallbackURLs UrlList
ImageType *string ImageType *string
DarkImageType *string
IsPublic bool IsPublic bool
PkceEnabled bool PkceEnabled bool `sortable:"true" filterable:"true"`
RequiresReauthentication bool RequiresReauthentication bool `sortable:"true" filterable:"true"`
Credentials OidcClientCredentials Credentials OidcClientCredentials
LaunchURL *string LaunchURL *string
@@ -68,6 +69,10 @@ func (c OidcClient) HasLogo() bool {
return c.ImageType != nil && *c.ImageType != "" return c.ImageType != nil && *c.ImageType != ""
} }
func (c OidcClient) HasDarkLogo() bool {
return c.DarkImageType != nil && *c.DarkImageType != ""
}
type OidcRefreshToken struct { type OidcRefreshToken struct {
Base Base
@@ -116,14 +121,7 @@ func (occ OidcClientCredentials) FederatedIdentityForIssuer(issuer string) (Oidc
} }
func (occ *OidcClientCredentials) Scan(value any) error { func (occ *OidcClientCredentials) Scan(value any) error {
switch v := value.(type) { return utils.UnmarshalJSONFromDatabase(occ, value)
case []byte:
return json.Unmarshal(v, occ)
case string:
return json.Unmarshal([]byte(v), occ)
default:
return fmt.Errorf("unsupported type: %T", value)
}
} }
func (occ OidcClientCredentials) Value() (driver.Value, error) { func (occ OidcClientCredentials) Value() (driver.Value, error) {
@@ -133,14 +131,7 @@ func (occ OidcClientCredentials) Value() (driver.Value, error) {
type UrlList []string //nolint:recvcheck type UrlList []string //nolint:recvcheck
func (cu *UrlList) Scan(value any) error { func (cu *UrlList) Scan(value any) error {
switch v := value.(type) { return utils.UnmarshalJSONFromDatabase(cu, value)
case []byte:
return json.Unmarshal(v, cu)
case string:
return json.Unmarshal([]byte(v), cu)
default:
return fmt.Errorf("unsupported type: %T", value)
}
} }
func (cu UrlList) Value() (driver.Value, error) { func (cu UrlList) Value() (driver.Value, error) {

View File

@@ -18,10 +18,10 @@ type User struct {
FirstName string `sortable:"true"` FirstName string `sortable:"true"`
LastName string `sortable:"true"` LastName string `sortable:"true"`
DisplayName string `sortable:"true"` DisplayName string `sortable:"true"`
IsAdmin bool `sortable:"true"` IsAdmin bool `sortable:"true" filterable:"true"`
Locale *string Locale *string
LdapID *string LdapID *string
Disabled bool `sortable:"true"` Disabled bool `sortable:"true" filterable:"true"`
CustomClaims []CustomClaim CustomClaims []CustomClaim
UserGroups []UserGroup `gorm:"many2many:user_groups_users;"` UserGroups []UserGroup `gorm:"many2many:user_groups_users;"`

View File

@@ -3,11 +3,11 @@ package model
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
) )
type WebauthnSession struct { type WebauthnSession struct {
@@ -16,6 +16,7 @@ type WebauthnSession struct {
Challenge string Challenge string
ExpiresAt datatype.DateTime ExpiresAt datatype.DateTime
UserVerification string UserVerification string
CredentialParams CredentialParameters
} }
type WebauthnCredential struct { type WebauthnCredential struct {
@@ -58,16 +59,20 @@ type AuthenticatorTransportList []protocol.AuthenticatorTransport //nolint:recvc
// Scan and Value methods for GORM to handle the custom type // Scan and Value methods for GORM to handle the custom type
func (atl *AuthenticatorTransportList) Scan(value interface{}) error { func (atl *AuthenticatorTransportList) Scan(value interface{}) error {
switch v := value.(type) { return utils.UnmarshalJSONFromDatabase(atl, value)
case []byte:
return json.Unmarshal(v, atl)
case string:
return json.Unmarshal([]byte(v), atl)
default:
return fmt.Errorf("unsupported type: %T", value)
}
} }
func (atl AuthenticatorTransportList) Value() (driver.Value, error) { func (atl AuthenticatorTransportList) Value() (driver.Value, error) {
return json.Marshal(atl) return json.Marshal(atl)
} }
type CredentialParameters []protocol.CredentialParameter //nolint:recvcheck
// Scan and Value methods for GORM to handle the custom type
func (cp *CredentialParameters) Scan(value interface{}) error {
return utils.UnmarshalJSONFromDatabase(cp, value)
}
func (cp CredentialParameters) Value() (driver.Value, error) {
return json.Marshal(cp)
}

View File

@@ -25,14 +25,14 @@ func NewApiKeyService(db *gorm.DB, emailService *EmailService) *ApiKeyService {
return &ApiKeyService{db: db, emailService: emailService} return &ApiKeyService{db: db, emailService: emailService}
} }
func (s *ApiKeyService) ListApiKeys(ctx context.Context, userID string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.ApiKey, utils.PaginationResponse, error) { func (s *ApiKeyService) ListApiKeys(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.ApiKey, utils.PaginationResponse, error) {
query := s.db. query := s.db.
WithContext(ctx). WithContext(ctx).
Where("user_id = ?", userID). Where("user_id = ?", userID).
Model(&model.ApiKey{}) Model(&model.ApiKey{})
var apiKeys []model.ApiKey var apiKeys []model.ApiKey
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &apiKeys) pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &apiKeys)
if err != nil { if err != nil {
return nil, utils.PaginationResponse{}, err return nil, utils.PaginationResponse{}, err
} }

View File

@@ -48,7 +48,7 @@ func (s *AppImagesService) UpdateImage(file *multipart.FileHeader, imageName str
currentExt, ok := s.extensions[imageName] currentExt, ok := s.extensions[imageName]
if !ok { if !ok {
return fmt.Errorf("unknown application image '%s'", imageName) s.extensions[imageName] = fileType
} }
imagePath := filepath.Join(common.EnvConfig.UploadPath, "application-images", fmt.Sprintf("%s.%s", imageName, fileType)) imagePath := filepath.Join(common.EnvConfig.UploadPath, "application-images", fmt.Sprintf("%s.%s", imageName, fileType))
@@ -69,13 +69,39 @@ func (s *AppImagesService) UpdateImage(file *multipart.FileHeader, imageName str
return nil return nil
} }
func (s *AppImagesService) DeleteImage(imageName string) error {
s.mu.Lock()
defer s.mu.Unlock()
ext, ok := s.extensions[imageName]
if !ok || ext == "" {
return &common.ImageNotFoundError{}
}
imagePath := filepath.Join(common.EnvConfig.UploadPath, "application-images", imageName+"."+ext)
if err := os.Remove(imagePath); err != nil && !os.IsNotExist(err) {
return err
}
delete(s.extensions, imageName)
return nil
}
func (s *AppImagesService) IsDefaultProfilePictureSet() bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, ok := s.extensions["default-profile-picture"]
return ok
}
func (s *AppImagesService) getExtension(name string) (string, error) { func (s *AppImagesService) getExtension(name string) (string, error) {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
ext, ok := s.extensions[name] ext, ok := s.extensions[name]
if !ok || ext == "" { if !ok || ext == "" {
return "", fmt.Errorf("unknown application image '%s'", name) return "", &common.ImageNotFoundError{}
} }
return strings.ToLower(ext), nil return strings.ToLower(ext), nil

View File

@@ -6,7 +6,6 @@ import (
"log/slog" "log/slog"
userAgentParser "github.com/mileusna/useragent" userAgentParser "github.com/mileusna/useragent"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/model" "github.com/pocket-id/pocket-id/backend/internal/model"
"github.com/pocket-id/pocket-id/backend/internal/utils" "github.com/pocket-id/pocket-id/backend/internal/utils"
"github.com/pocket-id/pocket-id/backend/internal/utils/email" "github.com/pocket-id/pocket-id/backend/internal/utils/email"
@@ -136,14 +135,14 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ctx context.Context, ipAddres
} }
// ListAuditLogsForUser retrieves all audit logs for a given user ID // ListAuditLogsForUser retrieves all audit logs for a given user ID
func (s *AuditLogService) ListAuditLogsForUser(ctx context.Context, userID string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.AuditLog, utils.PaginationResponse, error) { func (s *AuditLogService) ListAuditLogsForUser(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.AuditLog, utils.PaginationResponse, error) {
var logs []model.AuditLog var logs []model.AuditLog
query := s.db. query := s.db.
WithContext(ctx). WithContext(ctx).
Model(&model.AuditLog{}). Model(&model.AuditLog{}).
Where("user_id = ?", userID) Where("user_id = ?", userID)
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs) pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &logs)
return logs, pagination, err return logs, pagination, err
} }
@@ -152,7 +151,7 @@ func (s *AuditLogService) DeviceStringFromUserAgent(userAgent string) string {
return ua.Name + " on " + ua.OS + " " + ua.OSVersion return ua.Name + " on " + ua.OS + " " + ua.OSVersion
} }
func (s *AuditLogService) ListAllAuditLogs(ctx context.Context, sortedPaginationRequest utils.SortedPaginationRequest, filters dto.AuditLogFilterDto) ([]model.AuditLog, utils.PaginationResponse, error) { func (s *AuditLogService) ListAllAuditLogs(ctx context.Context, listRequestOptions utils.ListRequestOptions) ([]model.AuditLog, utils.PaginationResponse, error) {
var logs []model.AuditLog var logs []model.AuditLog
query := s.db. query := s.db.
@@ -160,33 +159,36 @@ func (s *AuditLogService) ListAllAuditLogs(ctx context.Context, sortedPagination
Preload("User"). Preload("User").
Model(&model.AuditLog{}) Model(&model.AuditLog{})
if filters.UserID != "" { if clientName, ok := listRequestOptions.Filters["clientName"]; ok {
query = query.Where("user_id = ?", filters.UserID)
}
if filters.Event != "" {
query = query.Where("event = ?", filters.Event)
}
if filters.ClientName != "" {
dialect := s.db.Name() dialect := s.db.Name()
switch dialect { switch dialect {
case "sqlite": case "sqlite":
query = query.Where("json_extract(data, '$.clientName') = ?", filters.ClientName) query = query.Where("json_extract(data, '$.clientName') IN ?", clientName)
case "postgres": case "postgres":
query = query.Where("data->>'clientName' = ?", filters.ClientName) query = query.Where("data->>'clientName' IN ?", clientName)
default: default:
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 { if locations, ok := listRequestOptions.Filters["location"]; ok {
case "external": mapped := make([]string, 0, len(locations))
query = query.Where("country != 'Internal Network'") for _, v := range locations {
case "internal": if s, ok := v.(string); ok {
query = query.Where("country = 'Internal Network'") switch s {
case "internal":
mapped = append(mapped, "Internal Network")
case "external":
mapped = append(mapped, "External Network")
}
}
}
if len(mapped) > 0 {
query = query.Where("country IN ?", mapped)
} }
} }
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &logs) pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &logs)
if err != nil { if err != nil {
return nil, pagination, err return nil, pagination, err
} }

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"crypto/subtle"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@@ -394,7 +395,7 @@ func (s *OidcService) createTokenFromAuthorizationCode(ctx context.Context, inpu
// If the client is public or PKCE is enabled, the code verifier must match the code challenge // If the client is public or PKCE is enabled, the code verifier must match the code challenge
if client.IsPublic || client.PkceEnabled { if client.IsPublic || client.PkceEnabled {
if !s.validateCodeVerifier(input.CodeVerifier, *authorizationCodeMetaData.CodeChallenge, *authorizationCodeMetaData.CodeChallengeMethodSha256) { if !validateCodeVerifier(input.CodeVerifier, *authorizationCodeMetaData.CodeChallenge, *authorizationCodeMetaData.CodeChallengeMethodSha256) {
return CreatedTokens{}, &common.OidcInvalidCodeVerifierError{} return CreatedTokens{}, &common.OidcInvalidCodeVerifierError{}
} }
} }
@@ -692,7 +693,7 @@ func (s *OidcService) getClientInternal(ctx context.Context, clientID string, tx
return client, nil return client, nil
} }
func (s *OidcService) ListClients(ctx context.Context, name string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.OidcClient, utils.PaginationResponse, error) { func (s *OidcService) ListClients(ctx context.Context, name string, listRequestOptions utils.ListRequestOptions) ([]model.OidcClient, utils.PaginationResponse, error) {
var clients []model.OidcClient var clients []model.OidcClient
query := s.db. query := s.db.
@@ -705,17 +706,17 @@ func (s *OidcService) ListClients(ctx context.Context, name string, sortedPagina
} }
// As allowedUserGroupsCount is not a column, we need to manually sort it // As allowedUserGroupsCount is not a column, we need to manually sort it
if sortedPaginationRequest.Sort.Column == "allowedUserGroupsCount" && utils.IsValidSortDirection(sortedPaginationRequest.Sort.Direction) { if listRequestOptions.Sort.Column == "allowedUserGroupsCount" && utils.IsValidSortDirection(listRequestOptions.Sort.Direction) {
query = query.Select("oidc_clients.*, COUNT(oidc_clients_allowed_user_groups.oidc_client_id)"). query = query.Select("oidc_clients.*, COUNT(oidc_clients_allowed_user_groups.oidc_client_id)").
Joins("LEFT JOIN oidc_clients_allowed_user_groups ON oidc_clients.id = oidc_clients_allowed_user_groups.oidc_client_id"). Joins("LEFT JOIN oidc_clients_allowed_user_groups ON oidc_clients.id = oidc_clients_allowed_user_groups.oidc_client_id").
Group("oidc_clients.id"). Group("oidc_clients.id").
Order("COUNT(oidc_clients_allowed_user_groups.oidc_client_id) " + sortedPaginationRequest.Sort.Direction) Order("COUNT(oidc_clients_allowed_user_groups.oidc_client_id) " + listRequestOptions.Sort.Direction)
response, err := utils.Paginate(sortedPaginationRequest.Pagination.Page, sortedPaginationRequest.Pagination.Limit, query, &clients) response, err := utils.Paginate(listRequestOptions.Pagination.Page, listRequestOptions.Pagination.Limit, query, &clients)
return clients, response, err return clients, response, err
} }
response, err := utils.PaginateAndSort(sortedPaginationRequest, query, &clients) response, err := utils.PaginateFilterAndSort(listRequestOptions, query, &clients)
return clients, response, err return clients, response, err
} }
@@ -745,12 +746,19 @@ func (s *OidcService) CreateClient(ctx context.Context, input dto.OidcClientCrea
} }
if input.LogoURL != nil { if input.LogoURL != nil {
err = s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.LogoURL) err = s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.LogoURL, true)
if err != nil { if err != nil {
return model.OidcClient{}, fmt.Errorf("failed to download logo: %w", err) return model.OidcClient{}, fmt.Errorf("failed to download logo: %w", err)
} }
} }
if input.DarkLogoURL != nil {
err = s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.DarkLogoURL, false)
if err != nil {
return model.OidcClient{}, fmt.Errorf("failed to download dark logo: %w", err)
}
}
err = tx.Commit().Error err = tx.Commit().Error
if err != nil { if err != nil {
return model.OidcClient{}, err return model.OidcClient{}, err
@@ -777,12 +785,19 @@ func (s *OidcService) UpdateClient(ctx context.Context, clientID string, input d
} }
if input.LogoURL != nil { if input.LogoURL != nil {
err := s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.LogoURL) err := s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.LogoURL, true)
if err != nil { if err != nil {
return model.OidcClient{}, fmt.Errorf("failed to download logo: %w", err) return model.OidcClient{}, fmt.Errorf("failed to download logo: %w", err)
} }
} }
if input.DarkLogoURL != nil {
err := s.downloadAndSaveLogoFromURL(ctx, tx, client.ID, *input.DarkLogoURL, false)
if err != nil {
return model.OidcClient{}, fmt.Errorf("failed to download dark logo: %w", err)
}
}
if err := tx.Commit().Error; err != nil { if err := tx.Commit().Error; err != nil {
return model.OidcClient{}, err return model.OidcClient{}, err
} }
@@ -869,7 +884,7 @@ func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string) (
return clientSecret, nil return clientSecret, nil
} }
func (s *OidcService) GetClientLogo(ctx context.Context, clientID string) (string, string, error) { func (s *OidcService) GetClientLogo(ctx context.Context, clientID string, light bool) (string, string, error) {
var client model.OidcClient var client model.OidcClient
err := s.db. err := s.db.
WithContext(ctx). WithContext(ctx).
@@ -879,23 +894,38 @@ func (s *OidcService) GetClientLogo(ctx context.Context, clientID string) (strin
return "", "", err return "", "", err
} }
if client.ImageType == nil { var imagePath, mimeType string
switch {
case !light && client.DarkImageType != nil:
// Dark logo if requested and exists
imagePath = common.EnvConfig.UploadPath + "/oidc-client-images/" + client.ID + "-dark." + *client.DarkImageType
mimeType = utils.GetImageMimeType(*client.DarkImageType)
case client.ImageType != nil:
// Light logo if requested or no dark logo is available
imagePath = common.EnvConfig.UploadPath + "/oidc-client-images/" + client.ID + "." + *client.ImageType
mimeType = utils.GetImageMimeType(*client.ImageType)
default:
return "", "", errors.New("image not found") return "", "", errors.New("image not found")
} }
imagePath := common.EnvConfig.UploadPath + "/oidc-client-images/" + client.ID + "." + *client.ImageType
mimeType := utils.GetImageMimeType(*client.ImageType)
return imagePath, mimeType, nil return imagePath, mimeType, nil
} }
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, light bool) error {
fileType := strings.ToLower(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{}
} }
imagePath := common.EnvConfig.UploadPath + "/oidc-client-images/" + clientID + "." + fileType var darkSuffix string
if !light {
darkSuffix = "-dark"
}
imagePath := common.EnvConfig.UploadPath + "/oidc-client-images/" + clientID + darkSuffix + "." + fileType
err := utils.SaveFile(file, imagePath) err := utils.SaveFile(file, imagePath)
if err != nil { if err != nil {
return err return err
@@ -903,7 +933,7 @@ func (s *OidcService) UpdateClientLogo(ctx context.Context, clientID string, fil
tx := s.db.Begin() tx := s.db.Begin()
err = s.updateClientLogoType(ctx, tx, clientID, fileType) err = s.updateClientLogoType(ctx, tx, clientID, fileType, light)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return err return err
@@ -955,6 +985,49 @@ func (s *OidcService) DeleteClientLogo(ctx context.Context, clientID string) err
return nil return nil
} }
func (s *OidcService) DeleteClientDarkLogo(ctx context.Context, clientID string) error {
tx := s.db.Begin()
defer func() {
tx.Rollback()
}()
var client model.OidcClient
err := tx.
WithContext(ctx).
First(&client, "id = ?", clientID).
Error
if err != nil {
return err
}
if client.DarkImageType == nil {
return errors.New("image not found")
}
oldImageType := *client.DarkImageType
client.DarkImageType = nil
err = tx.
WithContext(ctx).
Save(&client).
Error
if err != nil {
return err
}
imagePath := common.EnvConfig.UploadPath + "/oidc-client-images/" + client.ID + "-dark." + oldImageType
if err := os.Remove(imagePath); err != nil {
return err
}
err = tx.Commit().Error
if err != nil {
return err
}
return nil
}
func (s *OidcService) UpdateAllowedUserGroups(ctx context.Context, id string, input dto.OidcUpdateAllowedUserGroupsDto) (client model.OidcClient, err error) { func (s *OidcService) UpdateAllowedUserGroups(ctx context.Context, id string, input dto.OidcUpdateAllowedUserGroupsDto) (client model.OidcClient, err error) {
tx := s.db.Begin() tx := s.db.Begin()
defer func() { defer func() {
@@ -1083,13 +1156,20 @@ func (s *OidcService) createAuthorizationCode(ctx context.Context, clientID stri
return randomString, nil return randomString, nil
} }
func (s *OidcService) validateCodeVerifier(codeVerifier, codeChallenge string, codeChallengeMethodSha256 bool) bool { func validateCodeVerifier(codeVerifier, codeChallenge string, codeChallengeMethodSha256 bool) bool {
if codeVerifier == "" || codeChallenge == "" { if codeVerifier == "" || codeChallenge == "" {
return false return false
} }
if !codeChallengeMethodSha256 { if !codeChallengeMethodSha256 {
return codeVerifier == codeChallenge return subtle.ConstantTimeCompare([]byte(codeVerifier), []byte(codeChallenge)) == 1
}
// Base64 URL decode the challenge
// If it's not valid base64url, fail the operation
codeChallengeBytes, err := base64.RawURLEncoding.DecodeString(codeChallenge)
if err != nil {
return false
} }
// Compute SHA-256 hash of the codeVerifier // Compute SHA-256 hash of the codeVerifier
@@ -1097,10 +1177,7 @@ func (s *OidcService) validateCodeVerifier(codeVerifier, codeChallenge string, c
h.Write([]byte(codeVerifier)) h.Write([]byte(codeVerifier))
codeVerifierHash := h.Sum(nil) codeVerifierHash := h.Sum(nil)
// Base64 URL encode the verifier hash return subtle.ConstantTimeCompare(codeVerifierHash, codeChallengeBytes) == 1
encodedVerifierHash := base64.RawURLEncoding.EncodeToString(codeVerifierHash)
return encodedVerifierHash == codeChallenge
} }
func (s *OidcService) getCallbackURL(client *model.OidcClient, inputCallbackURL string, tx *gorm.DB, ctx context.Context) (callbackURL string, err error) { func (s *OidcService) getCallbackURL(client *model.OidcClient, inputCallbackURL string, tx *gorm.DB, ctx context.Context) (callbackURL string, err error) {
@@ -1324,9 +1401,10 @@ func (s *OidcService) GetDeviceCodeInfo(ctx context.Context, userCode string, us
return &dto.DeviceCodeInfoDto{ return &dto.DeviceCodeInfoDto{
Client: dto.OidcClientMetaDataDto{ Client: dto.OidcClientMetaDataDto{
ID: deviceAuth.Client.ID, ID: deviceAuth.Client.ID,
Name: deviceAuth.Client.Name, Name: deviceAuth.Client.Name,
HasLogo: deviceAuth.Client.HasLogo(), HasLogo: deviceAuth.Client.HasLogo(),
HasDarkLogo: deviceAuth.Client.HasDarkLogo(),
}, },
Scope: deviceAuth.Scope, Scope: deviceAuth.Scope,
AuthorizationRequired: !hasAuthorizedClient, AuthorizationRequired: !hasAuthorizedClient,
@@ -1350,7 +1428,7 @@ func (s *OidcService) GetAllowedGroupsCountOfClient(ctx context.Context, id stri
return count, nil return count, nil
} }
func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) { func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) {
query := s.db. query := s.db.
WithContext(ctx). WithContext(ctx).
@@ -1359,7 +1437,7 @@ func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string,
Where("user_id = ?", userID) Where("user_id = ?", userID)
var authorizedClients []model.UserAuthorizedOidcClient var authorizedClients []model.UserAuthorizedOidcClient
response, err := utils.PaginateAndSort(sortedPaginationRequest, query, &authorizedClients) response, err := utils.PaginateFilterAndSort(listRequestOptions, query, &authorizedClients)
return authorizedClients, response, err return authorizedClients, response, err
} }
@@ -1392,7 +1470,7 @@ func (s *OidcService) RevokeAuthorizedClient(ctx context.Context, userID string,
return nil return nil
} }
func (s *OidcService) ListAccessibleOidcClients(ctx context.Context, userID string, sortedPaginationRequest utils.SortedPaginationRequest) ([]dto.AccessibleOidcClientDto, utils.PaginationResponse, error) { func (s *OidcService) ListAccessibleOidcClients(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]dto.AccessibleOidcClientDto, utils.PaginationResponse, error) {
tx := s.db.Begin() tx := s.db.Begin()
defer func() { defer func() {
tx.Rollback() tx.Rollback()
@@ -1439,13 +1517,13 @@ func (s *OidcService) ListAccessibleOidcClients(ctx context.Context, userID stri
// Handle custom sorting for lastUsedAt column // Handle custom sorting for lastUsedAt column
var response utils.PaginationResponse var response utils.PaginationResponse
if sortedPaginationRequest.Sort.Column == "lastUsedAt" && utils.IsValidSortDirection(sortedPaginationRequest.Sort.Direction) { if listRequestOptions.Sort.Column == "lastUsedAt" && utils.IsValidSortDirection(listRequestOptions.Sort.Direction) {
query = query. query = query.
Joins("LEFT JOIN user_authorized_oidc_clients ON oidc_clients.id = user_authorized_oidc_clients.client_id AND user_authorized_oidc_clients.user_id = ?", userID). Joins("LEFT JOIN user_authorized_oidc_clients ON oidc_clients.id = user_authorized_oidc_clients.client_id AND user_authorized_oidc_clients.user_id = ?", userID).
Order("user_authorized_oidc_clients.last_used_at " + sortedPaginationRequest.Sort.Direction + " NULLS LAST") Order("user_authorized_oidc_clients.last_used_at " + listRequestOptions.Sort.Direction + " NULLS LAST")
} }
response, err = utils.PaginateAndSort(sortedPaginationRequest, query, &clients) response, err = utils.PaginateFilterAndSort(listRequestOptions, query, &clients)
if err != nil { if err != nil {
return nil, utils.PaginationResponse{}, err return nil, utils.PaginationResponse{}, err
} }
@@ -1458,10 +1536,11 @@ func (s *OidcService) ListAccessibleOidcClients(ctx context.Context, userID stri
} }
dtos[i] = dto.AccessibleOidcClientDto{ dtos[i] = dto.AccessibleOidcClientDto{
OidcClientMetaDataDto: dto.OidcClientMetaDataDto{ OidcClientMetaDataDto: dto.OidcClientMetaDataDto{
ID: client.ID, ID: client.ID,
Name: client.Name, Name: client.Name,
LaunchURL: client.LaunchURL, LaunchURL: client.LaunchURL,
HasLogo: client.HasLogo(), HasLogo: client.HasLogo(),
HasDarkLogo: client.HasDarkLogo(),
}, },
LastUsedAt: lastUsedAt, LastUsedAt: lastUsedAt,
} }
@@ -1883,7 +1962,7 @@ func (s *OidcService) IsClientAccessibleToUser(ctx context.Context, clientID str
return s.IsUserGroupAllowedToAuthorize(user, client), nil return s.IsUserGroupAllowedToAuthorize(user, client), nil
} }
func (s *OidcService) downloadAndSaveLogoFromURL(parentCtx context.Context, tx *gorm.DB, clientID string, raw string) error { func (s *OidcService) downloadAndSaveLogoFromURL(parentCtx context.Context, tx *gorm.DB, clientID string, raw string, light bool) error {
u, err := url.Parse(raw) u, err := url.Parse(raw)
if err != nil { if err != nil {
return err return err
@@ -1944,31 +2023,51 @@ func (s *OidcService) downloadAndSaveLogoFromURL(parentCtx context.Context, tx *
return err return err
} }
imagePath := filepath.Join(folderPath, clientID+"."+ext) var darkSuffix string
if !light {
darkSuffix = "-dark"
}
imagePath := filepath.Join(folderPath, clientID+darkSuffix+"."+ext)
err = utils.SaveFileStream(io.LimitReader(resp.Body, maxLogoSize+1), imagePath) err = utils.SaveFileStream(io.LimitReader(resp.Body, maxLogoSize+1), imagePath)
if err != nil { if err != nil {
return err return err
} }
if err := s.updateClientLogoType(ctx, tx, clientID, ext); err != nil { if err := s.updateClientLogoType(ctx, tx, clientID, ext, light); err != nil {
return err return err
} }
return nil return nil
} }
func (s *OidcService) updateClientLogoType(ctx context.Context, tx *gorm.DB, clientID, ext string) error { func (s *OidcService) updateClientLogoType(ctx context.Context, tx *gorm.DB, clientID, ext string, light bool) error {
uploadsDir := common.EnvConfig.UploadPath + "/oidc-client-images" uploadsDir := common.EnvConfig.UploadPath + "/oidc-client-images"
var darkSuffix string
if !light {
darkSuffix = "-dark"
}
var client model.OidcClient var client model.OidcClient
if err := tx.WithContext(ctx).First(&client, "id = ?", clientID).Error; err != nil { if err := tx.WithContext(ctx).First(&client, "id = ?", clientID).Error; err != nil {
return err return err
} }
if client.ImageType != nil && *client.ImageType != ext { if client.ImageType != nil && *client.ImageType != ext {
old := fmt.Sprintf("%s/%s.%s", uploadsDir, client.ID, *client.ImageType) old := fmt.Sprintf("%s/%s%s.%s", uploadsDir, client.ID, darkSuffix, *client.ImageType)
_ = os.Remove(old) _ = os.Remove(old)
} }
client.ImageType = &ext
return tx.WithContext(ctx).Save(&client).Error
var column string
if light {
column = "image_type"
} else {
column = "dark_image_type"
}
return tx.WithContext(ctx).
Model(&model.OidcClient{}).
Where("id = ?", clientID).
Update(column, ext).
Error
} }

View File

@@ -5,6 +5,8 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json" "encoding/json"
"net/http" "net/http"
"testing" "testing"
@@ -510,3 +512,28 @@ func TestOidcService_verifyClientCredentialsInternal(t *testing.T) {
}) })
}) })
} }
func TestValidateCodeVerifier_Plain(t *testing.T) {
require.False(t, validateCodeVerifier("", "", false))
require.False(t, validateCodeVerifier("", "", true))
t.Run("plain", func(t *testing.T) {
require.False(t, validateCodeVerifier("", "challenge", false))
require.False(t, validateCodeVerifier("verifier", "", false))
require.True(t, validateCodeVerifier("plainVerifier", "plainVerifier", false))
require.False(t, validateCodeVerifier("plainVerifier", "otherVerifier", false))
})
t.Run("SHA 256", func(t *testing.T) {
codeVerifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
hash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:])
require.True(t, validateCodeVerifier(codeVerifier, codeChallenge, true))
require.False(t, validateCodeVerifier("wrongVerifier", codeChallenge, true))
require.False(t, validateCodeVerifier(codeVerifier, "!", true))
// Invalid base64
require.False(t, validateCodeVerifier("NOT!VALID", codeChallenge, true))
})
}

View File

@@ -21,7 +21,7 @@ func NewUserGroupService(db *gorm.DB, appConfigService *AppConfigService) *UserG
return &UserGroupService{db: db, appConfigService: appConfigService} return &UserGroupService{db: db, appConfigService: appConfigService}
} }
func (s *UserGroupService) List(ctx context.Context, name string, sortedPaginationRequest utils.SortedPaginationRequest) (groups []model.UserGroup, response utils.PaginationResponse, err error) { func (s *UserGroupService) List(ctx context.Context, name string, listRequestOptions utils.ListRequestOptions) (groups []model.UserGroup, response utils.PaginationResponse, err error) {
query := s.db. query := s.db.
WithContext(ctx). WithContext(ctx).
Preload("CustomClaims"). Preload("CustomClaims").
@@ -32,17 +32,14 @@ func (s *UserGroupService) List(ctx context.Context, name string, sortedPaginati
} }
// As userCount is not a column we need to manually sort it // As userCount is not a column we need to manually sort it
if sortedPaginationRequest.Sort.Column == "userCount" && utils.IsValidSortDirection(sortedPaginationRequest.Sort.Direction) { if listRequestOptions.Sort.Column == "userCount" && utils.IsValidSortDirection(listRequestOptions.Sort.Direction) {
query = query.Select("user_groups.*, COUNT(user_groups_users.user_id)"). query = query.Select("user_groups.*, COUNT(user_groups_users.user_id)").
Joins("LEFT JOIN user_groups_users ON user_groups.id = user_groups_users.user_group_id"). Joins("LEFT JOIN user_groups_users ON user_groups.id = user_groups_users.user_group_id").
Group("user_groups.id"). Group("user_groups.id").
Order("COUNT(user_groups_users.user_id) " + sortedPaginationRequest.Sort.Direction) Order("COUNT(user_groups_users.user_id) " + listRequestOptions.Sort.Direction)
response, err := utils.Paginate(sortedPaginationRequest.Pagination.Page, sortedPaginationRequest.Pagination.Limit, query, &groups)
return groups, response, err
} }
response, err = utils.PaginateAndSort(sortedPaginationRequest, query, &groups) response, err = utils.PaginateFilterAndSort(listRequestOptions, query, &groups)
return groups, response, err return groups, response, err
} }

View File

@@ -10,6 +10,7 @@ import (
"log/slog" "log/slog"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@@ -33,9 +34,10 @@ type UserService struct {
emailService *EmailService emailService *EmailService
appConfigService *AppConfigService appConfigService *AppConfigService
customClaimService *CustomClaimService customClaimService *CustomClaimService
appImagesService *AppImagesService
} }
func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditLogService, emailService *EmailService, appConfigService *AppConfigService, customClaimService *CustomClaimService) *UserService { func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditLogService, emailService *EmailService, appConfigService *AppConfigService, customClaimService *CustomClaimService, appImagesService *AppImagesService) *UserService {
return &UserService{ return &UserService{
db: db, db: db,
jwtService: jwtService, jwtService: jwtService,
@@ -43,10 +45,11 @@ func NewUserService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditL
emailService: emailService, emailService: emailService,
appConfigService: appConfigService, appConfigService: appConfigService,
customClaimService: customClaimService, customClaimService: customClaimService,
appImagesService: appImagesService,
} }
} }
func (s *UserService) ListUsers(ctx context.Context, searchTerm string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.User, utils.PaginationResponse, error) { func (s *UserService) ListUsers(ctx context.Context, searchTerm string, listRequestOptions utils.ListRequestOptions) ([]model.User, utils.PaginationResponse, error) {
var users []model.User var users []model.User
query := s.db.WithContext(ctx). query := s.db.WithContext(ctx).
Model(&model.User{}). Model(&model.User{}).
@@ -60,7 +63,7 @@ func (s *UserService) ListUsers(ctx context.Context, searchTerm string, sortedPa
searchPattern, searchPattern, searchPattern, searchPattern) searchPattern, searchPattern, searchPattern, searchPattern)
} }
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &users) pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &users)
return users, pagination, err return users, pagination, err
} }
@@ -87,39 +90,42 @@ func (s *UserService) GetProfilePicture(ctx context.Context, userID string) (io.
return nil, 0, &common.InvalidUUIDError{} return nil, 0, &common.InvalidUUIDError{}
} }
// First check for a custom uploaded profile picture (userID.png)
profilePicturePath := common.EnvConfig.UploadPath + "/profile-pictures/" + userID + ".png"
file, err := os.Open(profilePicturePath)
if err == nil {
// Get the file size
fileInfo, err := file.Stat()
if err != nil {
file.Close()
return nil, 0, err
}
return file, fileInfo.Size(), nil
}
// If no custom picture exists, get the user's data for creating initials
user, err := s.GetUser(ctx, userID) user, err := s.GetUser(ctx, userID)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
// Check if we have a cached default picture for these initials profilePicturePath := filepath.Join(common.EnvConfig.UploadPath, "profile-pictures", userID+".png")
defaultProfilePicturesDir := common.EnvConfig.UploadPath + "/profile-pictures/defaults/"
defaultPicturePath := defaultProfilePicturesDir + user.Initials() + ".png" // Try custom profile picture
file, err = os.Open(defaultPicturePath) if file, size, err := utils.OpenFileWithSize(profilePicturePath); err == nil {
if err == nil { return file, size, nil
fileInfo, err := file.Stat() } else if !errors.Is(err, os.ErrNotExist) {
if err != nil { return nil, 0, err
file.Close()
return nil, 0, err
}
return file, fileInfo.Size(), nil
} }
// If no cached default picture exists, create one and save it for future use // Try default global profile picture
if s.appImagesService.IsDefaultProfilePictureSet() {
path, _, err := s.appImagesService.GetImage("default-profile-picture")
if err != nil {
return nil, 0, err
}
if file, size, err := utils.OpenFileWithSize(path); err == nil {
return file, size, nil
} else if !errors.Is(err, os.ErrNotExist) {
return nil, 0, err
}
}
// Try cached default for initials
defaultProfilePicturesDir := filepath.Join(common.EnvConfig.UploadPath, "profile-pictures", "defaults")
defaultPicturePath := filepath.Join(defaultProfilePicturesDir, user.Initials()+".png")
if file, size, err := utils.OpenFileWithSize(defaultPicturePath); err == nil {
return file, size, nil
}
// Create and return generated default with initials
defaultPicture, err := profilepicture.CreateDefaultProfilePicture(user.Initials()) defaultPicture, err := profilepicture.CreateDefaultProfilePicture(user.Initials())
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -128,19 +134,16 @@ func (s *UserService) GetProfilePicture(ctx context.Context, userID string) (io.
// Save the default picture for future use (in a goroutine to avoid blocking) // Save the default picture for future use (in a goroutine to avoid blocking)
defaultPictureBytes := defaultPicture.Bytes() defaultPictureBytes := defaultPicture.Bytes()
go func() { go func() {
// Ensure the directory exists if err := os.MkdirAll(defaultProfilePicturesDir, os.ModePerm); err != nil {
errInternal := os.MkdirAll(defaultProfilePicturesDir, os.ModePerm) slog.Error("Failed to create directory for default profile picture", slog.Any("error", err))
if errInternal != nil {
slog.Error("Failed to create directory for default profile picture", slog.Any("error", errInternal))
return return
} }
errInternal = utils.SaveFileStream(bytes.NewReader(defaultPictureBytes), defaultPicturePath) if err := utils.SaveFileStream(bytes.NewReader(defaultPictureBytes), defaultPicturePath); err != nil {
if errInternal != nil { slog.Error("Failed to cache default profile picture", slog.String("initials", user.Initials()), slog.Any("error", err))
slog.Error("Failed to cache default profile picture for initials", slog.String("initials", user.Initials()), slog.Any("error", errInternal))
} }
}() }()
return io.NopCloser(bytes.NewReader(defaultPictureBytes)), int64(defaultPicture.Len()), nil return io.NopCloser(bytes.NewReader(defaultPictureBytes)), int64(len(defaultPictureBytes)), nil
} }
func (s *UserService) GetUserGroups(ctx context.Context, userID string) ([]model.UserGroup, error) { func (s *UserService) GetUserGroups(ctx context.Context, userID string) ([]model.UserGroup, error) {
@@ -256,6 +259,7 @@ func (s *UserService) createUserInternal(ctx context.Context, input dto.UserCrea
Username: input.Username, Username: input.Username,
IsAdmin: input.IsAdmin, IsAdmin: input.IsAdmin,
Locale: input.Locale, Locale: input.Locale,
Disabled: input.Disabled,
} }
if input.LdapID != "" { if input.LdapID != "" {
user.LdapID = &input.LdapID user.LdapID = &input.LdapID
@@ -794,11 +798,11 @@ func (s *UserService) SignUp(ctx context.Context, signupData dto.SignUpDto, ipAd
return user, accessToken, nil return user, accessToken, nil
} }
func (s *UserService) ListSignupTokens(ctx context.Context, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.SignupToken, utils.PaginationResponse, error) { func (s *UserService) ListSignupTokens(ctx context.Context, listRequestOptions utils.ListRequestOptions) ([]model.SignupToken, utils.PaginationResponse, error) {
var tokens []model.SignupToken var tokens []model.SignupToken
query := s.db.WithContext(ctx).Model(&model.SignupToken{}) query := s.db.WithContext(ctx).Model(&model.SignupToken{})
pagination, err := utils.PaginateAndSort(sortedPaginationRequest, query, &tokens) pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &tokens)
return tokens, pagination, err return tokens, pagination, err
} }

View File

@@ -81,6 +81,7 @@ func (s *WebAuthnService) BeginRegistration(ctx context.Context, userID string)
&user, &user,
webauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementRequired), webauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementRequired),
webauthn.WithExclusions(user.WebAuthnCredentialDescriptors()), webauthn.WithExclusions(user.WebAuthnCredentialDescriptors()),
webauthn.WithExtensions(map[string]any{"credProps": true}), // Required for Firefox Android to properly save the key in Google password manager
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to begin WebAuthn registration: %w", err) return nil, fmt.Errorf("failed to begin WebAuthn registration: %w", err)
@@ -89,6 +90,7 @@ func (s *WebAuthnService) BeginRegistration(ctx context.Context, userID string)
sessionToStore := &model.WebauthnSession{ sessionToStore := &model.WebauthnSession{
ExpiresAt: datatype.DateTime(session.Expires), ExpiresAt: datatype.DateTime(session.Expires),
Challenge: session.Challenge, Challenge: session.Challenge,
CredentialParams: session.CredParams,
UserVerification: string(session.UserVerification), UserVerification: string(session.UserVerification),
} }
@@ -130,9 +132,10 @@ func (s *WebAuthnService) VerifyRegistration(ctx context.Context, sessionID, use
} }
session := webauthn.SessionData{ session := webauthn.SessionData{
Challenge: storedSession.Challenge, Challenge: storedSession.Challenge,
Expires: storedSession.ExpiresAt.ToTime(), Expires: storedSession.ExpiresAt.ToTime(),
UserID: []byte(userID), CredParams: storedSession.CredentialParams,
UserID: []byte(userID),
} }
var user model.User var user model.User

View File

@@ -239,3 +239,17 @@ func IsWritableDir(dir string) (bool, error) {
return true, nil return true, nil
} }
// OpenFileWithSize opens a file and returns its size
func OpenFileWithSize(path string) (io.ReadCloser, int64, error) {
f, err := os.Open(path)
if err != nil {
return nil, 0, err
}
info, err := f.Stat()
if err != nil {
f.Close()
return nil, 0, err
}
return f, info.Size(), nil
}

View File

@@ -3,6 +3,7 @@ package utils
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"time" "time"
) )
@@ -40,3 +41,14 @@ func (d *JSONDuration) UnmarshalJSON(b []byte) error {
return errors.New("invalid duration") return errors.New("invalid duration")
} }
} }
func UnmarshalJSONFromDatabase(data interface{}, value any) error {
switch v := value.(type) {
case []byte:
return json.Unmarshal(v, data)
case string:
return json.Unmarshal([]byte(v), data)
default:
return fmt.Errorf("unsupported type: %T", value)
}
}

View File

@@ -0,0 +1,205 @@
package utils
import (
"reflect"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type PaginationResponse struct {
TotalPages int64 `json:"totalPages"`
TotalItems int64 `json:"totalItems"`
CurrentPage int `json:"currentPage"`
ItemsPerPage int `json:"itemsPerPage"`
}
type ListRequestOptions struct {
Pagination struct {
Page int `form:"pagination[page]"`
Limit int `form:"pagination[limit]"`
} `form:"pagination"`
Sort struct {
Column string `form:"sort[column]"`
Direction string `form:"sort[direction]"`
} `form:"sort"`
Filters map[string][]any
}
type FieldMeta struct {
ColumnName string
IsSortable bool
IsFilterable bool
}
func ParseListRequestOptions(ctx *gin.Context) (listRequestOptions ListRequestOptions) {
if err := ctx.ShouldBindQuery(&listRequestOptions); err != nil {
return listRequestOptions
}
listRequestOptions.Filters = parseNestedFilters(ctx)
return listRequestOptions
}
func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result interface{}) (PaginationResponse, error) {
meta := extractModelMetadata(result)
query = applyFilters(params.Filters, query, meta)
query = applySorting(params.Sort.Column, params.Sort.Direction, query, meta)
return Paginate(params.Pagination.Page, params.Pagination.Limit, query, result)
}
func Paginate(page int, pageSize int, query *gorm.DB, result interface{}) (PaginationResponse, error) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
} else if pageSize > 100 {
pageSize = 100
}
var totalItems int64
if err := query.Count(&totalItems).Error; err != nil {
return PaginationResponse{}, err
}
totalPages := (totalItems + int64(pageSize) - 1) / int64(pageSize)
if totalItems == 0 {
totalPages = 1
}
if int64(page) > totalPages {
page = int(totalPages)
}
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Find(result).Error; err != nil {
return PaginationResponse{}, err
}
return PaginationResponse{
TotalPages: totalPages,
TotalItems: totalItems,
CurrentPage: page,
ItemsPerPage: pageSize,
}, nil
}
func NormalizeSortDirection(direction string) string {
d := strings.ToLower(strings.TrimSpace(direction))
if d != "asc" && d != "desc" {
return "asc"
}
return d
}
func IsValidSortDirection(direction string) bool {
d := strings.ToLower(strings.TrimSpace(direction))
return d == "asc" || d == "desc"
}
// parseNestedFilters handles ?filters[field][0]=val1&filters[field][1]=val2
func parseNestedFilters(ctx *gin.Context) map[string][]any {
result := make(map[string][]any)
query := ctx.Request.URL.Query()
for key, values := range query {
if !strings.HasPrefix(key, "filters[") {
continue
}
// Keys can be "filters[field]" or "filters[field][0]"
raw := strings.TrimPrefix(key, "filters[")
// Take everything up to the first closing bracket
if idx := strings.IndexByte(raw, ']'); idx != -1 {
field := raw[:idx]
for _, v := range values {
result[field] = append(result[field], ConvertStringToType(v))
}
}
}
return result
}
// applyFilters applies filtering to the GORM query based on the provided filters
func applyFilters(filters map[string][]any, query *gorm.DB, meta map[string]FieldMeta) *gorm.DB {
for key, values := range filters {
if key == "" || len(values) == 0 {
continue
}
fieldName := CapitalizeFirstLetter(key)
fieldMeta, ok := meta[fieldName]
if !ok || !fieldMeta.IsFilterable {
continue
}
query = query.Where(fieldMeta.ColumnName+" IN ?", values)
}
return query
}
// applySorting applies sorting to the GORM query based on the provided column and direction
func applySorting(sortColumn string, sortDirection string, query *gorm.DB, meta map[string]FieldMeta) *gorm.DB {
fieldName := CapitalizeFirstLetter(sortColumn)
fieldMeta, ok := meta[fieldName]
if !ok || !fieldMeta.IsSortable {
return query
}
sortDirection = NormalizeSortDirection(sortDirection)
query = query.Clauses(clause.OrderBy{
Columns: []clause.OrderByColumn{
{Column: clause.Column{Name: fieldMeta.ColumnName}, Desc: sortDirection == "desc"},
},
})
return query
}
// extractModelMetadata extracts FieldMeta from the model struct using reflection
func extractModelMetadata(model interface{}) map[string]FieldMeta {
meta := make(map[string]FieldMeta)
// Unwrap pointers and slices to get the element struct type
t := reflect.TypeOf(model)
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
t = t.Elem()
if t == nil {
return meta
}
}
// recursive parser that merges fields from embedded structs
var parseStruct func(reflect.Type)
parseStruct = func(st reflect.Type) {
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
ft := field.Type
// If the field is an embedded/anonymous struct, recurse into it
if field.Anonymous && ft.Kind() == reflect.Struct {
parseStruct(ft)
continue
}
// Normal field: record metadata
name := field.Name
meta[name] = FieldMeta{
ColumnName: CamelCaseToSnakeCase(name),
IsSortable: field.Tag.Get("sortable") == "true",
IsFilterable: field.Tag.Get("filterable") == "true",
}
}
}
parseStruct(t)
return meta
}

View File

@@ -1,99 +0,0 @@
package utils
import (
"reflect"
"strconv"
"strings"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type PaginationResponse struct {
TotalPages int64 `json:"totalPages"`
TotalItems int64 `json:"totalItems"`
CurrentPage int `json:"currentPage"`
ItemsPerPage int `json:"itemsPerPage"`
}
type SortedPaginationRequest struct {
Pagination struct {
Page int `form:"pagination[page]"`
Limit int `form:"pagination[limit]"`
} `form:"pagination"`
Sort struct {
Column string `form:"sort[column]"`
Direction string `form:"sort[direction]"`
} `form:"sort"`
}
func PaginateAndSort(sortedPaginationRequest SortedPaginationRequest, query *gorm.DB, result interface{}) (PaginationResponse, error) {
pagination := sortedPaginationRequest.Pagination
sort := sortedPaginationRequest.Sort
capitalizedSortColumn := CapitalizeFirstLetter(sort.Column)
sortField, sortFieldFound := reflect.TypeOf(result).Elem().Elem().FieldByName(capitalizedSortColumn)
isSortable, _ := strconv.ParseBool(sortField.Tag.Get("sortable"))
sort.Direction = NormalizeSortDirection(sort.Direction)
if sortFieldFound && isSortable {
columnName := CamelCaseToSnakeCase(sort.Column)
query = query.Clauses(clause.OrderBy{
Columns: []clause.OrderByColumn{
{Column: clause.Column{Name: columnName}, Desc: sort.Direction == "desc"},
},
})
}
return Paginate(pagination.Page, pagination.Limit, query, result)
}
func Paginate(page int, pageSize int, query *gorm.DB, result interface{}) (PaginationResponse, error) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
} else if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var totalItems int64
if err := query.Count(&totalItems).Error; err != nil {
return PaginationResponse{}, err
}
if err := query.Offset(offset).Limit(pageSize).Find(result).Error; err != nil {
return PaginationResponse{}, err
}
totalPages := (totalItems + int64(pageSize) - 1) / int64(pageSize)
if totalItems == 0 {
totalPages = 1
}
return PaginationResponse{
TotalPages: totalPages,
TotalItems: totalItems,
CurrentPage: page,
ItemsPerPage: pageSize,
}, nil
}
func NormalizeSortDirection(direction string) string {
d := strings.ToLower(strings.TrimSpace(direction))
if d != "asc" && d != "desc" {
return "asc"
}
return d
}
func IsValidSortDirection(direction string) bool {
d := strings.ToLower(strings.TrimSpace(direction))
return d == "asc" || d == "desc"
}

View File

@@ -81,26 +81,21 @@ func CapitalizeFirstLetter(str string) string {
return result.String() return result.String()
} }
func CamelCaseToSnakeCase(str string) string { var (
result := strings.Builder{} reAcronymBoundary = regexp.MustCompile(`([A-Z]+)([A-Z][a-z])`) // ABCd -> AB_Cd
result.Grow(int(float32(len(str)) * 1.1)) reLowerToUpper = regexp.MustCompile(`([a-z0-9])([A-Z])`) // aB -> a_B
for i, r := range str { )
if unicode.IsUpper(r) && i > 0 {
result.WriteByte('_') func CamelCaseToSnakeCase(s string) string {
} s = reAcronymBoundary.ReplaceAllString(s, "${1}_${2}")
result.WriteRune(unicode.ToLower(r)) s = reLowerToUpper.ReplaceAllString(s, "${1}_${2}")
} return strings.ToLower(s)
return result.String()
} }
var camelCaseToScreamingSnakeCaseRe = regexp.MustCompile(`([a-z0-9])([A-Z])`)
func CamelCaseToScreamingSnakeCase(s string) string { func CamelCaseToScreamingSnakeCase(s string) string {
// Insert underscores before uppercase letters (except the first one) s = reAcronymBoundary.ReplaceAllString(s, "${1}_${2}")
snake := camelCaseToScreamingSnakeCaseRe.ReplaceAllString(s, `${1}_${2}`) s = reLowerToUpper.ReplaceAllString(s, "${1}_${2}")
return strings.ToUpper(s)
// Convert to uppercase
return strings.ToUpper(snake)
} }
// GetFirstCharacter returns the first non-whitespace character of the string, correctly handling Unicode // GetFirstCharacter returns the first non-whitespace character of the string, correctly handling Unicode

View File

@@ -86,9 +86,9 @@ func TestCamelCaseToSnakeCase(t *testing.T) {
{"simple camelCase", "camelCase", "camel_case"}, {"simple camelCase", "camelCase", "camel_case"},
{"PascalCase", "PascalCase", "pascal_case"}, {"PascalCase", "PascalCase", "pascal_case"},
{"multipleWordsInCamelCase", "multipleWordsInCamelCase", "multiple_words_in_camel_case"}, {"multipleWordsInCamelCase", "multipleWordsInCamelCase", "multiple_words_in_camel_case"},
{"consecutive uppercase", "HTTPRequest", "h_t_t_p_request"}, {"consecutive uppercase", "HTTPRequest", "http_request"},
{"single lowercase word", "word", "word"}, {"single lowercase word", "word", "word"},
{"single uppercase word", "WORD", "w_o_r_d"}, {"single uppercase word", "WORD", "word"},
{"with numbers", "camel123Case", "camel123_case"}, {"with numbers", "camel123Case", "camel123_case"},
{"with numbers in middle", "model2Name", "model2_name"}, {"with numbers in middle", "model2Name", "model2_name"},
{"mixed case", "iPhone6sPlus", "i_phone6s_plus"}, {"mixed case", "iPhone6sPlus", "i_phone6s_plus"},
@@ -104,6 +104,34 @@ func TestCamelCaseToSnakeCase(t *testing.T) {
} }
} }
func TestCamelCaseToScreamingSnakeCase(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"empty string", "", ""},
{"simple camelCase", "camelCase", "CAMEL_CASE"},
{"PascalCase", "PascalCase", "PASCAL_CASE"},
{"multipleWordsInCamelCase", "multipleWordsInCamelCase", "MULTIPLE_WORDS_IN_CAMEL_CASE"},
{"consecutive uppercase", "HTTPRequest", "HTTP_REQUEST"},
{"single lowercase word", "word", "WORD"},
{"single uppercase word", "WORD", "WORD"},
{"with numbers", "camel123Case", "CAMEL123_CASE"},
{"with numbers in middle", "model2Name", "MODEL2_NAME"},
{"mixed case", "iPhone6sPlus", "I_PHONE6S_PLUS"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := CamelCaseToScreamingSnakeCase(tt.input)
if result != tt.expected {
t.Errorf("CamelCaseToScreamingSnakeCase(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestGetFirstCharacter(t *testing.T) { func TestGetFirstCharacter(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View File

@@ -0,0 +1,35 @@
package utils
import (
"strconv"
"strings"
)
// ConvertStringToType attempts to convert a string to bool, int, or float.
func ConvertStringToType(value string) any {
v := strings.TrimSpace(value)
if v == "" {
return v
}
// Try bool
if v == "true" {
return true
}
if v == "false" {
return false
}
// Try int
if i, err := strconv.Atoi(v); err == nil {
return i
}
// Try float
if f, err := strconv.ParseFloat(v, 64); err == nil {
return f
}
// Default: string
return v
}

View File

@@ -0,0 +1,37 @@
package utils
import (
"testing"
)
func TestConvertStringToType(t *testing.T) {
tests := []struct {
input string
expected any
}{
{"true", true},
{"false", false},
{" true ", true},
{" false ", false},
{"42", 42},
{" 42 ", 42},
{"3.14", 3.14},
{" 3.14 ", 3.14},
{"hello", "hello"},
{" hello ", "hello"},
{"", ""},
{" ", ""},
}
for _, tt := range tests {
result := ConvertStringToType(tt.input)
if result != tt.expected {
if f, ok := tt.expected.(float64); ok {
if rf, ok := result.(float64); ok && rf == f {
continue
}
}
t.Errorf("ConvertStringToType(%q) = %#v (type %T), want %#v (type %T)", tt.input, result, result, tt.expected, tt.expected)
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ALTER TABLE oidc_clients DROP COLUMN dark_image_type;

View File

@@ -0,0 +1 @@
ALTER TABLE oidc_clients ADD COLUMN dark_image_type TEXT;

View File

@@ -0,0 +1 @@
ALTER TABLE webauthn_sessions DROP COLUMN credential_params;

View File

@@ -0,0 +1 @@
ALTER TABLE webauthn_sessions ADD COLUMN credential_params JSONB NOT NULL DEFAULT '[]';

View File

@@ -0,0 +1,4 @@
DROP INDEX IF EXISTS idx_api_keys_key;
DROP INDEX IF EXISTS idx_oidc_refresh_tokens_token;
DROP INDEX IF EXISTS idx_signup_tokens_token;
DROP INDEX IF EXISTS idx_reauthentication_tokens_token;

View File

@@ -0,0 +1,5 @@
PRAGMA foreign_keys=OFF;
BEGIN;
ALTER TABLE oidc_clients DROP COLUMN dark_image_type;
COMMIT;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,5 @@
PRAGMA foreign_keys=OFF;
BEGIN;
ALTER TABLE oidc_clients ADD COLUMN dark_image_type TEXT;
COMMIT;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,5 @@
PRAGMA foreign_keys=OFF;
BEGIN;
ALTER TABLE webauthn_sessions DROP COLUMN credential_params;
COMMIT;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,5 @@
PRAGMA foreign_keys=OFF;
BEGIN;
ALTER TABLE webauthn_sessions ADD COLUMN credential_params TEXT NOT NULL DEFAULT '[]';
COMMIT;
PRAGMA foreign_keys=ON;

View File

@@ -10,16 +10,16 @@
}, },
"dependencies": { "dependencies": {
"@react-email/components": "0.1.1", "@react-email/components": "0.1.1",
"react": "^19.0.0", "react": "^19.2.0",
"react-dom": "^19.0.0", "react-dom": "^19.2.0",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
"@react-email/preview-server": "4.2.8", "@react-email/preview-server": "4.2.8",
"@types/node": "^24.0.10", "@types/node": "^24.9.1",
"@types/react": "^19.0.1", "@types/react": "^19.2.2",
"@types/react-dom": "^19.0.1", "@types/react-dom": "^19.2.2",
"react-email": "4.2.8", "react-email": "4.2.8",
"tsx": "^4.0.0" "tsx": "^4.20.6"
} }
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Jste si jisti, že chcete zrušit klíč API \"{apiKeyName}\"? To naruší všechny integrace pomocí tohoto klíče.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Jste si jisti, že chcete zrušit klíč API \"{apiKeyName}\"? To naruší všechny integrace pomocí tohoto klíče.",
"last_used": "Naposledy použito", "last_used": "Naposledy použito",
"actions": "Akce", "actions": "Akce",
"images_updated_successfully": "Obrázky úspěšně aktualizovány", "images_updated_successfully": "Obrázky byly úspěšně aktualizovány. Aktualizace může trvat několik minut.",
"general": "Obecné", "general": "Obecné",
"configure_smtp_to_send_emails": "Povolte e-mailová oznámení pro upozornění uživatelů, pokud je zjištěno přihlášení z nového zařízení nebo lokace.", "configure_smtp_to_send_emails": "Povolte e-mailová oznámení pro upozornění uživatelů, pokud je zjištěno přihlášení z nového zařízení nebo lokace.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Vložte přímou URL adresu obrázku (svg, png, webp). Ikony najdete na <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> nebo <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Vložte přímou URL adresu obrázku (svg, png, webp). Ikony najdete na <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> nebo <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Neplatná adresa URL", "invalid_url": "Neplatná adresa URL",
"require_user_email": "Vyžadovat e-mailovou adresu", "require_user_email": "Vyžadovat e-mailovou adresu",
"require_user_email_description": "Vyžaduje, aby uživatelé měli e-mailovou adresu. Pokud je tato možnost deaktivována, uživatelé bez e-mailové adresy nebudou moci používat funkce, které e-mailovou adresu vyžadují." "require_user_email_description": "Vyžaduje, aby uživatelé měli e-mailovou adresu. Pokud je tato možnost deaktivována, uživatelé bez e-mailové adresy nebudou moci používat funkce, které e-mailovou adresu vyžadují.",
"view": "Zobrazení",
"toggle_columns": "Přepnout sloupce",
"locale": "Jazyk",
"ldap_id": "LDAP ID",
"reauthentication": "Opětovné ověření",
"clear_filters": "Vymazat filtry",
"default_profile_picture": "Výchozí profilový obrázek"
} }

View File

@@ -155,7 +155,7 @@
"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.", "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": "Sidst brugt", "last_used": "Sidst brugt",
"actions": "Handlinger", "actions": "Handlinger",
"images_updated_successfully": "Billeder blev opdateret", "images_updated_successfully": "Billeder opdateret. Det kan tage et par minutter at opdatere.",
"general": "Generelt", "general": "Generelt",
"configure_smtp_to_send_emails": "Aktivér e-mailnotifikationer for at advare brugere, når et login registreres fra en ny enhed eller placering.", "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",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Indsæt en direkte billed-URL (svg, png, webp). Find ikoner på <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> eller <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Indsæt en direkte billed-URL (svg, png, webp). Find ikoner på <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> eller <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Ugyldig URL", "invalid_url": "Ugyldig URL",
"require_user_email": "Kræver e-mailadresse", "require_user_email": "Kræver e-mailadresse",
"require_user_email_description": "Kræver, at brugerne har en e-mailadresse. Hvis denne funktion er deaktiveret, kan brugere uden en e-mailadresse ikke bruge funktioner, der kræver en e-mailadresse." "require_user_email_description": "Kræver, at brugerne har en e-mailadresse. Hvis denne funktion er deaktiveret, kan brugere uden en e-mailadresse ikke bruge funktioner, der kræver en e-mailadresse.",
"view": "Vis",
"toggle_columns": "Skift kolonner",
"locale": "Lokale",
"ldap_id": "LDAP-id",
"reauthentication": "Genbekræftelse",
"clear_filters": "Ryd filtre",
"default_profile_picture": "Standardprofilbillede"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Bist du sicher, dass du den API-Schlüssel \"{apiKeyName}\" widerrufen willst? Das wird jegliche Integrationen, die diesen Schlüssel verwenden, brechen.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Bist du sicher, dass du den API-Schlüssel \"{apiKeyName}\" widerrufen willst? Das wird jegliche Integrationen, die diesen Schlüssel verwenden, brechen.",
"last_used": "Letzte Verwendung", "last_used": "Letzte Verwendung",
"actions": "Aktionen", "actions": "Aktionen",
"images_updated_successfully": "Bild erfolgreich aktualisiert", "images_updated_successfully": "Bilder erfolgreich aktualisiert. Die Aktualisierung kann ein paar Minuten dauern.",
"general": "Allgemein", "general": "Allgemein",
"configure_smtp_to_send_emails": "Aktiviere E-Mail Benachrichtigungen, um Benutzer zu informieren, wenn ein Login von einem neuen Gerät oder Standort erkannt wird.", "configure_smtp_to_send_emails": "Aktiviere E-Mail Benachrichtigungen, um Benutzer zu informieren, wenn ein Login von einem neuen Gerät oder Standort erkannt wird.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Füge eine direkte Bild-URL ein (svg, png, webp). Finde Symbole bei <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> oder <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Füge eine direkte Bild-URL ein (svg, png, webp). Finde Symbole bei <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> oder <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Ungültige URL", "invalid_url": "Ungültige URL",
"require_user_email": "E-Mail-Adresse erforderlich", "require_user_email": "E-Mail-Adresse erforderlich",
"require_user_email_description": "Benutzer müssen eine E-Mail-Adresse haben. Wenn das deaktiviert ist, können Leute ohne E-Mail-Adresse die Funktionen, die eine E-Mail-Adresse brauchen, nicht nutzen." "require_user_email_description": "Benutzer müssen eine E-Mail-Adresse haben. Wenn das deaktiviert ist, können Leute ohne E-Mail-Adresse die Funktionen, die eine E-Mail-Adresse brauchen, nicht nutzen.",
"view": "Ansicht",
"toggle_columns": "Spalten umschalten",
"locale": "Ort",
"ldap_id": "LDAP-ID",
"reauthentication": "Erneute Authentifizierung",
"clear_filters": "Filter löschen",
"default_profile_picture": "Standard-Profilbild"
} }

View File

@@ -155,7 +155,7 @@
"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": "Are you sure you want to revoke the API key \"{apiKeyName}\"? This will break any integrations using this key.",
"last_used": "Last Used", "last_used": "Last Used",
"actions": "Actions", "actions": "Actions",
"images_updated_successfully": "Images updated successfully", "images_updated_successfully": "Images updated successfully. It may take a few minutes to update.",
"general": "General", "general": "General",
"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": "Enable email notifications to alert users when a login is detected from a new device or location.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Paste a direct image URL (svg, png, webp). Find icons at <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> or <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Paste a direct image URL (svg, png, webp). Find icons at <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> or <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Invalid URL", "invalid_url": "Invalid URL",
"require_user_email": "Require Email Address", "require_user_email": "Require Email Address",
"require_user_email_description": "Requires users to have an email address. If disabled, the users without an email address won't be able to use features that require an email address." "require_user_email_description": "Requires users to have an email address. If disabled, the users without an email address won't be able to use features that require an email address.",
"view": "View",
"toggle_columns": "Toggle columns",
"locale": "Locale",
"ldap_id": "LDAP ID",
"reauthentication": "Re-authentication",
"clear_filters": "Clear Filters",
"default_profile_picture": "Default Profile Picture"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "¿Estás seguro de que deseas invalidar la API Key \"{apiKeyName}\"? Esto romperá cualquier integración que esté usando esta clave.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "¿Estás seguro de que deseas invalidar la API Key \"{apiKeyName}\"? Esto romperá cualquier integración que esté usando esta clave.",
"last_used": "Utilizado por última vez", "last_used": "Utilizado por última vez",
"actions": "Acciones", "actions": "Acciones",
"images_updated_successfully": "Imágenes actualizadas correctamente", "images_updated_successfully": "Imágenes actualizadas correctamente. La actualización puede tardar unos minutos.",
"general": "General", "general": "General",
"configure_smtp_to_send_emails": "Habilita las notificaciones por correo electrónico para alertar a los usuarios cuando se detecta un inicio de sesión desde un nuevo dispositivo o ubicación.", "configure_smtp_to_send_emails": "Habilita las notificaciones por correo electrónico para alertar a los usuarios cuando se detecta un inicio de sesión desde un nuevo dispositivo o ubicación.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Pega una URL de imagen directa (svg, png, webp). Encuentra iconos en <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> o <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Pega una URL de imagen directa (svg, png, webp). Encuentra iconos en <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> o <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "URL no válida", "invalid_url": "URL no válida",
"require_user_email": "Requerir dirección de correo electrónico", "require_user_email": "Requerir dirección de correo electrónico",
"require_user_email_description": "Requiere que los usuarios tengan una dirección de correo electrónico. Si se desactiva, los usuarios que no tengan una dirección de correo electrónico no podrán utilizar las funciones que la requieran." "require_user_email_description": "Requiere que los usuarios tengan una dirección de correo electrónico. Si se desactiva, los usuarios que no tengan una dirección de correo electrónico no podrán utilizar las funciones que la requieran.",
"view": "Ver",
"toggle_columns": "Alternar columnas",
"locale": "Configuración regional",
"ldap_id": "Identificador LDAP",
"reauthentication": "Reautenticación",
"clear_filters": "Borrar filtros",
"default_profile_picture": "Imagen de perfil predeterminada"
} }

View File

@@ -17,14 +17,14 @@
"image_should_be_in_format": "L'image doit être au format PNG ou JPEG.", "image_should_be_in_format": "L'image doit être au format PNG ou JPEG.",
"items_per_page": "Éléments par page", "items_per_page": "Éléments par page",
"no_items_found": "Aucune donnée trouvée", "no_items_found": "Aucune donnée trouvée",
"select_items": "Choisis des trucs...", "select_items": "Sélectionner des éléments...",
"search": "Rechercher...", "search": "Rechercher...",
"expand_card": "Carte d'expansion", "expand_card": "Développer la carte",
"copied": "Copié", "copied": "Copié",
"click_to_copy": "Cliquer pour copier", "click_to_copy": "Cliquer pour copier",
"something_went_wrong": "Quelque chose n'a pas fonctionné", "something_went_wrong": "Quelque chose n'a pas fonctionné",
"go_back_to_home": "Retourner à l'accueil", "go_back_to_home": "Retourner à l'accueil",
"alternative_sign_in_methods": "Autres façons de se connecter", "alternative_sign_in_methods": "Méthodes de connexion alternatives",
"login_background": "Arrière-plan de connexion", "login_background": "Arrière-plan de connexion",
"logo": "Logo", "logo": "Logo",
"login_code": "Code de connexion", "login_code": "Code de connexion",
@@ -75,10 +75,10 @@
"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.",
"sign_in_with_login_code": "Connecte-toi avec ton code d'accès", "sign_in_with_login_code": "Connectez-vous avec votre code d'accès",
"request_a_login_code_via_email": "Demander un code de connexion par e-mail.", "request_a_login_code_via_email": "Demander un code de connexion par e-mail.",
"go_back": "Retour", "go_back": "Retour",
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "Un e-mail a été envoyé à l'e-mail mentionné, si elle existe dans le système.", "an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "Un e-mail a été envoyé à l'adresse mentionnée, si celle-ci existe dans le système.",
"enter_code": "Entrez le code", "enter_code": "Entrez le code",
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Entrez votre adresse e-mail pour recevoir un email avec un code de connexion.", "enter_your_email_address_to_receive_an_email_with_a_login_code": "Entrez votre adresse e-mail pour recevoir un email avec un code de connexion.",
"your_email": "Votre email", "your_email": "Votre email",
@@ -90,10 +90,10 @@
"users": "Utilisateurs", "users": "Utilisateurs",
"user_groups": "Groupes dutilisateurs", "user_groups": "Groupes dutilisateurs",
"oidc_clients": "Clients OIDC", "oidc_clients": "Clients OIDC",
"api_keys": "Clés API", "api_keys": "Clés d'API",
"application_configuration": "Configuration de lapplication", "application_configuration": "Configuration de lapplication",
"settings": "Paramètres", "settings": "Paramètres",
"update_pocket_id": "Mise à jour de Pocket ID", "update_pocket_id": "Mettre à jour Pocket ID",
"powered_by": "Propulsé par", "powered_by": "Propulsé par",
"see_your_account_activities_from_the_last_3_months": "Consultez les activités de votre compte au cours des 3 derniers mois.", "see_your_account_activities_from_the_last_3_months": "Consultez les activités de votre compte au cours des 3 derniers mois.",
"time": "Date et heure", "time": "Date et heure",
@@ -139,7 +139,7 @@
"add_api_key": "Crée une clé API", "add_api_key": "Crée une clé API",
"manage_api_keys": "Gérer les clés API", "manage_api_keys": "Gérer les clés API",
"api_key_created": "Clé API créée", "api_key_created": "Clé API créée",
"for_security_reasons_this_key_will_only_be_shown_once": "Pour des raisons de sécurité, cette clé ne sera affichée qu'une seule fois. Veuillez la conserver en toute sécurité.", "for_security_reasons_this_key_will_only_be_shown_once": "Pour des raisons de sécurité, cette clé ne sera affichée qu'une seule fois. Veuillez la conserver dans un endroit sûr.",
"description": "Description", "description": "Description",
"api_key": "Clé API", "api_key": "Clé API",
"close": "Fermer", "close": "Fermer",
@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Êtes-vous sûr de vouloir révoquer la clé API \"{apiKeyName}\" ? Cela va casser toutes les intégrations utilisant cette clé.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Êtes-vous sûr de vouloir révoquer la clé API \"{apiKeyName}\" ? Cela va casser toutes les intégrations utilisant cette clé.",
"last_used": "Dernière utilisation", "last_used": "Dernière utilisation",
"actions": "Actions", "actions": "Actions",
"images_updated_successfully": "Image mise à jour avec succès", "images_updated_successfully": "Les images ont été mises à jour sans problème. Ça peut prendre quelques minutes pour que tout se mette à jour.",
"general": "Général", "general": "Général",
"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.", "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",
@@ -163,7 +163,7 @@
"images": "Images", "images": "Images",
"update": "Mise à jour", "update": "Mise à jour",
"email_configuration_updated_successfully": "La configuration du serveur mail à été mise à jour avec succès", "email_configuration_updated_successfully": "La configuration du serveur mail à été mise à jour avec succès",
"save_changes_question": "Enregistrer des changements?", "save_changes_question": "Enregistrer ces changements?",
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "Vous devez enregistrer les modifications avant d'envoyer un e-mail de test. Voulez-vous enregistrer maintenant ?", "you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "Vous devez enregistrer les modifications avant d'envoyer un e-mail de test. Voulez-vous enregistrer maintenant ?",
"save_and_send": "Enregistrer et envoyer", "save_and_send": "Enregistrer et envoyer",
"test_email_sent_successfully": "L'e-mail de test a été envoyé avec succès à votre adresse e-mail.", "test_email_sent_successfully": "L'e-mail de test a été envoyé avec succès à votre adresse e-mail.",
@@ -256,7 +256,7 @@
"manage_user_groups": "Gérer les groupes d'utilisateurs", "manage_user_groups": "Gérer les groupes d'utilisateurs",
"friendly_name": "Nom d'affichage", "friendly_name": "Nom d'affichage",
"name_that_will_be_displayed_in_the_ui": "Nom qui sera affiché dans l'interface utilisateur", "name_that_will_be_displayed_in_the_ui": "Nom qui sera affiché dans l'interface utilisateur",
"name_that_will_be_in_the_groups_claim": "Nommez ce qui sera dans le \"groupe\" claim", "name_that_will_be_in_the_groups_claim": "Nom qui sera dans la revendication \"groups\"",
"delete_name": "Supprimer {name}", "delete_name": "Supprimer {name}",
"are_you_sure_you_want_to_delete_this_user_group": "Êtes-vous sûr de vouloir supprimer ce groupe d'utilisateurs?", "are_you_sure_you_want_to_delete_this_user_group": "Êtes-vous sûr de vouloir supprimer ce groupe d'utilisateurs?",
"user_group_deleted_successfully": "Groupe d'utilisateurs supprimé avec succès", "user_group_deleted_successfully": "Groupe d'utilisateurs supprimé avec succès",
@@ -272,14 +272,14 @@
"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": "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.", "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 qui 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",
"public_client": "Client public", "public_client": "Client public",
"public_clients_description": "Les clients publics n'ont pas de secret client et utilisent PKCE à la place. Activez cette option si votre client est une application SPA ou une application mobile.", "public_clients_description": "Les clients publics n'ont pas de secret client et utilisent PKCE à la place. Activez cette option si votre client est une application SPA ou une application mobile.",
"pkce": "PKCE", "pkce": "PKCE",
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Le Public Key Code Exchange est une fonctionnalité de sécurité conçue pour prévenir les attaques CSRF et linterception de code dautorisation.", "public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Le Public Key Code Exchange est une fonctionnalité de sécurité conçue pour prévenir les attaques CSRF et dinterception de code dautorisation.",
"requires_reauthentication": "Nécessite une nouvelle authentification", "requires_reauthentication": "Nécessite une nouvelle authentification",
"requires_users_to_authenticate_again_on_each_authorization": "Demande aux utilisateurs de se connecter à nouveau à chaque autorisation, même s'ils sont déjà connectés.", "requires_users_to_authenticate_again_on_each_authorization": "Demande aux utilisateurs de se connecter à nouveau à chaque autorisation, même s'ils sont déjà connectés.",
"name_logo": "Logo {name}", "name_logo": "Logo {name}",
@@ -318,7 +318,7 @@
"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": "Choisis la langue que tu veux utiliser. Attention, certains textes peuvent être traduits automatiquement et ne pas être tout à fait exacts.", "select_the_language_you_want_to_use": "Choisissez la langue que vous souhaitez utiliser. Attention, certains textes peuvent être traduits automatiquement et ne pas être tout à fait exacts.",
"contribute_to_translation": "Si vous trouvez un problème, n'hésitez pas à contribuer à la traduction sur <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.", "contribute_to_translation": "Si vous trouvez un problème, n'hésitez pas à contribuer à la traduction sur <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.",
"personal": "Personnel", "personal": "Personnel",
"global": "Global", "global": "Global",
@@ -348,7 +348,7 @@
"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é.", "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é.",
"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é.", "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é.",
"api_key_expiration": "Expiration de la clé API", "api_key_expiration": "Expiration de la clé API",
"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.", "send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Envoyer un email à l'utilisateur lorsque sa clé d'API est sur le point d'expirer.",
"authorize_device": "Autoriser l'appareil", "authorize_device": "Autoriser l'appareil",
"the_device_has_been_authorized": "L'appareil a été autorisé.", "the_device_has_been_authorized": "L'appareil a été autorisé.",
"enter_code_displayed_in_previous_step": "Entrez le code affiché à l'étape précédente.", "enter_code_displayed_in_previous_step": "Entrez le code affiché à l'étape précédente.",
@@ -382,7 +382,7 @@
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Sélectionnez une couleur d'accent pour personnaliser l'apparence de Pocket ID.", "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", "accent_color": "Couleur d'accent",
"custom_accent_color": "Couleur d'accent personnalisée", "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).", "custom_accent_color_description": "Entrez une couleur personnalisée en utilisant un format CSS valide (ex. : hex, rgb, hsl).",
"color_value": "Valeur de la couleur", "color_value": "Valeur de la couleur",
"apply": "Appliquer", "apply": "Appliquer",
"signup_token": "Jeton d'inscription", "signup_token": "Jeton d'inscription",
@@ -429,19 +429,19 @@
"signup_open": "Inscription ouverte", "signup_open": "Inscription ouverte",
"signup_open_description": "Toute personne peut créer un nouveau compte sans restriction.", "signup_open_description": "Toute personne peut créer un nouveau compte sans restriction.",
"of": "sur", "of": "sur",
"skip_passkey_setup": "Ignorer la configuration de la clé d'accès", "skip_passkey_setup": "Ignorer la configuration d'une clé d'accès",
"skip_passkey_setup_description": "Il est fortement recommandé de configurer une clé d'accès, car sans elle, vous serez verrouillé hors de votre compte dès l'expiration de la session.", "skip_passkey_setup_description": "Il est fortement recommandé de configurer une clé d'accès, car sans elle, vous serez verrouillé hors de votre compte dès l'expiration de la session.",
"my_apps": "Mes applications", "my_apps": "Mes applications",
"no_apps_available": "Aucune appli disponible", "no_apps_available": "Aucune app disponible",
"contact_your_administrator_for_app_access": "Contacte ton administrateur pour avoir accès aux applications.", "contact_your_administrator_for_app_access": "Contactez votre administrateur pour avoir accès aux applications.",
"launch": "Lancement", "launch": "Lancement",
"client_launch_url": "URL de lancement du client", "client_launch_url": "URL de lancement du client",
"client_launch_url_description": "L'URL qui s'ouvrira quand quelqu'un lancera l'appli depuis la page Mes applis.", "client_launch_url_description": "L'URL qui s'ouvrira quand quelqu'un lancera l'appli depuis la page Mes applis.",
"client_name_description": "Le nom du client qui apparaît dans l'interface utilisateur Pocket ID.", "client_name_description": "Le nom du client qui apparaît dans l'interface utilisateur Pocket ID.",
"revoke_access": "Supprimer l'accès", "revoke_access": "Supprimer l'accès",
"revoke_access_description": "Supprimer l'accès à <b>{clientName}</b>. <b>{clientName}</b> ne pourra plus accéder aux infos de ton compte.", "revoke_access_description": "Supprimer l'accès à <b>{clientName}</b>. <b>{clientName}</b> ne pourra plus accéder aux infos de votre compte.",
"revoke_access_successful": "L'accès à {clientName} a été supprimé.", "revoke_access_successful": "L'accès à {clientName} a été supprimé.",
"last_signed_in_ago": "Dernière connexion il y a {time} il y a", "last_signed_in_ago": "Dernière connexion il y a {time}",
"invalid_client_id": "L'ID client ne peut contenir que des lettres, des chiffres, des traits de soulignement et des tirets.", "invalid_client_id": "L'ID client ne peut contenir que des lettres, des chiffres, des traits de soulignement et des tirets.",
"custom_client_id_description": "Définissez un identifiant client personnalisé si votre application l'exige. Sinon, laissez ce champ vide pour qu'un identifiant aléatoire soit généré.", "custom_client_id_description": "Définissez un identifiant client personnalisé si votre application l'exige. Sinon, laissez ce champ vide pour qu'un identifiant aléatoire soit généré.",
"generated": "Généré", "generated": "Généré",
@@ -452,8 +452,15 @@
"configure_application_images": "Configurer les images d'application", "configure_application_images": "Configurer les images d'application",
"ui_config_disabled_info_title": "Configuration de l'interface utilisateur désactivée", "ui_config_disabled_info_title": "Configuration de l'interface utilisateur désactivée",
"ui_config_disabled_info_description": "La configuration de l'interface utilisateur est désactivée parce que les paramètres de configuration de l'application sont gérés par des variables d'environnement. Certains paramètres peuvent ne pas être modifiables.", "ui_config_disabled_info_description": "La configuration de l'interface utilisateur est désactivée parce que les paramètres de configuration de l'application sont gérés par des variables d'environnement. Certains paramètres peuvent ne pas être modifiables.",
"logo_from_url_description": "Colle une URL d'image directe (svg, png, webp). Trouve des icônes sur <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> ou <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Collez une URL pointant une image (svg, png, webp). Trouvez des icônes sur <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> ou <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "URL pas valide", "invalid_url": "URL invalide",
"require_user_email": "Besoin d'une adresse e-mail", "require_user_email": "Adresse e-mail requise",
"require_user_email_description": "Les utilisateurs doivent avoir une adresse e-mail. Si cette option est désactivée, ceux qui n'ont pas d'adresse e-mail ne pourront pas utiliser les fonctionnalités qui en ont besoin." "require_user_email_description": "Les utilisateurs doivent avoir une adresse e-mail. Si cette option est désactivée, ceux qui n'ont pas d'adresse e-mail ne pourront pas utiliser les fonctionnalités nécessitant un e-mail.",
"view": "Voir",
"toggle_columns": "Basculer les colonnes",
"locale": "Paramètres régionaux",
"ldap_id": "ID LDAP",
"reauthentication": "Réauthentification",
"clear_filters": "Effacer les filtres",
"default_profile_picture": "Photo de profil par défaut"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Sei sicuro di voler revocare la chiave API \"{apiKeyName}\"? Questo comprometterà qualsiasi integrazione che utilizza questa chiave.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Sei sicuro di voler revocare la chiave API \"{apiKeyName}\"? Questo comprometterà qualsiasi integrazione che utilizza questa chiave.",
"last_used": "Ultimo utilizzo", "last_used": "Ultimo utilizzo",
"actions": "Azioni", "actions": "Azioni",
"images_updated_successfully": "Immagini aggiornate con successo", "images_updated_successfully": "Immagini aggiornate senza problemi. Potrebbe volerci qualche minuto per l'aggiornamento.",
"general": "Generale", "general": "Generale",
"configure_smtp_to_send_emails": "Abilita le notifiche email per avvisare gli utenti quando viene rilevato un accesso da un nuovo dispositivo o posizione.", "configure_smtp_to_send_emails": "Abilita le notifiche email per avvisare gli utenti quando viene rilevato un accesso da un nuovo dispositivo o posizione.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Incolla l'URL diretto dell'immagine (svg, png, webp). Trova le icone su <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> o <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Incolla l'URL diretto dell'immagine (svg, png, webp). Trova le icone su <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> o <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "URL non valido", "invalid_url": "URL non valido",
"require_user_email": "Richiesta indirizzo e-mail", "require_user_email": "Richiesta indirizzo e-mail",
"require_user_email_description": "Chiede agli utenti di avere un indirizzo email. Se disattivato, chi non ha un indirizzo email non potrà usare le funzioni che lo richiedono." "require_user_email_description": "Chiede agli utenti di avere un indirizzo email. Se disattivato, chi non ha un indirizzo email non potrà usare le funzioni che lo richiedono.",
"view": "Visualizza",
"toggle_columns": "Attiva/disattiva colonne",
"locale": "Locale",
"ldap_id": "ID LDAP",
"reauthentication": "Riautenticazione",
"clear_filters": "Cancella filtri",
"default_profile_picture": "Immagine del profilo predefinita"
} }

466
frontend/messages/ja.json Normal file
View File

@@ -0,0 +1,466 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"my_account": "マイアカウント",
"logout": "ログアウト",
"confirm": "確認",
"docs": "ドキュメント",
"key": "キー",
"value": "値",
"remove_custom_claim": "カスタムクレームを削除",
"add_custom_claim": "カスタムクレームを追加",
"add_another": "さらに追加",
"select_a_date": "日付を選択",
"select_file": "ファイルを選択",
"profile_picture": "プロフィール画像",
"profile_picture_is_managed_by_ldap_server": "プロフィール画像はLDAPサーバーによって管理されており、ここでは変更できません。",
"click_profile_picture_to_upload_custom": "プロフィール画像をクリックして、ファイルからカスタム画像をアップロードします。",
"image_should_be_in_format": "画像はPNGまたはJPEG形式である必要があります。",
"items_per_page": "ページあたりの表示件数",
"no_items_found": "項目が見つかりません",
"select_items": "項目を選択…",
"search": "検索…",
"expand_card": "カードを展開",
"copied": "コピーしました",
"click_to_copy": "クリックしてコピー",
"something_went_wrong": "エラーが発生しました",
"go_back_to_home": "ホームへ戻る",
"alternative_sign_in_methods": "別のサインイン方法",
"login_background": "ログインの背景",
"logo": "ロゴ",
"login_code": "ログインコード",
"create_a_login_code_to_sign_in_without_a_passkey_once": "ユーザーがパスキーなしで一度だけサインインできるログインコードを作成する。",
"one_hour": "1 時間",
"twelve_hours": "12 時間",
"one_day": "1 日",
"one_week": "1 週間",
"one_month": "1 ヶ月",
"expiration": "有効期限",
"generate_code": "コードの生成",
"name": "名前",
"browser_unsupported": "ブラウザ未対応",
"this_browser_does_not_support_passkeys": "このブラウザはパスキーをサポートしていません。別のサインイン方法を使用してください。",
"an_unknown_error_occurred": "不明なエラーが発生しました",
"authentication_process_was_aborted": "認証プロセスが中止されました",
"error_occurred_with_authenticator": "認証ツールでエラーが発生しました",
"authenticator_does_not_support_discoverable_credentials": "認証ツールは discoverable credential をサポートしていません",
"authenticator_does_not_support_resident_keys": "認証ツールは resident key をサポートしていません",
"passkey_was_previously_registered": "このパスキーは既に登録されています",
"authenticator_does_not_support_any_of_the_requested_algorithms": "認証ツールは要求されたアルゴリズムのいずれをもサポートしていません",
"authenticator_timed_out": "認証ツールがタイムアウトしました",
"critical_error_occurred_contact_administrator": "重大なエラーが発生しました。管理者にお問い合わせください。",
"sign_in_to": "{name} にサインイン",
"client_not_found": "クライアントが見つかりません",
"client_wants_to_access_the_following_information": "<b>{client}</b> は以下の情報にアクセスしようとしています:",
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "{appName} アカウントで <b>{client}</b> にサインインしますか?",
"email": "メール",
"view_your_email_address": "メールアドレスを表示",
"profile": "プロフィール",
"view_your_profile_information": "プロフィール情報を表示",
"groups": "グループ",
"view_the_groups_you_are_a_member_of": "あなたがメンバーであるグループを表示",
"cancel": "キャンセル",
"sign_in": "サインイン",
"try_again": "リトライ",
"client_logo": "クライアントロゴ",
"sign_out": "サインアウト",
"do_you_want_to_sign_out_of_pocketid_with_the_account": "<b>{username}</b> アカウントで {appName} からサインアウトしますか?",
"sign_in_to_appname": "{appName} にサインイン",
"please_try_to_sign_in_again": "もう一度サインインしてください。",
"authenticate_with_passkey_to_access_account": "アカウントにアクセスするには、パスキーで認証してください。",
"authenticate": "認証",
"please_try_again": "再度お試しください。",
"continue": "続行",
"alternative_sign_in": "別のサインイン",
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "パスキーにアクセスできない場合は、以下のいずれかの方法でサインインできます。",
"use_your_passkey_instead": "代わりにパスキーを使用しますか?",
"email_login": "メールでログイン",
"enter_a_login_code_to_sign_in": "ログインコードを入力してサインインしてください。",
"sign_in_with_login_code": "ログインコードでサインイン",
"request_a_login_code_via_email": "メールでログインコードをリクエストする。",
"go_back": "戻る",
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "システムに存在する場合、指定されたメールアドレスにメールが送信されました。",
"enter_code": "コードを入力",
"enter_your_email_address_to_receive_an_email_with_a_login_code": "ログインコードのメールを受信するには、メールアドレスを入力してください。",
"your_email": "あなたのメール",
"submit": "送信",
"enter_the_code_you_received_to_sign_in": "サインインするために受け取ったコードを入力してください。",
"code": "コード",
"invalid_redirect_url": "無効な redirect URL",
"audit_log": "監査ログ",
"users": "ユーザー",
"user_groups": "ユーザーグループ",
"oidc_clients": "OIDCクライアント",
"api_keys": "API キー",
"application_configuration": "アプリケーション設定",
"settings": "設定",
"update_pocket_id": "Pocket ID を更新",
"powered_by": "Powered by",
"see_your_account_activities_from_the_last_3_months": "過去3ヶ月間のアカウントアクティビティを確認する。",
"time": "時間",
"event": "イベント",
"approximate_location": "おおよその場所",
"ip_address": "IPアドレス",
"device": "デバイス",
"client": "クライアント",
"unknown": "不明",
"account_details_updated_successfully": "アカウントの詳細が正常に更新されました",
"profile_picture_updated_successfully": "プロフィール画像が正常に更新されました。反映まで数分かかる場合があります。",
"account_settings": "アカウント設定",
"passkey_missing": "パスキーが見つかりません",
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "アカウントへのアクセスを失わないように、パスキーを追加してください。",
"single_passkey_configured": "Single Passkey Configured",
"it_is_recommended_to_add_more_than_one_passkey": "アカウントへのアクセスを失わないように、複数のパスキーを追加することをお勧めします。",
"account_details": "アカウントの詳細",
"passkeys": "パスキー",
"manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "認証に使用するパスキーを管理します。",
"add_passkey": "パスキーを追加",
"create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "パスキーなしで別のデバイスからサインインするためのワンタイムログインコードを作成します。",
"create": "作成",
"first_name": "名",
"last_name": "姓",
"username": "ユーザー名",
"save": "保存",
"username_can_only_contain": "ユーザー名には小文字、数字、アンダー スコア、ドット、ハイフン、および '@' 記号のみを含めることができます",
"username_must_start_with": "ユーザー名は英数字で始まる必要があります",
"username_must_end_with": "ユーザー名は英数字で終わる必要があります",
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "以下のコードを使用してサインインしてください。コードは15分後に失効します。",
"or_visit": "or visit",
"added_on": "追加日",
"rename": "名前を変更",
"delete": "削除",
"are_you_sure_you_want_to_delete_this_passkey": "このパスキーを削除してもよろしいですか?",
"passkey_deleted_successfully": "パスキーが正常に削除されました",
"delete_passkey_name": "{passkeyName} を削除",
"passkey_name_updated_successfully": "パスキー名が正常に更新されました",
"name_passkey": "パスキー名",
"name_your_passkey_to_easily_identify_it_later": "パスキーを後で簡単に識別するように名前を付けます。",
"create_api_key": "API キーを作成",
"add_a_new_api_key_for_programmatic_access": "<link href='https://pocket-id.org/docs/api'>Pocket ID API</link> へのプログラムによるアクセス用に新しいAPI キーを追加します。",
"add_api_key": "API キーを追加",
"manage_api_keys": "API キーの管理",
"api_key_created": "APIキー が作成されました",
"for_security_reasons_this_key_will_only_be_shown_once": "セキュリティ上の理由から、このキーは一度だけ表示されます。安全に保管してください。",
"description": "説明",
"api_key": "API キー",
"close": "閉じる",
"name_to_identify_this_api_key": "この API キーを識別するための名前。",
"expires_at": "有効期限",
"when_this_api_key_will_expire": "このAPI キーが期限切れになる日付。",
"optional_description_to_help_identify_this_keys_purpose": "このキーの目的を特定するのに役立つ任意の説明。",
"expiration_date_must_be_in_the_future": "有効期限は未来の日付にする必要があります",
"revoke_api_key": "API キーの削除",
"never": "なし",
"revoke": "削除",
"api_key_revoked_successfully": "API キーが正常に削除されました",
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "APIキー \"{apiKeyName}\" を本当に削除しますかこのAPI キーを使用しているすべての連携が停止します。",
"last_used": "最終使用日",
"actions": "Actions",
"images_updated_successfully": "画像の更新が正常に完了しました。更新には数分かかる場合があります。",
"general": "一般",
"configure_smtp_to_send_emails": "新しいデバイスや場所からのログインが検出された際にユーザーに警告するメール通知を有効にします。",
"ldap": "LDAP",
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "LDAP サーバーからユーザーとグループを同期するように LDAP 設定を構成します。",
"images": "画像",
"update": "更新",
"email_configuration_updated_successfully": "メール設定が正常に更新されました",
"save_changes_question": "変更を保存しますか?",
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "テストメールを送信する前に変更を保存する必要があります。今すぐ保存しますか?",
"save_and_send": "保存して送信",
"test_email_sent_successfully": "テストメールがあなたのメールアドレスに正常に送信されました。",
"failed_to_send_test_email": "テストメールの送信に失敗しました。詳細についてはサーバーのログを確認してください。",
"smtp_configuration": "SMTP 設定",
"smtp_host": "SMTP ホスト",
"smtp_port": "SMTP ポート",
"smtp_user": "SMTP ユーザー",
"smtp_password": "SMTP パスワード",
"smtp_from": "SMTP 送信元",
"smtp_tls_option": "SMTP TLS オプション",
"email_tls_option": "メール TLS オプション",
"skip_certificate_verification": "証明書検証をスキップ",
"this_can_be_useful_for_selfsigned_certificates": "これは自己署名証明書の場合に有用です。",
"enabled_emails": "有効なメール",
"email_login_notification": "メールログイン通知",
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "ユーザーが新しいデバイスからログインした際にメールを送信する。",
"emai_login_code_requested_by_user": "ユーザーがリクエストしたメールログインコード",
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "ユーザーがメールに送信されたログインコードをリクエストすることでパスキーをバイパスできるようにします。これによりセキュリティが大幅に低下し、ユーザーのメールにアクセスできる者なら誰でもログイン可能になります。",
"email_login_code_from_admin": "管理者からのメールログインコード",
"allows_an_admin_to_send_a_login_code_to_the_user": "管理者がメールでログインコードをユーザーに送信することを許可します。",
"send_test_email": "テストメールを送信",
"application_configuration_updated_successfully": "アプリケーション設定が正常に更新されました",
"application_name": "アプリケーション名",
"session_duration": "セッション期間",
"the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "ユーザーが再度ログインする必要があるまでのセッションの継続時間。(分単位)",
"enable_self_account_editing": "自身のアカウント編集を有効にする",
"whether_the_users_should_be_able_to_edit_their_own_account_details": "ユーザーが自身のアカウントの詳細を編集できるかどうか。",
"emails_verified": "メールアドレス確認済み",
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "ユーザのEメールをOIDCクライアントで検証済みとしてマークするかどうか。",
"ldap_configuration_updated_successfully": "LDAP 設定が正常に更新されました",
"ldap_disabled_successfully": "LDAPは正常に無効化されました",
"ldap_sync_finished": "LDAP同期が完了しました",
"client_configuration": "クライアントの設定",
"ldap_url": "LDAP URL",
"ldap_bind_dn": "LDAP Bind DN",
"ldap_bind_password": "LDAP Bind Password",
"ldap_base_dn": "LDAP Base DN",
"user_search_filter": "ユーザー検索フィルター",
"the_search_filter_to_use_to_search_or_sync_users": "ユーザーの検索、同期に使用する検索フィルター。",
"groups_search_filter": "グループ検索フィルター",
"the_search_filter_to_use_to_search_or_sync_groups": "グループの検索、同期に使用する検索フィルター。",
"attribute_mapping": "属性マッピング",
"user_unique_identifier_attribute": "User Unique Identifier Attribute",
"the_value_of_this_attribute_should_never_change": "この属性の値は決して変更されてはなりません。",
"username_attribute": "ユーザー名属性",
"user_mail_attribute": "ユーザーメール属性",
"user_first_name_attribute": "ユーザーの名属性",
"user_last_name_attribute": "ユーザーの姓属性",
"user_profile_picture_attribute": "ユーザープロフィール画像属性",
"the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "この属性の値は、URL、バイナリ、またはBase64エンコードされた画像のいずれかです。",
"group_members_attribute": "グループメンバー属性",
"the_attribute_to_use_for_querying_members_of_a_group": "グループのメンバーをクエリする際に使用する属性。",
"group_unique_identifier_attribute": "グループ固有識別子属性",
"group_rdn_attribute": "Group RDN Attribute (in DN)",
"admin_group_name": "管理者グループ名",
"members_of_this_group_will_have_admin_privileges_in_pocketid": "このグループのメンバーはPocket IDで管理者権限を持っています。",
"disable": "無効",
"sync_now": "今すぐ同期",
"enable": "有効",
"user_created_successfully": "ユーザーは正常に作成されました",
"create_user": "ユーザーを作成",
"add_a_new_user_to_appname": "{appName} に新しいユーザーを追加",
"add_user": "ユーザーを追加",
"manage_users": "ユーザー管理",
"admin_privileges": "管理者権限",
"admins_have_full_access_to_the_admin_panel": "管理者は管理パネルへのフルアクセス権限を持っています。",
"delete_firstname_lastname": "削除 {firstName} {lastName}",
"are_you_sure_you_want_to_delete_this_user": "このユーザーを削除してもよろしいですか?",
"user_deleted_successfully": "ユーザーの削除が正常に完了しました",
"role": "ロール",
"source": "Source",
"admin": "管理者",
"user": "ユーザー",
"local": "ローカル",
"toggle_menu": "Toggle menu",
"edit": "編集",
"user_groups_updated_successfully": "ユーザーグループが正常に更新されました",
"user_updated_successfully": "ユーザーは正常に更新されました",
"custom_claims_updated_successfully": "カスタムクレームが正常に更新されました",
"back": "Back",
"user_details_firstname_lastname": "ユーザーの詳細 {firstName} {lastName}",
"manage_which_groups_this_user_belongs_to": "このユーザーが所属するグループを管理します。",
"custom_claims": "カスタムクレーム",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "カスタムクレームは、ユーザーに関する追加情報を保存するために使用できるキーと値のペアです。これらのクレームは、'profile' スコープが要求された場合に ID トークンに含まれます。",
"user_group_created_successfully": "ユーザーグループが正常に作成されました",
"create_user_group": "ユーザーグループを作成",
"create_a_new_group_that_can_be_assigned_to_users": "ユーザーに割り当て可能な新しいグループを作成します。",
"add_group": "グループを追加",
"manage_user_groups": "ユーザーグループの管理",
"friendly_name": "Friendly Name",
"name_that_will_be_displayed_in_the_ui": "Name that will be displayed in the UI",
"name_that_will_be_in_the_groups_claim": "Name that will be in the \"groups\" claim",
"delete_name": "{name} を削除",
"are_you_sure_you_want_to_delete_this_user_group": "このユーザーグループを削除しますか?",
"user_group_deleted_successfully": "ユーザーグループが正常に削除されました",
"user_count": "ユーザー数",
"user_group_updated_successfully": "ユーザーグループが正常に更新されました",
"users_updated_successfully": "ユーザーは正常に更新されました",
"user_group_details_name": "ユーザーグループの詳細 {name}",
"assign_users_to_this_group": "このグループにユーザーを割り当てます。",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "カスタムクレームは、ユーザーに関する追加情報を保存するために使用できるキーと値のペアです。これらのクレームは、'profile' スコープが要求された場合に ID トークンに含まれます。競合が発生した場合、ユーザーに定義されたカスタムクレームが優先されます。",
"oidc_client_created_successfully": "OIDC クライアントが正常に作成されました",
"create_oidc_client": "OIDC クライアントの作成",
"add_a_new_oidc_client_to_appname": "{appName} に新しい OIDC クライアントを追加します。",
"add_oidc_client": "OIDC クライアントを追加",
"manage_oidc_clients": "OIDC クライアントの管理",
"one_time_link": "ワンタイムリンク",
"use_this_link_to_sign_in_once": "このリンクを使用してサインインしてください。これは、まだパスキーを追加していないユーザーや、パスキーを紛失したユーザーに必要な手順です。",
"add": "追加",
"callback_urls": "Callback URLs",
"logout_callback_urls": "Logout Callback URLs",
"public_client": "Public Client",
"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.",
"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.",
"requires_reauthentication": "再認証が必要",
"requires_users_to_authenticate_again_on_each_authorization": "Requires users to authenticate again on each authorization, even if already signed in",
"name_logo": "{name} ロゴ",
"change_logo": "ロゴの変更",
"upload_logo": "ロゴをアップロード",
"remove_logo": "ロゴを削除",
"are_you_sure_you_want_to_delete_this_oidc_client": "このOIDCクライアントを削除してもよろしいですか",
"oidc_client_deleted_successfully": "OIDC クライアントが正常に削除されました",
"authorization_url": "Authorization URL",
"oidc_discovery_url": "OIDC Discovery URL",
"token_url": "Token URL",
"userinfo_url": "Userinfo URL",
"logout_url": "Logout URL",
"certificate_url": "Certificate URL",
"enabled": "有効",
"disabled": "無効",
"oidc_client_updated_successfully": "OIDC クライアントが正常に更新されました",
"create_new_client_secret": "新しいクライアントシークレットを作成する",
"are_you_sure_you_want_to_create_a_new_client_secret": "新しいクライアントシークレットを作成してもよろしいですか?古いシークレットは無効化されます。",
"generate": "Generate",
"new_client_secret_created_successfully": "新しいクライアントシークレットが正常に作成されました",
"allowed_user_groups_updated_successfully": "許可されたユーザーグループが正常に更新されました",
"oidc_client_name": "OIDC クライアント {name}",
"client_id": "Client ID",
"client_secret": "クライアントシークレット",
"show_more_details": "詳細を表示",
"allowed_user_groups": "許可されたユーザーグループ",
"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.",
"favicon": "Favicon",
"light_mode_logo": "ライトモードのロゴ",
"dark_mode_logo": "ダークモードのロゴ",
"background_image": "背景画像",
"language": "言語",
"reset_profile_picture_question": "プロフィール画像をリセットしますか?",
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "アップロードされた画像を削除し、プロフィール画像をデフォルトにリセットします。続行しますか?",
"reset": "リセット",
"reset_to_default": "デフォルトに戻す",
"profile_picture_has_been_reset": "プロフィール画像がリセットされました。更新には数分かかる場合があります。",
"select_the_language_you_want_to_use": "使用する言語を選択してください。一部のテキストは自動翻訳により、不正確な場合がありますのでご注意ください。",
"contribute_to_translation": "問題が見つかった場合は、 <link href='https://crowdin.com/project/pocket-id'>Crowdin</link> で翻訳に貢献してください。",
"personal": "Personal",
"global": "Global",
"all_users": "すべてのユーザー",
"all_events": "すべてのイベント",
"all_clients": "すべてのクライアント",
"all_locations": "すべての場所",
"global_audit_log": "グローバル監査ログ",
"see_all_account_activities_from_the_last_3_months": "過去3ヶ月間のすべてのユーザーアクティビティを表示します。",
"token_sign_in": "トークンサインイン",
"client_authorization": "Client Authorization",
"new_client_authorization": "New Client Authorization",
"disable_animations": "アニメーションの無効化",
"turn_off_ui_animations": "Turn off animations throughout the UI.",
"user_disabled": "アカウントの無効化",
"disabled_users_cannot_log_in_or_use_services": "無効化されたユーザーはログインやサービスを利用できません。",
"user_disabled_successfully": "ユーザーが正常に無効化されました。",
"user_enabled_successfully": "ユーザーが正常に有効化されました。",
"status": "Status",
"disable_firstname_lastname": "無効化 {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.",
"ldap_soft_delete_users": "LDAPから無効なユーザーを保持します。",
"ldap_soft_delete_users_description": "有効にすると、LDAPから削除されたユーザーはシステムから削除されるのではなく、無効化されます。",
"login_code_email_success": "ログインコードがユーザーに送信されました。",
"send_email": "メールを送信",
"show_code": "コードを表示",
"callback_url_description": "クライアントが提供するURL。空白のままにすると自動的に追加されます。ワイルドカード(*)はサポートされていますが、セキュリティを向上させるためには避けてください。",
"logout_callback_url_description": "クライアントがログアウト用に提供するURL。ワイルドカード(*)はサポートされていますが、セキュリティを向上させるためには避けてください。",
"api_key_expiration": "API キーの有効期限",
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "API キーの有効期限が近づいたら、ユーザーにメールを送信します。",
"authorize_device": "デバイスの認証",
"the_device_has_been_authorized": "デバイスは認証されました。",
"enter_code_displayed_in_previous_step": "前のステップで表示されたコードを入力してください。",
"authorize": "Authorize",
"federated_client_credentials": "連携クライアントの資格情報",
"federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.",
"add_federated_client_credential": "Add Federated Client Credential",
"add_another_federated_client_credential": "Add another federated client credential",
"oidc_allowed_group_count": "許可されたグループ数",
"unrestricted": "制限なし",
"show_advanced_options": "詳細設定を表示",
"hide_advanced_options": "詳細設定を隠す",
"oidc_data_preview": "OIDC Data Preview",
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Preview the OIDC data that would be sent for different users",
"id_token": "ID Token",
"access_token": "Access Token",
"userinfo": "Userinfo",
"id_token_payload": "ID Token Payload",
"access_token_payload": "Access Token Payload",
"userinfo_endpoint_response": "Userinfo Endpoint Response",
"copy": "コピー",
"no_preview_data_available": "No preview data available",
"copy_all": "すべてコピー",
"preview": "プレビュー",
"preview_for_user": "{name} のプレビュー",
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Preview the OIDC data that would be sent for this user",
"show": "表示",
"select_an_option": "Select an option",
"select_user": "ユーザーを選択",
"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": "Color Value",
"apply": "適用",
"signup_token": "サインアップトークン",
"create_a_signup_token_to_allow_new_user_registration": "新規ユーザー登録を許可するためのサインアップトークンを作成する。",
"usage_limit": "使用回数制限",
"number_of_times_token_can_be_used": "サインアップトークンが使用できる回数。",
"expires": "Expires",
"signup": "サインアップ",
"user_creation": "ユーザー作成",
"configure_user_creation": "新しいユーザーのサインアップ方法やデフォルトの権限など、ユーザー作成設定を管理します。",
"user_creation_groups_description": "新しいユーザーにこれらのグループを自動的に割り当てます。",
"user_creation_claims_description": "新しいユーザーにこれらのカスタムクレームを自動的に割り当てます。",
"user_creation_updated_successfully": "ユーザー作成設定が正常に更新されました。",
"signup_disabled_description": "ユーザーのサインアップは無効化されています。管理者のみが新しいユーザーアカウントを作成できます。",
"signup_requires_valid_token": "アカウントを作成するには有効なサインアップトークンが必要です",
"validating_signup_token": "サインアップトークンを検証中",
"go_to_login": "ログインへ移動",
"signup_to_appname": "{appName} にサインアップ",
"create_your_account_to_get_started": "アカウントを作成して始めましょう。",
"initial_account_creation_description": "ご利用を開始するにはアカウントを作成してください。パスキーの設定は後で可能です。",
"setup_your_passkey": "パスキーを設定",
"create_a_passkey_to_securely_access_your_account": "安全にアカウントにアクセスするためのパスキーを作成します。これがサインインの主な方法になります。",
"skip_for_now": "あとで",
"account_created": "アカウントが作成されました",
"enable_user_signups": "ユーザーサインアップを有効にする",
"enable_user_signups_description": "Pocket IDでユーザーが新規アカウントを登録する方法を決定します。",
"user_signups_are_disabled": "ユーザーのサインアップは現在無効化されています",
"create_signup_token": "サインアップトークンを作成",
"view_active_signup_tokens": "有効なサインアップトークンを表示",
"manage_signup_tokens": "サインアップトークンの管理",
"view_and_manage_active_signup_tokens": "有効なサインアップトークンの表示と管理。",
"signup_token_deleted_successfully": "サインアップトークンが正常に削除されました。",
"expired": "Expired",
"used_up": "Used Up",
"active": "Active",
"usage": "Usage",
"created": "Created",
"token": "トークン",
"loading": "読み込み中",
"delete_signup_token": "サインアップトークンを削除",
"are_you_sure_you_want_to_delete_this_signup_token": "このサインアップトークンを削除してもよろしいですか?この操作は元に戻せません。",
"signup_with_token": "トークンでサインアップ",
"signup_with_token_description": "ユーザーは管理者が作成した有効なサインアップトークンを使用してのみサインアップできます。",
"signup_open": "サインアップを開く",
"signup_open_description": "誰でも制限なしに新しいアカウントを作成できます。",
"of": "of",
"skip_passkey_setup": "パスキーの設定をスキップ",
"skip_passkey_setup_description": "パスキーの設定を強く推奨します。設定していない場合、セッションが切れた時点でアカウントにアクセスできなくなります。",
"my_apps": "マイアプリ",
"no_apps_available": "利用可能なアプリはありません",
"contact_your_administrator_for_app_access": "アプリケーションにアクセスするには、管理者に問い合わせてください。",
"launch": "起動",
"client_launch_url": "Client Launch URL",
"client_launch_url_description": "ユーザーが「マイアプリ」ページからアプリを起動した際に開かれるURL。",
"client_name_description": "The name of the client that shows in the Pocket ID UI.",
"revoke_access": "アクセスを取り消す",
"revoke_access_description": "<b>{clientName}</b>へのアクセスを取り消します。 <b>{clientName}</b> はあなたのアカウント情報にアクセスできなくなります。",
"revoke_access_successful": "{clientName} へのアクセスは正常に取り消されました。",
"last_signed_in_ago": "Last signed in {time} ago",
"invalid_client_id": "Client ID can only contain letters, numbers, underscores, and hyphens",
"custom_client_id_description": "Set a custom client ID if this is required by your application. Otherwise, leave it blank to generate a random one.",
"generated": "Generated",
"administration": "Administration",
"group_rdn_attribute_description": "The attribute used in the groups distinguished name (DN).",
"display_name_attribute": "Display Name Attribute",
"display_name": "表示名",
"configure_application_images": "アプリケーションの画像を設定",
"ui_config_disabled_info_title": "UI Configuration Disabled",
"ui_config_disabled_info_description": "The UI configuration is disabled because the application configuration settings are managed through environment variables. Some settings may not be editable.",
"logo_from_url_description": "画像の直接のURL (svg, png, webp) を貼り付けます。アイコンは <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> か <link href=\"https://dashboardicons.com\">Dashboard Icons</link> で探せます。",
"invalid_url": "無効な URL",
"require_user_email": "Require Email Address",
"require_user_email_description": "ユーザーにメールアドレスの登録を必須とします。無効にした場合、メールアドレスを持たないユーザーはメールアドレスが必要な機能を利用できなくなります。",
"view": "表示",
"toggle_columns": "列の表示/非表示を切り替える",
"locale": "ロケール",
"ldap_id": "LDAP ID",
"reauthentication": "再認証",
"clear_filters": "フィルターをクリア",
"default_profile_picture": "デフォルトのプロフィール画像"
}

View File

@@ -103,8 +103,8 @@
"device": "기기", "device": "기기",
"client": "클라이언트", "client": "클라이언트",
"unknown": "알 수 없음", "unknown": "알 수 없음",
"account_details_updated_successfully": "계정 세부 사항이 성공적으로 업데이트되었습니다", "account_details_updated_successfully": "계정 정보가 성공적으로 변경되었습니다",
"profile_picture_updated_successfully": "프로필 사진이 성공적으로 업데이트되었습니다. 업데이트 적용까지 몇 분 정도 걸릴 수 있습니다.", "profile_picture_updated_successfully": "프로필 사진이 성공적으로 변경되었습니다. 변경 적용까지 몇 분 정도 걸릴 수 있습니다.",
"account_settings": "계정 설정", "account_settings": "계정 설정",
"passkey_missing": "패스키가 없습니다", "passkey_missing": "패스키가 없습니다",
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "계정 접근 권한을 잃지 않기 위해 패스키를 추가해주세요.", "please_provide_a_passkey_to_prevent_losing_access_to_your_account": "계정 접근 권한을 잃지 않기 위해 패스키를 추가해주세요.",
@@ -131,11 +131,11 @@
"are_you_sure_you_want_to_delete_this_passkey": "이 패스키를 삭제하시겠습니까?", "are_you_sure_you_want_to_delete_this_passkey": "이 패스키를 삭제하시겠습니까?",
"passkey_deleted_successfully": "패스키가 성공적으로 삭제되었습니다", "passkey_deleted_successfully": "패스키가 성공적으로 삭제되었습니다",
"delete_passkey_name": "{passkeyName} 삭제", "delete_passkey_name": "{passkeyName} 삭제",
"passkey_name_updated_successfully": "패스키 이름이 성공적으로 업데이트되었습니다", "passkey_name_updated_successfully": "패스키 이름이 성공적으로 변경되었습니다",
"name_passkey": "패스키 이름", "name_passkey": "패스키 이름",
"name_your_passkey_to_easily_identify_it_later": "패스키의 이름을 지정하여 나중에 쉽게 구분할 수 있도록 합니다.", "name_your_passkey_to_easily_identify_it_later": "패스키의 이름을 지정하여 나중에 쉽게 구분할 수 있도록 합니다.",
"create_api_key": "API 키 생성", "create_api_key": "API 키 생성",
"add_a_new_api_key_for_programmatic_access": "<link href='https://pocket-id.org/docs/api'>Pocket ID API에</link> 대한 프로그래매틱 액세스를 위해 새 API 키를 추가하세요.", "add_a_new_api_key_for_programmatic_access": "<link href='https://pocket-id.org/docs/api'>Pocket ID API에</link> 대한 프로그램 접근을 위해 새 API 키를 추가하세요.",
"add_api_key": "API 키 추가", "add_api_key": "API 키 추가",
"manage_api_keys": "API 키 관리", "manage_api_keys": "API 키 관리",
"api_key_created": "API 키 생성됨", "api_key_created": "API 키 생성됨",
@@ -155,14 +155,14 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "API 키 \"{apiKeyName}\"를 정말로 취소하시겠습니까? 이 키를 사용하는 모든 통합이 작동하지 않습니다.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "API 키 \"{apiKeyName}\"를 정말로 취소하시겠습니까? 이 키를 사용하는 모든 통합이 작동하지 않습니다.",
"last_used": "마지막 사용", "last_used": "마지막 사용",
"actions": "동작", "actions": "동작",
"images_updated_successfully": "이미지가 성공적으로 업데이트되었습니다", "images_updated_successfully": "이미지가 성공적으로 변경되었습니다. 변경에는 몇 분 정도 소요될 수 있습니다.",
"general": "일반", "general": "일반",
"configure_smtp_to_send_emails": "새로운 기기나 위치에서 로그인 감지 시 이메일 알림을 활성화합니다.", "configure_smtp_to_send_emails": "새로운 기기나 위치에서 로그인 감지 시 이메일 알림을 활성화합니다.",
"ldap": "LDAP", "ldap": "LDAP",
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "LDAP 서버에서 사용자와 그룹을 동기화하기 위해 LDAP을 구성합니다.", "configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "LDAP 서버에서 사용자와 그룹을 동기화하기 위해 LDAP을 구성합니다.",
"images": "이미지", "images": "이미지",
"update": "업데이트", "update": "업데이트",
"email_configuration_updated_successfully": "이메일 설정이 성공적으로 업데이트되었습니다", "email_configuration_updated_successfully": "이메일 구성이 성공적으로 변경되었습니다",
"save_changes_question": "변경 내용을 저장하시겠습니까?", "save_changes_question": "변경 내용을 저장하시겠습니까?",
"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": "테스트 이메일을 전송하기 전에 변경 내용을 저장해야 합니다. 지금 저장하시겠습니까?",
"save_and_send": "저장하고 전송하기", "save_and_send": "저장하고 전송하기",
@@ -186,7 +186,7 @@
"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": "테스트 이메일 보내기",
"application_configuration_updated_successfully": "애플리케이션 구성이 성공적으로 업데이트되었습니다", "application_configuration_updated_successfully": "애플리케이션 구성이 성공적으로 변경되었습니다",
"application_name": "애플리케이션 이름", "application_name": "애플리케이션 이름",
"session_duration": "세션 기간", "session_duration": "세션 기간",
"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": "사용자가 다시 로그인하기 전 세션의 시간(분)입니다.",
@@ -194,7 +194,7 @@
"whether_the_users_should_be_able_to_edit_their_own_account_details": "사용자가 자신의 계정 정보를 편집할 수 있습니다.", "whether_the_users_should_be_able_to_edit_their_own_account_details": "사용자가 자신의 계정 정보를 편집할 수 있습니다.",
"emails_verified": "이메일 인증됨", "emails_verified": "이메일 인증됨",
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "OIDC 클라이언트에게 사용자의 이메일이 인증된 것으로 표시합니다.", "whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "OIDC 클라이언트에게 사용자의 이메일이 인증된 것으로 표시합니다.",
"ldap_configuration_updated_successfully": "LDAP 구성이 성공적으로 업데이트되었습니다", "ldap_configuration_updated_successfully": "LDAP 구성이 성공적으로 변경되었습니다",
"ldap_disabled_successfully": "LDAP가 성공적으로 비활성화되었습니다", "ldap_disabled_successfully": "LDAP가 성공적으로 비활성화되었습니다",
"ldap_sync_finished": "LDAP 동기화 완료", "ldap_sync_finished": "LDAP 동기화 완료",
"client_configuration": "클라이언트 구성", "client_configuration": "클라이언트 구성",
@@ -301,7 +301,7 @@
"are_you_sure_you_want_to_create_a_new_client_secret": "새로운 클라이언트 시크릿 생성하시겠습니까? 기존 클라이언트 시크릿은 무효화됩니다.", "are_you_sure_you_want_to_create_a_new_client_secret": "새로운 클라이언트 시크릿 생성하시겠습니까? 기존 클라이언트 시크릿은 무효화됩니다.",
"generate": "생성", "generate": "생성",
"new_client_secret_created_successfully": "새로운 클라이언트 시크릿이 성공적으로 생성되었습니다", "new_client_secret_created_successfully": "새로운 클라이언트 시크릿이 성공적으로 생성되었습니다",
"allowed_user_groups_updated_successfully": "허용된 사용자 그룹이 성공적으로 업데이트되었습니다", "allowed_user_groups_updated_successfully": "허용된 사용자 그룹이 성공적으로 변경되었습니다",
"oidc_client_name": "OIDC 클라이언트 {name}", "oidc_client_name": "OIDC 클라이언트 {name}",
"client_id": "클라이언트 ID", "client_id": "클라이언트 ID",
"client_secret": "클라이언트 시크릿", "client_secret": "클라이언트 시크릿",
@@ -395,7 +395,7 @@
"configure_user_creation": "사용자 생성 설정을 관리합니다. 여기에는 신규 사용자 등록 방법 및 신규 사용자의 기본 권한이 포함됩니다.", "configure_user_creation": "사용자 생성 설정을 관리합니다. 여기에는 신규 사용자 등록 방법 및 신규 사용자의 기본 권한이 포함됩니다.",
"user_creation_groups_description": "새 사용자가 계정을 만들 때 이 그룹을 자동으로 할당합니다.", "user_creation_groups_description": "새 사용자가 계정을 만들 때 이 그룹을 자동으로 할당합니다.",
"user_creation_claims_description": "새 사용자가 계정을 만들 때 이 사용자 정의 클레임을 자동으로 할당합니다.", "user_creation_claims_description": "새 사용자가 계정을 만들 때 이 사용자 정의 클레임을 자동으로 할당합니다.",
"user_creation_updated_successfully": "사용자 생성 설정이 성공적으로 업데이트되었습니다.", "user_creation_updated_successfully": "사용자 생성 설정이 성공적으로 변경되었습니다.",
"signup_disabled_description": "계정 생성이 완전히 비활성화됩니다. 새로운 사용자 계정은 관리자만 생성할 수 있습니다.", "signup_disabled_description": "계정 생성이 완전히 비활성화됩니다. 새로운 사용자 계정은 관리자만 생성할 수 있습니다.",
"signup_requires_valid_token": "계정을 생성하려면 유효한 가입 토큰이 필요합니다", "signup_requires_valid_token": "계정을 생성하려면 유효한 가입 토큰이 필요합니다",
"validating_signup_token": "가입 토큰 검증", "validating_signup_token": "가입 토큰 검증",
@@ -455,5 +455,12 @@
"logo_from_url_description": "이미지 다이렉트 URL (svg, png, webp)을 붙여넣으세요. <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> 또는 <link href=\"https://dashboardicons.com\">Dashboard Icons</link>에서 아이콘을 찾으세요.", "logo_from_url_description": "이미지 다이렉트 URL (svg, png, webp)을 붙여넣으세요. <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> 또는 <link href=\"https://dashboardicons.com\">Dashboard Icons</link>에서 아이콘을 찾으세요.",
"invalid_url": "잘못된 URL", "invalid_url": "잘못된 URL",
"require_user_email": "이메일 주소 필수", "require_user_email": "이메일 주소 필수",
"require_user_email_description": "사용자에게 이메일 주소가 있어야 합니다. 이 설정이 비활성화되면 이메일 주소가 없는 사용자는 이메일 주소가 필요한 기능을 사용할 수 없습니다." "require_user_email_description": "사용자에게 이메일 주소가 있어야 합니다. 이 설정이 비활성화되면 이메일 주소가 없는 사용자는 이메일 주소가 필요한 기능을 사용할 수 없습니다.",
"view": "보기",
"toggle_columns": "열 표시/숨기기",
"locale": "로케일",
"ldap_id": "LDAP ID",
"reauthentication": "재인증",
"clear_filters": "필터 지우기",
"default_profile_picture": "기본 프로필 사진"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Weet je zeker dat je de API-sleutel \"{apiKeyName}\" wilt intrekken? Hiermee worden alle integraties die deze sleutel gebruiken, verbroken.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Weet je zeker dat je de API-sleutel \"{apiKeyName}\" wilt intrekken? Hiermee worden alle integraties die deze sleutel gebruiken, verbroken.",
"last_used": "Laatst gebruikt", "last_used": "Laatst gebruikt",
"actions": "Acties", "actions": "Acties",
"images_updated_successfully": "Afbeeldingen succesvol bijgewerkt", "images_updated_successfully": "Afbeeldingen zijn bijgewerkt. Het kan even duren voordat alles is bijgewerkt.",
"general": "Algemeen", "general": "Algemeen",
"configure_smtp_to_send_emails": "Zet e-mailmeldingen aan om gebruikers te laten weten als iemand inlogt vanaf een nieuw apparaat of een nieuwe plek.", "configure_smtp_to_send_emails": "Zet e-mailmeldingen aan om gebruikers te laten weten als iemand inlogt vanaf een nieuw apparaat of een nieuwe plek.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Plak een directe afbeeldings-URL (svg, png, webp). Zoek pictogrammen op <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> of <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Plak een directe afbeeldings-URL (svg, png, webp). Zoek pictogrammen op <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> of <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Ongeldige URL", "invalid_url": "Ongeldige URL",
"require_user_email": "E-mailadres vereist", "require_user_email": "E-mailadres vereist",
"require_user_email_description": "Je moet een e-mailadres hebben. Als je dit uitschakelt, kunnen mensen zonder e-mailadres geen functies gebruiken waarvoor een e-mailadres nodig is." "require_user_email_description": "Je moet een e-mailadres hebben. Als je dit uitschakelt, kunnen mensen zonder e-mailadres geen functies gebruiken waarvoor een e-mailadres nodig is.",
"view": "Bekijken",
"toggle_columns": "Kolommen wisselen",
"locale": "Locatie",
"ldap_id": "LDAP-ID",
"reauthentication": "Opnieuw inloggen",
"clear_filters": "Filters wissen",
"default_profile_picture": "Standaard profielfoto"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Czy na pewno chcesz unieważnić klucz API \"{apiKeyName}\"? To przerwie wszelkie integracje korzystające z tego klucza.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Czy na pewno chcesz unieważnić klucz API \"{apiKeyName}\"? To przerwie wszelkie integracje korzystające z tego klucza.",
"last_used": "Ostatnio używane", "last_used": "Ostatnio używane",
"actions": "Akcje", "actions": "Akcje",
"images_updated_successfully": "Sukces! Obrazy zostały zaktualizowane.", "images_updated_successfully": "Obrazy zostały pomyślnie zaktualizowane. Aktualizacja może potrwać kilka minut.",
"general": "Ogólne", "general": "Ogólne",
"configure_smtp_to_send_emails": "Włącz powiadomienia e-mail, aby informować użytkowników, gdy logowanie zostanie wykryte z nowego urządzenia lub lokalizacji.", "configure_smtp_to_send_emails": "Włącz powiadomienia e-mail, aby informować użytkowników, gdy logowanie zostanie wykryte z nowego urządzenia lub lokalizacji.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Wklej bezpośredni adres URL obrazu (svg, png, webp). Znajdź ikony na stronie <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> lub <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Wklej bezpośredni adres URL obrazu (svg, png, webp). Znajdź ikony na stronie <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> lub <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Nieprawidłowy adres URL", "invalid_url": "Nieprawidłowy adres URL",
"require_user_email": "Wymagany adres e-mail", "require_user_email": "Wymagany adres e-mail",
"require_user_email_description": "Wymaga od was posiadania adresu e-mail. Jeśli opcja zostanie wyłączona, wy bez adresu e-mail nie będziecie mogli korzystać z funkcji wymagających adresu e-mail." "require_user_email_description": "Wymaga od was posiadania adresu e-mail. Jeśli opcja zostanie wyłączona, wy bez adresu e-mail nie będziecie mogli korzystać z funkcji wymagających adresu e-mail.",
"view": "Widok",
"toggle_columns": "Przełącz kolumny",
"locale": "Ustawienia regionalne",
"ldap_id": "Identyfikator LDAP",
"reauthentication": "Ponowne uwierzytelnianie",
"clear_filters": "Wyczyść filtry",
"default_profile_picture": "Domyślne zdjęcie profilowe"
} }

View File

@@ -16,7 +16,7 @@
"click_profile_picture_to_upload_custom": "Clique na foto de perfil para enviar uma imagem personalizada dos seus arquivos.", "click_profile_picture_to_upload_custom": "Clique na foto de perfil para enviar uma imagem personalizada dos seus arquivos.",
"image_should_be_in_format": "A imagem deve estar no formato PNG ou JPEG.", "image_should_be_in_format": "A imagem deve estar no formato PNG ou JPEG.",
"items_per_page": "Itens por página", "items_per_page": "Itens por página",
"no_items_found": "Nenhum item encontrado", "no_items_found": "Nada foi encontrado",
"select_items": "Selecione os itens...", "select_items": "Selecione os itens...",
"search": "Pesquisar...", "search": "Pesquisar...",
"expand_card": "Expandir cartão", "expand_card": "Expandir cartão",
@@ -27,8 +27,8 @@
"alternative_sign_in_methods": "Outras formas de entrar", "alternative_sign_in_methods": "Outras formas de entrar",
"login_background": "Histórico de login", "login_background": "Histórico de login",
"logo": "Logotipo", "logo": "Logotipo",
"login_code": "Código de Login:", "login_code": "Código de Login",
"create_a_login_code_to_sign_in_without_a_passkey_once": "Crie um código de login que o usuário possa usar para entrar sem precisar digitar uma senha.", "create_a_login_code_to_sign_in_without_a_passkey_once": "Crie um código de login de uso único para que o usuário possa entrar sem precisar de uma chave de acesso.",
"one_hour": "1 hora", "one_hour": "1 hora",
"twelve_hours": "12 horas", "twelve_hours": "12 horas",
"one_day": "1 dia", "one_day": "1 dia",
@@ -38,52 +38,52 @@
"generate_code": "Gerar Código", "generate_code": "Gerar Código",
"name": "Nome", "name": "Nome",
"browser_unsupported": "Navegador não suportado", "browser_unsupported": "Navegador não suportado",
"this_browser_does_not_support_passkeys": "Esse navegador não aceita chaves de acesso. Tenta usar outro jeito de entrar.", "this_browser_does_not_support_passkeys": "Este navegador não aceita chaves de acesso. Tente usar outro método de login.",
"an_unknown_error_occurred": "Ocorreu um erro desconhecido", "an_unknown_error_occurred": "Ocorreu um erro desconhecido",
"authentication_process_was_aborted": "O processo de autenticação foi abortado", "authentication_process_was_aborted": "O processo de autenticação foi abortado",
"error_occurred_with_authenticator": "Ocorreu um erro com o autenticador", "error_occurred_with_authenticator": "Ocorreu um erro com o autenticador",
"authenticator_does_not_support_discoverable_credentials": "O autenticador não suporta credenciais detectáveis", "authenticator_does_not_support_discoverable_credentials": "O autenticador não suporta credenciais detectáveis",
"authenticator_does_not_support_resident_keys": "O autenticador não aceita chaves residentes.", "authenticator_does_not_support_resident_keys": "O autenticador não aceita chaves residentes",
"passkey_was_previously_registered": "Essa chave mestra já foi registrada antes.", "passkey_was_previously_registered": "Esta chave de acesso já está registrada",
"authenticator_does_not_support_any_of_the_requested_algorithms": "O autenticador não suporta nenhum dos algoritmos solicitados.", "authenticator_does_not_support_any_of_the_requested_algorithms": "O autenticador não suporta nenhum dos algoritmos solicitados",
"authenticator_timed_out": "Tempo limite do autenticador atingido", "authenticator_timed_out": "Tempo limite do autenticador atingido",
"critical_error_occurred_contact_administrator": "Ocorreu um erro grave. Por favor, entre em contato com o administrador.", "critical_error_occurred_contact_administrator": "Ocorreu um erro grave. Por favor, entre em contato com o administrador.",
"sign_in_to": "Entrar em {name}", "sign_in_to": "Entrar em {name}",
"client_not_found": "Cliente não encontrado", "client_not_found": "Cliente não encontrado",
"client_wants_to_access_the_following_information": "<b>{client}</b> quer acessar as seguintes informações:", "client_wants_to_access_the_following_information": "<b>{client}</b> quer acessar as seguintes informações:",
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "Você quer entrar em <b>{client}</b> com a sua conta {appName}?", "do_you_want_to_sign_in_to_client_with_your_app_name_account": "Deseja entrar em <b>{client}</b> com a sua conta {appName}?",
"email": "E-mail", "email": "E-mail",
"view_your_email_address": "Ver seu endereço de e-mail", "view_your_email_address": "Ver seu endereço de e-mail",
"profile": "Perfil", "profile": "Perfil",
"view_your_profile_information": "Dá uma olhada nas informações do seu perfil", "view_your_profile_information": "Veja as informações do seu perfil",
"groups": "Grupos", "groups": "Grupos",
"view_the_groups_you_are_a_member_of": "Dá uma olhada nos grupos que você faz parte", "view_the_groups_you_are_a_member_of": "Veja os grupos que você faz parte",
"cancel": "Cancelar", "cancel": "Cancelar",
"sign_in": "Entrar", "sign_in": "Entrar",
"try_again": "Tentar novamente", "try_again": "Tentar novamente",
"client_logo": "Logo do Cliente", "client_logo": "Logo do Cliente",
"sign_out": "Sair", "sign_out": "Sair",
"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": "Deseja sair de {appName} com a conta <b>{username}</b>?",
"sign_in_to_appname": "Entrar em {appName}", "sign_in_to_appname": "Entrar em {appName}",
"please_try_to_sign_in_again": "Tenta entrar de novo.", "please_try_to_sign_in_again": "Tente entrar novamente.",
"authenticate_with_passkey_to_access_account": "Autentique-se com sua chave de acesso para entrar na sua conta.", "authenticate_with_passkey_to_access_account": "Identifique-se com sua chave de acesso para entrar em sua conta.",
"authenticate": "Autenticar", "authenticate": "Autenticar",
"please_try_again": "Tenta de novo, por favor.", "please_try_again": "Por favor, tente novamente.",
"continue": "Continuar", "continue": "Continuar",
"alternative_sign_in": "Entrar de outra forma", "alternative_sign_in": "Entrar de outra forma",
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Se você não tem acesso à sua chave de acesso, pode entrar usando um dos métodos a seguir.", "if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Se perdeu sua chave de acesso, pode entrar usando um dos métodos a seguir.",
"use_your_passkey_instead": "Quer usar sua chave de acesso?", "use_your_passkey_instead": "Quer usar sua chave de acesso?",
"email_login": "Entrar com e-mail", "email_login": "Entrar com e-mail",
"enter_a_login_code_to_sign_in": "Digite um código de login para entrar.", "enter_a_login_code_to_sign_in": "Digite um código de login para entrar.",
"sign_in_with_login_code": "Faça login com o código de acesso", "sign_in_with_login_code": "Faça login com o código de acesso",
"request_a_login_code_via_email": "Pede um código de login por e-mail.", "request_a_login_code_via_email": "Solicitar um código de login por e-mail.",
"go_back": "Voltar", "go_back": "Voltar",
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "Mandamos um e-mail pro endereço que você deu, se ele estiver no nosso sistema.", "an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "E-mail enviado para o endereço fornecido, se ele existir no sistema.",
"enter_code": "Digite o código", "enter_code": "Digite o código",
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Digite seu e-mail pra receber um e-mail com um código de login.", "enter_your_email_address_to_receive_an_email_with_a_login_code": "Digite seu e-mail para receber um código de login.",
"your_email": "Seu e-mail", "your_email": "Seu e-mail",
"submit": "Enviar", "submit": "Enviar",
"enter_the_code_you_received_to_sign_in": "Digite o código que você recebeu para entrar.", "enter_the_code_you_received_to_sign_in": "Para entrar, digite o código que recebeu.",
"code": "Código", "code": "Código",
"invalid_redirect_url": "URL de redirecionamento inválido", "invalid_redirect_url": "URL de redirecionamento inválido",
"audit_log": "Registro de Auditoria", "audit_log": "Registro de Auditoria",
@@ -95,7 +95,7 @@
"settings": "Configurações", "settings": "Configurações",
"update_pocket_id": "Atualizar Pocket ID", "update_pocket_id": "Atualizar Pocket ID",
"powered_by": "Fornecido por", "powered_by": "Fornecido por",
"see_your_account_activities_from_the_last_3_months": "Veja suas atividades de conta dos últimos 3 meses.", "see_your_account_activities_from_the_last_3_months": "Veja as atividades da conta nos últimos 3 meses.",
"time": "Hora", "time": "Hora",
"event": "Evento", "event": "Evento",
"approximate_location": "Localização Aproximada", "approximate_location": "Localização Aproximada",
@@ -104,34 +104,34 @@
"client": "Cliente", "client": "Cliente",
"unknown": "Desconhecido", "unknown": "Desconhecido",
"account_details_updated_successfully": "Detalhes da conta atualizados com sucesso", "account_details_updated_successfully": "Detalhes da conta atualizados com sucesso",
"profile_picture_updated_successfully": "Foto do perfil atualizada com sucesso. Pode demorar alguns minutos para atualizar.", "profile_picture_updated_successfully": "Foto de perfil alterada com sucesso. A atualização pode demorar alguns minutos.",
"account_settings": "Configurações de Conta", "account_settings": "Configurações de Conta",
"passkey_missing": "Chave de acesso ausente", "passkey_missing": "Chave de acesso ausente",
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Adicione uma senha para evitar perder o acesso à sua conta.", "please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Adicione uma chave de acesso para evitar perder o acesso à sua conta.",
"single_passkey_configured": "Chave única configurada", "single_passkey_configured": "Uma chave de acesso configurada",
"it_is_recommended_to_add_more_than_one_passkey": melhor adicionar mais de uma senha de acesso pra evitar perder o acesso à sua conta.", "it_is_recommended_to_add_more_than_one_passkey": recomendado adicionar mais de uma chave de acesso para evitar perder o acesso à sua conta.",
"account_details": "Detalhes da Conta", "account_details": "Detalhes da Conta",
"passkeys": "Chaves-mestras", "passkeys": "Chaves de acesso",
"manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "Gerencie as chaves de acesso que você pode usar para se autenticar.", "manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "Gerencie as chaves de acesso que pode usar para se identificar.",
"add_passkey": "Adicionar chave de acesso", "add_passkey": "Adicionar chave de acesso",
"create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "Crie um código de login único para entrar em outro dispositivo sem precisar de uma senha.", "create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "Crie um código de login único para entrar em outro dispositivo sem precisar da chave de acesso.",
"create": "Criar", "create": "Criar",
"first_name": "Primeiro nome", "first_name": "Nome",
"last_name": "Último nome", "last_name": "Sobrenome",
"username": "Nome de usuário", "username": "Nome de usuário",
"save": "Salvar", "save": "Salvar",
"username_can_only_contain": "O nome de usuário só pode conter letras minúsculas, números, underscores, pontos, hífens e símbolos '@'", "username_can_only_contain": "O nome de usuário só pode conter letras minúsculas, números, subtraço, pontos, hifens e símbolos '@'",
"username_must_start_with": "O nome de usuário precisa começar com um caractere alfanumérico.", "username_must_start_with": "O nome de usuário precisa começar com um caractere alfanumérico",
"username_must_end_with": "O nome de usuário precisa terminar com um caractere alfanumérico.", "username_must_end_with": "O nome de usuário precisa terminar com um caractere alfanumérico",
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Faça o login usando o código a seguir. O código irá expirar em 15 minutos.", "sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Faça o login usando o código a seguir. O código irá expirar em 15 minutos.",
"or_visit": "ou visite", "or_visit": "ou visite",
"added_on": "Adicionado em", "added_on": "Adicionado em",
"rename": "Renomear", "rename": "Renomear",
"delete": "Apagar", "delete": "Deletar",
"are_you_sure_you_want_to_delete_this_passkey": "Tem certeza que quer apagar essa chave de acesso?", "are_you_sure_you_want_to_delete_this_passkey": "Tem certeza que deseja deletar esta chave de acesso?",
"passkey_deleted_successfully": "Chave de acesso apagada com sucesso", "passkey_deleted_successfully": "Chave de acesso deletada com sucesso",
"delete_passkey_name": "Apagar {passkeyName}", "delete_passkey_name": "Deletar {passkeyName}",
"passkey_name_updated_successfully": "Nome da chave de acesso atualizado com sucesso", "passkey_name_updated_successfully": "Nome da chave de acesso atualizada com sucesso",
"name_passkey": "Nome da chave de acesso", "name_passkey": "Nome da chave de acesso",
"name_your_passkey_to_easily_identify_it_later": "Dê um nome à sua chave de acesso para identificá-la facilmente mais tarde.", "name_your_passkey_to_easily_identify_it_later": "Dê um nome à sua chave de acesso para identificá-la facilmente mais tarde.",
"create_api_key": "Criar chave API", "create_api_key": "Criar chave API",
@@ -139,23 +139,23 @@
"add_api_key": "Adicionar chave API", "add_api_key": "Adicionar chave API",
"manage_api_keys": "Gerenciar chaves API", "manage_api_keys": "Gerenciar chaves API",
"api_key_created": "Chave API criada", "api_key_created": "Chave API criada",
"for_security_reasons_this_key_will_only_be_shown_once": "Por segurança, essa chave só vai aparecer uma vez. Guarde-a em um lugar seguro.", "for_security_reasons_this_key_will_only_be_shown_once": "Por segurança, esta chave só será exibida uma vez. Salve em um lugar seguro.",
"description": "Descrição", "description": "Descrição",
"api_key": "Chave API", "api_key": "Chave API",
"close": "Fechar", "close": "Fechar",
"name_to_identify_this_api_key": "Nome pra identificar essa chave API.", "name_to_identify_this_api_key": "Nome para identificar esta chave API.",
"expires_at": "Vence em", "expires_at": "Valido até",
"when_this_api_key_will_expire": "Quando essa chave API vai expirar.", "when_this_api_key_will_expire": "Quando esta chave API irá expirar.",
"optional_description_to_help_identify_this_keys_purpose": "Descrição opcional para ajudar a identificar a finalidade desta chave.", "optional_description_to_help_identify_this_keys_purpose": "Descrição opcional para ajudar a identificar a finalidade desta chave.",
"expiration_date_must_be_in_the_future": "A data de validade precisa ser no futuro.", "expiration_date_must_be_in_the_future": "A data de validade precisa ser no futuro",
"revoke_api_key": "Revogar chave API", "revoke_api_key": "Revogar chave API",
"never": "Nunca", "never": "Nunca",
"revoke": "Revogar", "revoke": "Revogar",
"api_key_revoked_successfully": "Chave API revogada com sucesso", "api_key_revoked_successfully": "Chave API revogada com sucesso",
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Tem certeza que quer cancelar a chave API “{apiKeyName}”? Isso vai desligar todas as integrações que usam essa chave.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Tem certeza que deseja cancelar a chave API “{apiKeyName}”? Isto irá desligar todas as integrações que usam esta chave.",
"last_used": "Último uso", "last_used": "Último uso",
"actions": "Ações", "actions": "Ações",
"images_updated_successfully": "Imagens atualizadas com sucesso", "images_updated_successfully": "Imagens atualizadas com sucesso. A atualização pode demorar alguns minutos.",
"general": "Geral", "general": "Geral",
"configure_smtp_to_send_emails": "Ative as notificações por e-mail para avisar os usuários quando um login for detectado em um novo dispositivo ou local.", "configure_smtp_to_send_emails": "Ative as notificações por e-mail para avisar os usuários quando um login for detectado em um novo dispositivo ou local.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -164,10 +164,10 @@
"update": "Atualização", "update": "Atualização",
"email_configuration_updated_successfully": "Configuração do e-mail atualizada com sucesso", "email_configuration_updated_successfully": "Configuração do e-mail atualizada com sucesso",
"save_changes_question": "Salvar alterações?", "save_changes_question": "Salvar alterações?",
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "Você precisa salvar as alterações antes de enviar um e-mail de teste. Quer salvar agora?", "you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "É necessário salvar as alterações antes de enviar um e-mail de teste. Deseja salvar agora?",
"save_and_send": "Salvar e enviar", "save_and_send": "Salvar e enviar",
"test_email_sent_successfully": "E-mail de teste enviado com sucesso para o seu endereço de e-mail.", "test_email_sent_successfully": "E-mail de teste enviado com sucesso para o seu endereço de e-mail.",
"failed_to_send_test_email": "Não deu certo enviar o e-mail de teste. Dá uma olhada nos registros do servidor pra saber mais.", "failed_to_send_test_email": "Falha ao enviar o e-mail de teste. Verifique os registros no servidor para mais detalhes.",
"smtp_configuration": "Configuração SMTP", "smtp_configuration": "Configuração SMTP",
"smtp_host": "Host SMTP", "smtp_host": "Host SMTP",
"smtp_port": "Porta SMTP", "smtp_port": "Porta SMTP",
@@ -177,21 +177,21 @@
"smtp_tls_option": "Opção SMTP TLS", "smtp_tls_option": "Opção SMTP TLS",
"email_tls_option": "Opção TLS para e-mail", "email_tls_option": "Opção TLS para e-mail",
"skip_certificate_verification": "Pular a verificação do certificado", "skip_certificate_verification": "Pular a verificação do certificado",
"this_can_be_useful_for_selfsigned_certificates": "Isso pode ser útil para certificados autoassinados.", "this_can_be_useful_for_selfsigned_certificates": "Isto pode ser útil para certificados autoassinados.",
"enabled_emails": "E-mails ativados", "enabled_emails": "E-mails ativados",
"email_login_notification": "Notificação de login por e-mail", "email_login_notification": "Notificação de login por e-mail",
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Manda um e-mail pro usuário quando ele entrar com um novo aparelho.", "send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Envia um e-mail para o usuário quando ele entrar com um novo dispositivo.",
"emai_login_code_requested_by_user": "Código de login por e-mail solicitado pelo usuário", "emai_login_code_requested_by_user": "Código de login por e-mail solicitado pelo usuário",
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Permite que os usuários pulem as senhas pedindo um código de login que vai pro e-mail deles. Isso deixa a segurança bem mais fraca, já que qualquer um que tiver acesso ao e-mail do usuário pode entrar.", "allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Permite que os usuários ignorem suas chaves de acesso, pedindo um código de login diretamente no e-mail do usuário. Isso diminui a segurança, já que qualquer um com acesso ao e-mail do usuário pode entrar em sua conta.",
"email_login_code_from_admin": "Código de login por e-mail do administrador", "email_login_code_from_admin": "Código de login por e-mail do administrador",
"allows_an_admin_to_send_a_login_code_to_the_user": "Permite que um administrador envie um código de login pro usuário por e-mail.", "allows_an_admin_to_send_a_login_code_to_the_user": "Permite que um administrador envie um código de login para o e-mail do usuário.",
"send_test_email": "Enviar e-mail de teste", "send_test_email": "Enviar e-mail de teste",
"application_configuration_updated_successfully": "A configuração do aplicativo foi atualizada com sucesso.", "application_configuration_updated_successfully": "A configuração do aplicativo foi atualizada com sucesso",
"application_name": "Nome do aplicativo", "application_name": "Nome do aplicativo",
"session_duration": "Duração da sessão", "session_duration": "Duração da sessão",
"the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "O tempo que dura uma sessão, em minutos, antes que o usuário precise fazer login de novo.", "the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "O tempo que dura uma sessão, em minutos, antes que o usuário precise fazer login de novo.",
"enable_self_account_editing": "Ativar edição da conta pessoal", "enable_self_account_editing": "Ativar edição da conta pessoal",
"whether_the_users_should_be_able_to_edit_their_own_account_details": "Se os usuários podem editar os detalhes da conta deles.", "whether_the_users_should_be_able_to_edit_their_own_account_details": "Se os usuários podem editar os detalhes de suas contas.",
"emails_verified": "E-mails verificados", "emails_verified": "E-mails verificados",
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "Se o e-mail do usuário deve ser marcado como verificado para os clientes OIDC.", "whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "Se o e-mail do usuário deve ser marcado como verificado para os clientes OIDC.",
"ldap_configuration_updated_successfully": "Configuração LDAP atualizada com sucesso", "ldap_configuration_updated_successfully": "Configuração LDAP atualizada com sucesso",
@@ -199,41 +199,41 @@
"ldap_sync_finished": "Sincronização LDAP concluída", "ldap_sync_finished": "Sincronização LDAP concluída",
"client_configuration": "Configuração do cliente", "client_configuration": "Configuração do cliente",
"ldap_url": "URL LDAP", "ldap_url": "URL LDAP",
"ldap_bind_dn": "DN de ligação LDAP", "ldap_bind_dn": "LDAP vincular DN",
"ldap_bind_password": "Senha de ligação LDAP", "ldap_bind_password": "LDAP vincular Senha",
"ldap_base_dn": "DN base LDAP", "ldap_base_dn": "LDAP DN base",
"user_search_filter": "Filtro de pesquisa de usuários", "user_search_filter": "Filtro de pesquisa de usuários",
"the_search_filter_to_use_to_search_or_sync_users": "O filtro de pesquisa que você usa pra procurar/sincronizar usuários.", "the_search_filter_to_use_to_search_or_sync_users": "O filtro de pesquisa utilizado para encontrar/sincronizar usuários.",
"groups_search_filter": "Filtro de pesquisa de grupos", "groups_search_filter": "Filtro de pesquisa de grupos",
"the_search_filter_to_use_to_search_or_sync_groups": "O filtro de pesquisa que você usa pra procurar/sincronizar grupos.", "the_search_filter_to_use_to_search_or_sync_groups": "O filtro de pesquisa utilizado para encontrar/sincronizar grupos.",
"attribute_mapping": "Mapeamento de atributos", "attribute_mapping": "Mapeamento de atributos",
"user_unique_identifier_attribute": "Atributo de identificador único do usuário", "user_unique_identifier_attribute": "Atributo de identificador único do usuário",
"the_value_of_this_attribute_should_never_change": "O valor desse atributo nunca deve mudar.", "the_value_of_this_attribute_should_never_change": "O valor deste atributo nunca deve mudar.",
"username_attribute": "Atributo do nome de usuário", "username_attribute": "Atributo do nome de usuário",
"user_mail_attribute": "Atributo de e-mail do usuário", "user_mail_attribute": "Atributo de e-mail do usuário",
"user_first_name_attribute": "Atributo do primeiro nome do usuário", "user_first_name_attribute": "Atributo do primeiro nome do usuário",
"user_last_name_attribute": "Atributo do sobrenome do usuário", "user_last_name_attribute": "Atributo do sobrenome do usuário",
"user_profile_picture_attribute": "Atributo da foto do perfil do usuário", "user_profile_picture_attribute": "Atributo da foto do perfil do usuário",
"the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "O valor desse atributo pode ser uma URL, um binário ou uma imagem codificada em base64.", "the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "O valor deste atributo pode ser uma URL, um binário ou uma imagem codificada em base64.",
"group_members_attribute": "Atributo dos membros do grupo", "group_members_attribute": "Atributo dos membros do grupo",
"the_attribute_to_use_for_querying_members_of_a_group": "O atributo a ser usado para consultar membros de um grupo.", "the_attribute_to_use_for_querying_members_of_a_group": "O atributo a ser usado para consultar membros de um grupo.",
"group_unique_identifier_attribute": "Atributo identificador exclusivo do grupo", "group_unique_identifier_attribute": "Atributo identificador exclusivo do grupo",
"group_rdn_attribute": "Atributo RDN do grupo (em DN)", "group_rdn_attribute": "Atributo RDN do grupo (em DN)",
"admin_group_name": "Nome do grupo de administradores", "admin_group_name": "Nome do grupo de administradores",
"members_of_this_group_will_have_admin_privileges_in_pocketid": "Os membros desse grupo vão ter privilégios de administrador no Pocket ID.", "members_of_this_group_will_have_admin_privileges_in_pocketid": "Os membros deste grupo terão privilégios de administrador no Pocket ID.",
"disable": "Desativar", "disable": "Desativar",
"sync_now": "Sincronizar agora", "sync_now": "Sincronizar agora",
"enable": "Ativar", "enable": "Ativar",
"user_created_successfully": "Usuário criado com sucesso", "user_created_successfully": "Usuário criado com sucesso",
"create_user": "Criar Usuário", "create_user": "Criar Usuário",
"add_a_new_user_to_appname": "Adicionar um novo usuário para {appName}", "add_a_new_user_to_appname": "Adicionar um novo usuário em {appName}",
"add_user": "Adicionar Usuário", "add_user": "Adicionar Usuário",
"manage_users": "Gerenciar Usuários", "manage_users": "Gerenciar Usuários",
"admin_privileges": "Privilégios de administrador", "admin_privileges": "Privilégios de administrador",
"admins_have_full_access_to_the_admin_panel": "Os administradores têm acesso total ao painel de administração.", "admins_have_full_access_to_the_admin_panel": "Os administradores têm acesso total ao painel de administração.",
"delete_firstname_lastname": "Apagar {firstName} {lastName}", "delete_firstname_lastname": "Deletar {firstName} {lastName}",
"are_you_sure_you_want_to_delete_this_user": "Tem certeza que quer excluir esse usuário?", "are_you_sure_you_want_to_delete_this_user": "Tem certeza que deseja deletar este usuário?",
"user_deleted_successfully": "Usuário excluído com sucesso", "user_deleted_successfully": "Usuário deletado com sucesso",
"role": "Função", "role": "Função",
"source": "Fonte", "source": "Fonte",
"admin": "Admin", "admin": "Admin",
@@ -243,51 +243,51 @@
"edit": "Editar", "edit": "Editar",
"user_groups_updated_successfully": "Grupos de usuários atualizados com sucesso", "user_groups_updated_successfully": "Grupos de usuários atualizados com sucesso",
"user_updated_successfully": "Usuário atualizado com sucesso", "user_updated_successfully": "Usuário atualizado com sucesso",
"custom_claims_updated_successfully": "Reclamações personalizadas atualizadas com sucesso", "custom_claims_updated_successfully": "Reivindicações personalizadas atualizadas com sucesso",
"back": "Voltar", "back": "Voltar",
"user_details_firstname_lastname": "Detalhes do usuário {firstName} {lastName}", "user_details_firstname_lastname": "Detalhes do usuário {firstName} {lastName}",
"manage_which_groups_this_user_belongs_to": "Controle quais grupos esse usuário faz parte.", "manage_which_groups_this_user_belongs_to": "Gerencie os grupos deste usuário.",
"custom_claims": "Reclamações personalizadas", "custom_claims": "Reivindicações personalizadas",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "As reivindicações personalizadas são pares de chave-valor que podem ser usados para guardar informações adicionais sobre um usuário. Essas reivindicações serão incluídas no token de identificação se o escopo “perfil” for solicitado.", "custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "As reivindicações personalizadas são pares de chave-valor que podem ser utilizados para guardar informações adicionais sobre um usuário. Essas reivindicações serão incluídas no token de identificação se o escopo “profile” for solicitado.",
"user_group_created_successfully": "Grupo de usuários criado com sucesso", "user_group_created_successfully": "Grupo de usuários criado com sucesso",
"create_user_group": "Criar Grupo de Usuários", "create_user_group": "Criar Grupo de Usuários",
"create_a_new_group_that_can_be_assigned_to_users": "Crie um novo grupo que possa ser atribuído aos usuários.", "create_a_new_group_that_can_be_assigned_to_users": "Crie um grupo que possa ser atribuído aos usuários.",
"add_group": "Adicionar Grupo", "add_group": "Adicionar Grupo",
"manage_user_groups": "Gerenciar grupos de usuários", "manage_user_groups": "Gerenciar grupos de usuários",
"friendly_name": "Nome Amigável", "friendly_name": "Nome Amigável",
"name_that_will_be_displayed_in_the_ui": "Nome que vai aparecer na interface do usuário", "name_that_will_be_displayed_in_the_ui": "Nome que vai aparecer na interface do usuário",
"name_that_will_be_in_the_groups_claim": "Nome que vai aparecer na reivindicação “grupos”", "name_that_will_be_in_the_groups_claim": "Nome que vai aparecer na reivindicação “groups”",
"delete_name": "Apagar {name}", "delete_name": "Deletar {name}",
"are_you_sure_you_want_to_delete_this_user_group": "Tem certeza que quer excluir esse grupo de usuários?", "are_you_sure_you_want_to_delete_this_user_group": "Tem certeza que deseja deletar este grupo de usuários?",
"user_group_deleted_successfully": "Grupo de usuários excluído com sucesso", "user_group_deleted_successfully": "Grupo de usuários deletado com sucesso",
"user_count": "Contagem de usuários", "user_count": "Total de usuários",
"user_group_updated_successfully": "Grupo de usuários atualizado com sucesso", "user_group_updated_successfully": "Grupo de usuários atualizado com sucesso",
"users_updated_successfully": "Usuários atualizados com sucesso", "users_updated_successfully": "Usuários atualizados com sucesso",
"user_group_details_name": "Detalhes do grupo de usuários {name}", "user_group_details_name": "Detalhes do grupo de usuários {name}",
"assign_users_to_this_group": "Adicione usuários a este grupo.", "assign_users_to_this_group": "Adicione usuários a este grupo.",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "As reivindicações personalizadas são pares de chave-valor que podem ser usados para guardar informações adicionais sobre um usuário. Essas reivindicações serão incluídas no token de identificação se o escopo “perfil” for solicitado. As reivindicações personalizadas definidas no usuário serão priorizadas se houver conflitos.", "custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "As reivindicações personalizadas são pares de chave-valor que podem ser utilizados para guardar informações adicionais sobre um usuário. Essas reivindicações serão incluídas no token de identificação se o escopo “profile” for solicitado. As reivindicações personalizadas definidas no usuário serão priorizadas se houver conflitos.",
"oidc_client_created_successfully": "Cliente OIDC criado com sucesso", "oidc_client_created_successfully": "Cliente OIDC criado com sucesso",
"create_oidc_client": "Criar cliente OIDC", "create_oidc_client": "Criar cliente OIDC",
"add_a_new_oidc_client_to_appname": "Adicione um novo cliente OIDC em {appName}.", "add_a_new_oidc_client_to_appname": "Adicione um novo cliente OIDC em {appName}.",
"add_oidc_client": "Adicionar cliente OIDC", "add_oidc_client": "Adicionar cliente OIDC",
"manage_oidc_clients": "Gerenciar clientes OIDC", "manage_oidc_clients": "Gerenciar clientes OIDC",
"one_time_link": "Link único", "one_time_link": "Link de uso único",
"use_this_link_to_sign_in_once": "Use este link para fazer login uma vez. Isso é necessário para usuários que ainda não adicionaram uma chave de acesso ou que a perderam.", "use_this_link_to_sign_in_once": "Use este link para fazer login uma vez. Isso é necessário para usuários que ainda não adicionaram uma chave de acesso ou que a perderam.",
"add": "Adicionar", "add": "Adicionar",
"callback_urls": "URLs de retorno de chamada", "callback_urls": "URLs de retorno de chamada",
"logout_callback_urls": "URLs de retorno de chamada de logout", "logout_callback_urls": "URLs de retorno de chamada de logout",
"public_client": "Cliente Público", "public_client": "Cliente Público",
"public_clients_description": "Public clients do not have a client secret and use PKCE instead. Enable this if your client is a SPA or mobile app.", "public_clients_description": "Os clientes públicos não têm um segredo de cliente. Eles são feitos para aplicativos móveis, web e nativos, onde os segredos não podem ser guardados com segurança.",
"pkce": "PKCE", "pkce": "PKCE",
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "A troca de chaves públicas é um recurso de segurança que evita ataques CSRF e interceptação de códigos de autorização.", "public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "A troca de chaves públicas é um recurso de segurança que evita ataques CSRF e interceptação de códigos de autorização.",
"requires_reauthentication": "Precisa autenticar de novo", "requires_reauthentication": "Obrigatório autenticar novamente",
"requires_users_to_authenticate_again_on_each_authorization": "Pede que os usuários se autentiquem de novo em cada autorização, mesmo que já estejam conectados.", "requires_users_to_authenticate_again_on_each_authorization": "Pede que os usuários se autentiquem novamente em cada autorização, mesmo que já estejam conectados",
"name_logo": "{name} logotipo", "name_logo": "{name} logotipo",
"change_logo": "Alterar logotipo", "change_logo": "Alterar logotipo",
"upload_logo": "Carregar logotipo", "upload_logo": "Carregar logotipo",
"remove_logo": "Tirar o logotipo", "remove_logo": "Remover o logotipo",
"are_you_sure_you_want_to_delete_this_oidc_client": "Tem certeza que quer apagar esse cliente OIDC?", "are_you_sure_you_want_to_delete_this_oidc_client": "Tem certeza que deseja deletar este cliente OIDC?",
"oidc_client_deleted_successfully": "Cliente OIDC excluído com sucesso", "oidc_client_deleted_successfully": "Cliente OIDC deletado com sucesso",
"authorization_url": "URL de autorização", "authorization_url": "URL de autorização",
"oidc_discovery_url": "URL de descoberta OIDC", "oidc_discovery_url": "URL de descoberta OIDC",
"token_url": "URL do token", "token_url": "URL do token",
@@ -295,10 +295,10 @@
"logout_url": "URL de logout", "logout_url": "URL de logout",
"certificate_url": "URL do certificado", "certificate_url": "URL do certificado",
"enabled": "Habilitado", "enabled": "Habilitado",
"disabled": "Deficientes", "disabled": "Desativado",
"oidc_client_updated_successfully": "Cliente OIDC atualizado com sucesso", "oidc_client_updated_successfully": "Cliente OIDC atualizado com sucesso",
"create_new_client_secret": "Criar novo segredo do cliente", "create_new_client_secret": "Criar novo segredo do cliente",
"are_you_sure_you_want_to_create_a_new_client_secret": "Tem certeza que quer criar um novo segredo de cliente? O antigo vai ser invalidado.", "are_you_sure_you_want_to_create_a_new_client_secret": "Tem certeza que deseja criar um segredo de cliente? O antigo será invalidado.",
"generate": "Gerar", "generate": "Gerar",
"new_client_secret_created_successfully": "Novo segredo do cliente criado com sucesso", "new_client_secret_created_successfully": "Novo segredo do cliente criado com sucesso",
"allowed_user_groups_updated_successfully": "Grupos de usuários permitidos atualizados com sucesso", "allowed_user_groups_updated_successfully": "Grupos de usuários permitidos atualizados com sucesso",
@@ -307,19 +307,19 @@
"client_secret": "Segredo do cliente", "client_secret": "Segredo do cliente",
"show_more_details": "Mostrar mais detalhes", "show_more_details": "Mostrar mais detalhes",
"allowed_user_groups": "Grupos de usuários permitidos", "allowed_user_groups": "Grupos de usuários permitidos",
"add_user_groups_to_this_client_to_restrict_access_to_users_in_these_groups": "Adicione grupos de usuários a este cliente para restringir o acesso aos usuários desses grupos. Se nenhum grupo de usuários for selecionado, todos os usuários terão acesso a este cliente.", "add_user_groups_to_this_client_to_restrict_access_to_users_in_these_groups": "Adicione grupos de usuários a este cliente para restringir o acesso somente aos usuários que fazem parte destes grupos. Se nenhum grupo de usuários for selecionado, todos os usuários terão acesso a este cliente.",
"favicon": "Favicon", "favicon": "Ícone de Favorito",
"light_mode_logo": "Logotipo do modo claro", "light_mode_logo": "Logotipo do modo claro",
"dark_mode_logo": "Logotipo do Modo Escuro", "dark_mode_logo": "Logotipo do Modo Escuro",
"background_image": "Imagem de fundo", "background_image": "Imagem de fundo",
"language": "Idioma", "language": "Idioma",
"reset_profile_picture_question": "Queres redefinir a tua foto de perfil?", "reset_profile_picture_question": "Redefinir a foto de perfil?",
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Isso vai tirar a foto que você mandou e voltar a foto do perfil pro padrão. Quer mesmo continuar?", "this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Isto irá remover a foto atual e a imagem de perfil padrão será utilizada. Deseja continuar?",
"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": "A foto do perfil foi redefinida. A atualização pode demorar alguns minutos.", "profile_picture_has_been_reset": "A foto do perfil foi redefinida. A atualização pode demorar alguns minutos.",
"select_the_language_you_want_to_use": "Escolha o idioma que você quer usar. Lembre-se de que alguns textos podem ser traduzidos automaticamente e podem não estar certos.", "select_the_language_you_want_to_use": "Escolha o idioma que deseja usar. Lembre-se de que alguns textos podem ser traduzidos automaticamente e podem não estar certos.",
"contribute_to_translation": "Se você encontrar algum problema, pode ajudar na tradução no <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.", "contribute_to_translation": "Se encontrar algum problema, ajude na tradução no <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.",
"personal": "Pessoal", "personal": "Pessoal",
"global": "Global", "global": "Global",
"all_users": "Todos os usuários", "all_users": "Todos os usuários",
@@ -327,42 +327,42 @@
"all_clients": "Todos os clientes", "all_clients": "Todos os clientes",
"all_locations": "Todos os locais", "all_locations": "Todos os locais",
"global_audit_log": "Registro de auditoria global", "global_audit_log": "Registro de auditoria global",
"see_all_account_activities_from_the_last_3_months": "Dá uma olhada em tudo que os usuários fizeram nos últimos 3 meses.", "see_all_account_activities_from_the_last_3_months": "Veja todas as atividades de todos os usuários nos últimos 3 meses.",
"token_sign_in": "Entrar com token", "token_sign_in": "Entrar com token",
"client_authorization": "Autorização do cliente", "client_authorization": "Autorização do cliente",
"new_client_authorization": "Autorização de novo cliente", "new_client_authorization": "Autorização de novo cliente",
"disable_animations": "Desativar animações", "disable_animations": "Desativar animações",
"turn_off_ui_animations": "Desligue as animações em toda a interface do usuário.", "turn_off_ui_animations": "Desligue as animações em toda a interface do usuário.",
"user_disabled": "Conta desativada", "user_disabled": "Conta desativada",
"disabled_users_cannot_log_in_or_use_services": "Usuários com deficiência não conseguem fazer login ou usar os serviços.", "disabled_users_cannot_log_in_or_use_services": "Usuários inativos não conseguem fazer login ou usar os serviços.",
"user_disabled_successfully": "O usuário foi desativado com sucesso.", "user_disabled_successfully": "O usuário foi desativado com sucesso.",
"user_enabled_successfully": "O usuário foi ativado com sucesso.", "user_enabled_successfully": "O usuário foi ativado com sucesso.",
"status": "Status", "status": "Status",
"disable_firstname_lastname": "Desativar {firstName} {lastName}", "disable_firstname_lastname": "Desativar {firstName} {lastName}",
"are_you_sure_you_want_to_disable_this_user": "Tem certeza que quer desativar esse usuário? Ele não vai conseguir entrar nem acessar nenhum serviço.", "are_you_sure_you_want_to_disable_this_user": "Tem certeza que deseja desativar este usuário? Não será mais possível entrar nem acessar os serviços.",
"ldap_soft_delete_users": "Impedir que usuários desativados acessem o LDAP.", "ldap_soft_delete_users": "Manter usuários desativados no LDAP.",
"ldap_soft_delete_users_description": "Quando ativada, os usuários removidos do LDAP serão desativados em vez de excluídos do sistema.", "ldap_soft_delete_users_description": "Quando ativado, usuários removidos do LDAP serão apenas desativados ao invés de deletados do sistema.",
"login_code_email_success": "O código de login foi enviado para o usuário.", "login_code_email_success": "O código de login foi enviado para o usuário.",
"send_email": "Enviar e-mail", "send_email": "Enviar e-mail",
"show_code": "Mostrar código", "show_code": "Mostrar código",
"callback_url_description": "URL(s) fornecido(s) pelo seu cliente. Vai ser adicionado automaticamente se você deixar em branco. Caracteres curinga (*) são aceitos, mas é melhor evitar para garantir mais segurança.", "callback_url_description": "URL(s) providenciados pelo cliente. Será adicionado automaticamente se deixar em branco. Apesar de caracteres curinga (*) serem aceitos, para uma segurança maior, não é recomendado utiliza-los.",
"logout_callback_url_description": "URL(s) que seu cliente deu pra sair da conta. Você pode usar curingas (*), mas é melhor evitar pra ficar mais seguro.", "logout_callback_url_description": "URL(s) providenciados pelo cliente para sair da conta. Apesar de curingas (*) serem aceitos, para uma segurança maior, não é recomendado utiliza-los.",
"api_key_expiration": "Expiração da chave API", "api_key_expiration": "Expiração da chave API",
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Manda um e-mail pro usuário quando a chave API dele estiver quase a expirar.", "send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Envia um e-mail para o usuário quando a chave API estiver próxima de expirar.",
"authorize_device": "Autorizar dispositivo", "authorize_device": "Autorizar dispositivo",
"the_device_has_been_authorized": "O dispositivo foi autorizado.", "the_device_has_been_authorized": "O dispositivo foi autorizado.",
"enter_code_displayed_in_previous_step": "Digite o código que apareceu na etapa anterior.", "enter_code_displayed_in_previous_step": "Digite o código que apareceu na etapa anterior.",
"authorize": "Autorizar", "authorize": "Autorizar",
"federated_client_credentials": "Credenciais de Cliente Federadas", "federated_client_credentials": "Credenciais de Cliente Federadas",
"federated_client_credentials_description": "Usando credenciais de cliente federadas, você pode autenticar clientes OIDC usando tokens JWT emitidos por autoridades de terceiros.", "federated_client_credentials_description": "Ao utilizar credenciais de cliente federadas, é possível autenticar clientes OIDC usando tokens JWT emitidos por autoridades de terceiros.",
"add_federated_client_credential": "Adicionar credencial de cliente federado", "add_federated_client_credential": "Adicionar credencial de cliente federado",
"add_another_federated_client_credential": "Adicionar outra credencial de cliente federado", "add_another_federated_client_credential": "Adicionar outra credencial de cliente federado",
"oidc_allowed_group_count": "Contagem de grupos permitidos", "oidc_allowed_group_count": "Total de grupos permitidos",
"unrestricted": "Sem restrições", "unrestricted": "Sem restrições",
"show_advanced_options": "Mostrar opções avançadas", "show_advanced_options": "Mostrar opções avançadas",
"hide_advanced_options": "Ocultar opções avançadas", "hide_advanced_options": "Ocultar opções avançadas",
"oidc_data_preview": "Pré-visualização dos dados OIDC", "oidc_data_preview": "Pré-visualização dos dados OIDC",
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Dá uma olhada nos dados OIDC que seriam enviados para diferentes usuários", "preview_the_oidc_data_that_would_be_sent_for_different_users": "Veja quais os dados OIDC que seriam enviados por diferentes usuários",
"id_token": "Token de identificação", "id_token": "Token de identificação",
"access_token": "Token de acesso", "access_token": "Token de acesso",
"userinfo": "Informações do usuário", "userinfo": "Informações do usuário",
@@ -370,46 +370,46 @@
"access_token_payload": "Carga útil do token de acesso", "access_token_payload": "Carga útil do token de acesso",
"userinfo_endpoint_response": "Resposta do ponto final da informação do usuário", "userinfo_endpoint_response": "Resposta do ponto final da informação do usuário",
"copy": "Copiar", "copy": "Copiar",
"no_preview_data_available": "Não tem dados de pré-visualização disponíveis", "no_preview_data_available": "Não dados de pré-visualização disponíveis",
"copy_all": "Copiar tudo", "copy_all": "Copiar tudo",
"preview": "Pré-visualização", "preview": "Pré-visualização",
"preview_for_user": "Prévia de “ {name} ”", "preview_for_user": "Prévia de “ {name} ”",
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Dá uma olhada nos dados OIDC que seriam enviados para esse usuário.", "preview_the_oidc_data_that_would_be_sent_for_this_user": "Veja os dados OIDC que seriam enviados para este usuário",
"show": "Mostrar", "show": "Mostrar",
"select_an_option": "Escolha uma opção", "select_an_option": "Selecione uma opção",
"select_user": "Selecionar usuário", "select_user": "Selecionar usuário",
"error": "Erro", "error": "Erro",
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Escolha uma cor de destaque pra personalizar a aparência do Pocket ID.", "select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Escolha uma cor de destaque para personalizar a aparência do Pocket ID.",
"accent_color": "Cor de destaque", "accent_color": "Cor de destaque",
"custom_accent_color": "Cor de destaque personalizada", "custom_accent_color": "Cor de destaque personalizada",
"custom_accent_color_description": "Digite uma cor personalizada usando formatos de cor CSS válidos (por exemplo, hex, rgb, hsl).", "custom_accent_color_description": "Digite uma cor personalizada usando formatos de cor CSS válidos (por exemplo, hex, rgb, hsl).",
"color_value": "Valor da cor", "color_value": "Valor da cor",
"apply": "Inscreva-se", "apply": "Aplicar",
"signup_token": "Token de inscrição", "signup_token": "Token de inscrição",
"create_a_signup_token_to_allow_new_user_registration": "Crie um token de inscrição para permitir o registro de novos usuários.", "create_a_signup_token_to_allow_new_user_registration": "Crie um token de inscrição para permitir o registro de novos usuários.",
"usage_limit": "Limite de uso", "usage_limit": "Limite de uso",
"number_of_times_token_can_be_used": "Número de vezes que o token de inscrição pode ser usado.", "number_of_times_token_can_be_used": "Número de vezes que o token de inscrição pode ser usado.",
"expires": "Vence", "expires": "Valido até",
"signup": "Cadastre-se", "signup": "Cadastre-se",
"user_creation": "Criação de usuário", "user_creation": "Criar usuário",
"configure_user_creation": "Gerencie as configurações de criação de usuários, incluindo métodos de inscrição e permissões padrão para novos usuários.", "configure_user_creation": "Gerencie as configurações da criação de usuários, incluindo métodos de inscrição e permissões padrão para novos usuários.",
"user_creation_groups_description": "Atribuir esses grupos automaticamente aos novos usuários quando eles se cadastrarem.", "user_creation_groups_description": "Atribuir esses grupos automaticamente aos novos usuários no momento da inscrição.",
"user_creation_claims_description": "Atribua essas reivindicações personalizadas automaticamente aos novos usuários no momento da inscrição.", "user_creation_claims_description": "Atribua estas reivindicações personalizadas automaticamente aos novos usuários no momento da inscrição.",
"user_creation_updated_successfully": "As configurações de criação do usuário foram atualizadas com sucesso.", "user_creation_updated_successfully": "As configurações de criação do usuário foram atualizadas com sucesso.",
"signup_disabled_description": "As inscrições de usuários estão totalmente desativadas. Só os administradores podem criar novas contas de usuário.", "signup_disabled_description": "As inscrições de usuários estão totalmente desativadas. Só os administradores podem criar contas de usuário.",
"signup_requires_valid_token": preciso um token de inscrição válido pra criar uma conta.", "signup_requires_valid_token": necessário um token de inscrição válido para criar uma conta",
"validating_signup_token": "Validando o token de inscrição", "validating_signup_token": "Validando o token de inscrição",
"go_to_login": "Vá para o login", "go_to_login": "Vá para o login",
"signup_to_appname": "Cadastre-se em {appName}", "signup_to_appname": "Cadastre-se em {appName}",
"create_your_account_to_get_started": "Crie sua conta pra começar.", "create_your_account_to_get_started": "Crie sua conta para começar.",
"initial_account_creation_description": "Crie sua conta pra começar. Você vai poder definir uma senha mais tarde.", "initial_account_creation_description": "Crie sua conta para começar. Será possível cadastrar uma chave de acesso mais tarde.",
"setup_your_passkey": "Configure sua chave de acesso", "setup_your_passkey": "Configure sua chave de acesso",
"create_a_passkey_to_securely_access_your_account": "Crie uma senha para acessar sua conta com segurança. Essa vai ser sua principal forma de entrar.", "create_a_passkey_to_securely_access_your_account": "Cadastre uma chave de acesso para entrar em sua conta com segurança. Esta será sua principal forma de entrar.",
"skip_for_now": "Pular por enquanto", "skip_for_now": "Pular por enquanto",
"account_created": "Conta criada", "account_created": "Conta criada",
"enable_user_signups": "Ativar inscrições de usuários", "enable_user_signups": "Ativar inscrições de usuários",
"enable_user_signups_description": "Decida como os usuários podem se cadastrar para novas contas no Pocket ID.", "enable_user_signups_description": "Decida como os usuários podem se cadastrar para novas contas no Pocket ID.",
"user_signups_are_disabled": "As inscrições de usuários estão desativadas no momento.", "user_signups_are_disabled": "A inscrição de novos usuários está desativada no momento",
"create_signup_token": "Criar token de inscrição", "create_signup_token": "Criar token de inscrição",
"view_active_signup_tokens": "Ver tokens de inscrição ativos", "view_active_signup_tokens": "Ver tokens de inscrição ativos",
"manage_signup_tokens": "Gerenciar tokens de inscrição", "manage_signup_tokens": "Gerenciar tokens de inscrição",
@@ -423,27 +423,27 @@
"token": "Token", "token": "Token",
"loading": "Carregando", "loading": "Carregando",
"delete_signup_token": "Apagar token de inscrição", "delete_signup_token": "Apagar token de inscrição",
"are_you_sure_you_want_to_delete_this_signup_token": "Tem certeza que quer apagar esse token de inscrição? Não dá pra voltar atrás.", "are_you_sure_you_want_to_delete_this_signup_token": "Tem certeza que deseja deletar este token de inscrição? Não é possível desfazer esta ação.",
"signup_with_token": "Cadastre-se com token", "signup_with_token": "Cadastre-se com token",
"signup_with_token_description": "Os usuários só podem se cadastrar usando um token de cadastro válido criado por um administrador.", "signup_with_token_description": "Os usuários só podem se cadastrar usando um token de cadastro válido criado por um administrador.",
"signup_open": "Inscrição aberta", "signup_open": "Inscrição aberta",
"signup_open_description": "Qualquer pessoa pode criar uma conta nova sem restrições.", "signup_open_description": "Qualquer pessoa pode criar uma conta nova, sem restrições.",
"of": "de", "of": "de",
"skip_passkey_setup": "Pular configuração da chave de acesso", "skip_passkey_setup": "Pular configuração da chave de acesso",
"skip_passkey_setup_description": super recomendável criar uma senha de acesso, porque sem ela você vai ficar sem poder entrar na sua conta assim que a sessão acabar.", "skip_passkey_setup_description": altamente recomendado cadastrar uma chave de acesso, sem uma, não se possível entrar em sua conta novamente quando esta sessão acabar.",
"my_apps": "Meus aplicativos", "my_apps": "Meus aplicativos",
"no_apps_available": "Não tem aplicativos disponíveis", "no_apps_available": "Não aplicativos disponíveis",
"contact_your_administrator_for_app_access": "Fala com o seu administrador pra conseguir acesso aos aplicativos.", "contact_your_administrator_for_app_access": "Fale com o seu administrador para conseguir acesso aos aplicativos.",
"launch": "Lançamento", "launch": "Lançamento",
"client_launch_url": "URL de lançamento do cliente", "client_launch_url": "URL de lançamento do cliente",
"client_launch_url_description": "A URL que vai abrir quando alguém abrir o aplicativo na página Meus aplicativos.", "client_launch_url_description": "A URL carregada quando um usuário abrir o aplicativo na página Meus aplicativos.",
"client_name_description": "O nome do cliente que aparece na interface do Pocket ID.", "client_name_description": "O nome do cliente que aparece na interface do Pocket ID.",
"revoke_access": "Revogar acesso", "revoke_access": "Revogar acesso",
"revoke_access_description": "Revogar acesso a <b>{clientName}</b>. <b>{clientName}</b> não vai mais conseguir acessar as informações da sua conta.", "revoke_access_description": "Revogar acesso a <b>{clientName}</b>. <b>{clientName}</b> não conseguirá mais acessar as informações da sua conta.",
"revoke_access_successful": "O acesso a {clientName} foi revogado com sucesso.", "revoke_access_successful": "O acesso a {clientName} foi revogado com sucesso.",
"last_signed_in_ago": "Último login em {time} atrás", "last_signed_in_ago": "Último login à {time} atrás",
"invalid_client_id": "A ID do cliente só pode ter letras, números, sublinhados e hífens.", "invalid_client_id": "A ID do cliente só pode ter letras, números, sub traços e hifens",
"custom_client_id_description": "Defina um ID de cliente personalizado se for necessário para o seu aplicativo. Caso contrário, deixe em branco para gerar um aleatório.", "custom_client_id_description": "Se requerido pelo seu aplicativo, defina um ID de cliente personalizado. Caso contrário, deixe em branco para gerar um aleatório.",
"generated": "Gerado", "generated": "Gerado",
"administration": "Administração", "administration": "Administração",
"group_rdn_attribute_description": "O atributo usado no nome distinto (DN) dos grupos.", "group_rdn_attribute_description": "O atributo usado no nome distinto (DN) dos grupos.",
@@ -451,9 +451,16 @@
"display_name": "Nome de exibição", "display_name": "Nome de exibição",
"configure_application_images": "Configurar imagens de aplicativos", "configure_application_images": "Configurar imagens de aplicativos",
"ui_config_disabled_info_title": "Configuração da interface do usuário desativada", "ui_config_disabled_info_title": "Configuração da interface do usuário desativada",
"ui_config_disabled_info_description": "A configuração da interface do usuário está desativada porque as configurações do aplicativo são gerenciadas por meio de variáveis de ambiente. Algumas configurações podem não ser editáveis.", "ui_config_disabled_info_description": "A configuração da interface do usuário está desativada porque as configurações do aplicativo são gerenciadas através de variáveis de ambiente. Algumas configurações podem não ser editáveis.",
"logo_from_url_description": "Cole uma URL direta da imagem (svg, png, webp). Encontre ícones em <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> ou <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Cole uma URL direta da imagem (svg, png, webp). Encontre ícones em <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> ou <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "URL inválido", "invalid_url": "URL inválido",
"require_user_email": "É preciso um endereço de e-mail", "require_user_email": "É preciso um endereço de e-mail",
"require_user_email_description": "Pede que os usuários tenham um endereço de e-mail. Se desativado, os usuários sem endereço de e-mail não vão poder usar os recursos que precisam disso." "require_user_email_description": "Obriga que os usuários tenham um endereço de e-mail. Se desativado, os usuários sem endereço de e-mail não vão poder usar os recursos que precisam disso.",
"view": "Ver",
"toggle_columns": "Alternar colunas",
"locale": "Localização",
"ldap_id": "ID LDAP",
"reauthentication": "Re-autenticação",
"clear_filters": "Limpar filtros",
"default_profile_picture": "Foto de perfil padrão"
} }

View File

@@ -6,17 +6,17 @@
"docs": "Документация", "docs": "Документация",
"key": "Ключ", "key": "Ключ",
"value": "Значение", "value": "Значение",
"remove_custom_claim": "Удалить пользовательский claim", "remove_custom_claim": "Удалить пользовательское утверждение",
"add_custom_claim": "Добавить пользовательский claim", "add_custom_claim": "Добавить пользовательское утверждение",
"add_another": "Добавить ещё", "add_another": "Добавить ещё",
"select_a_date": "Выбрать дату", "select_a_date": "Выбрать дату",
"select_file": "Выбрать файл", "select_file": "Выбрать файл",
"profile_picture": "Изображение профиля", "profile_picture": "Изображение профиля",
"profile_picture_is_managed_by_ldap_server": "Изображение профиля управляется LDAP сервером и не может быть изменено здесь.", "profile_picture_is_managed_by_ldap_server": "Изображение профиля управляется сервером LDAP и не может быть изменено здесь.",
"click_profile_picture_to_upload_custom": "Нажмите на изображение профиля, чтобы загрузить его из ваших файлов.", "click_profile_picture_to_upload_custom": "Нажмите на изображение профиля, чтобы загрузить его из ваших файлов.",
"image_should_be_in_format": "Изображение должно быть в формате PNG или JPEG.", "image_should_be_in_format": "Изображение должно быть в формате PNG или JPEG.",
"items_per_page": "Элементов на странице", "items_per_page": "Элементов на странице",
"no_items_found": "Элементов не найдено", "no_items_found": "Элементы не найдены",
"select_items": "Выбрать элементы...", "select_items": "Выбрать элементы...",
"search": "Поиск...", "search": "Поиск...",
"expand_card": "Развернуть карточку", "expand_card": "Развернуть карточку",
@@ -41,21 +41,21 @@
"this_browser_does_not_support_passkeys": "Этот браузер не поддерживает пасскеи. Пожалуйста, воспользуйтесь альтернативным способом входа.", "this_browser_does_not_support_passkeys": "Этот браузер не поддерживает пасскеи. Пожалуйста, воспользуйтесь альтернативным способом входа.",
"an_unknown_error_occurred": "Произошла неизвестная ошибка", "an_unknown_error_occurred": "Произошла неизвестная ошибка",
"authentication_process_was_aborted": "Процесс аутентификации был прерван", "authentication_process_was_aborted": "Процесс аутентификации был прерван",
"error_occurred_with_authenticator": "С аутентификатором произошла ошибка", "error_occurred_with_authenticator": "Произошла ошибка аутентификатора",
"authenticator_does_not_support_discoverable_credentials": "Аутентификатор не поддерживает discoverable credentials", "authenticator_does_not_support_discoverable_credentials": "Аутентификатор не поддерживает обнаруживаемые учетные данные",
"authenticator_does_not_support_resident_keys": "Аутентификатор не поддерживает resident keys", "authenticator_does_not_support_resident_keys": "Аутентификатор не поддерживает резидентные ключи",
"passkey_was_previously_registered": "Этот пасскей был ранее зарегистрирован", "passkey_was_previously_registered": "Этот пасскей был ранее зарегистрирован",
"authenticator_does_not_support_any_of_the_requested_algorithms": "Аутентификатор не поддерживает ни один из запрошенных алгоритмов", "authenticator_does_not_support_any_of_the_requested_algorithms": "Аутентификатор не поддерживает ни один из запрошенных алгоритмов",
"authenticator_timed_out": "Время ожидания аутентификатора истекло", "authenticator_timed_out": "Время ожидания аутентификатора истекло",
"critical_error_occurred_contact_administrator": "Произошла критическая ошибка. Обратитесь к администратору.", "critical_error_occurred_contact_administrator": "Произошла критическая ошибка. Обратитесь к администратору.",
"sign_in_to": "Вход в {name}", "sign_in_to": "Войти в {name}",
"client_not_found": "Клиент не найден", "client_not_found": "Клиент не найден",
"client_wants_to_access_the_following_information": "<b>{client}</b> запрашивает доступ к следующей информации:", "client_wants_to_access_the_following_information": "<b>{client}</b> запрашивает доступ к следующей информации:",
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "Вы хотите войти в <b>{client}</b> с помощью вашей учетной записи {appName}?", "do_you_want_to_sign_in_to_client_with_your_app_name_account": "Вы хотите войти в <b>{client}</b> с помощью вашей учетной записи {appName}?",
"email": "Электронная почта", "email": "Электронная почта",
"view_your_email_address": "Просмотр адреса электронной почты", "view_your_email_address": "Просмотр адреса электронной почты",
"profile": "Профиль", "profile": "Профиль",
"view_your_profile_information": "Просмотр информации о вашем профиле", "view_your_profile_information": "Просмотр информации вашего профиля",
"groups": "Группы", "groups": "Группы",
"view_the_groups_you_are_a_member_of": "Просмотр групп, в которых вы состоите", "view_the_groups_you_are_a_member_of": "Просмотр групп, в которых вы состоите",
"cancel": "Отменить", "cancel": "Отменить",
@@ -64,7 +64,7 @@
"client_logo": "Логотип клиента", "client_logo": "Логотип клиента",
"sign_out": "Выйти", "sign_out": "Выйти",
"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_with_passkey_to_access_account": "Авторизуйтесь с использованием пасскея для доступа к вашей учетной записи.", "authenticate_with_passkey_to_access_account": "Авторизуйтесь с использованием пасскея для доступа к вашей учетной записи.",
"authenticate": "Авторизоваться", "authenticate": "Авторизоваться",
@@ -74,8 +74,8 @@
"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": "Если у вас нет доступа к вашему пасскею, вы можете войти одним из следующих способов.",
"use_your_passkey_instead": "Воспользоваться пасскеем вместо этого?", "use_your_passkey_instead": "Воспользоваться пасскеем вместо этого?",
"email_login": "Вход через электронную почту", "email_login": "Вход через электронную почту",
"enter_a_login_code_to_sign_in": "Введите предварительно созданный код входа.", "enter_a_login_code_to_sign_in": "Введите код входа, чтобы войти.",
"sign_in_with_login_code": "Войти с кодом для входа", "sign_in_with_login_code": "Войти с помощью кода входа",
"request_a_login_code_via_email": "Запросить код входа на электронную почту.", "request_a_login_code_via_email": "Запросить код входа на электронную почту.",
"go_back": "Назад", "go_back": "Назад",
"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": "Письмо было отправлено на указанный адрес электронной почты, если он существует в системе.",
@@ -83,23 +83,23 @@
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Введите ваш адрес электронной почты, чтобы получить письмо с кодом входа.", "enter_your_email_address_to_receive_an_email_with_a_login_code": "Введите ваш адрес электронной почты, чтобы получить письмо с кодом входа.",
"your_email": "Ваш адрес электронной почты", "your_email": "Ваш адрес электронной почты",
"submit": "Отправить", "submit": "Отправить",
"enter_the_code_you_received_to_sign_in": "Введите полученный код входа.", "enter_the_code_you_received_to_sign_in": "Введите полученный код, чтобы войти.",
"code": "Код", "code": "Код",
"invalid_redirect_url": "Неправильный URL-адрес перенаправления", "invalid_redirect_url": "Неправильный URL-адрес перенаправления",
"audit_log": "Журнал аудита", "audit_log": "Журнал аудита",
"users": "Пользователи", "users": "Пользователи",
"user_groups": "Группы пользователей", "user_groups": "Группы пользователей",
"oidc_clients": "Клиенты OIDC", "oidc_clients": "Клиенты OIDC",
"api_keys": "API ключи", "api_keys": "Ключи API",
"application_configuration": "Конфигурация приложения", "application_configuration": "Конфигурация приложения",
"settings": "Настройки", "settings": "Настройки",
"update_pocket_id": "Обновите Pocket ID", "update_pocket_id": "Обновите Pocket ID",
"powered_by": "Powered by", "powered_by": "Работает на",
"see_your_account_activities_from_the_last_3_months": "Смотрите активность вашей учетной записи за последние 3 месяца.", "see_your_account_activities_from_the_last_3_months": "Смотрите активность своей учетной записи за последние 3 месяца.",
"time": "Время", "time": "Время",
"event": "Событие", "event": "Событие",
"approximate_location": "Приблизительное местоположение", "approximate_location": "Примерное местоположение",
"ip_address": "IP адрес", "ip_address": "IP-адрес",
"device": "Устройство", "device": "Устройство",
"client": "Клиент", "client": "Клиент",
"unknown": "Неизвестно", "unknown": "Неизвестно",
@@ -120,7 +120,7 @@
"last_name": "Фамилия", "last_name": "Фамилия",
"username": "Имя пользователя", "username": "Имя пользователя",
"save": "Сохранить", "save": "Сохранить",
"username_can_only_contain": "Имя пользователя может содержать только строчные буквы, цифры, знак подчеркивания, точки, дефиса и символ '@'", "username_can_only_contain": "Имя пользователя может содержать только строчные буквы, цифры, подчеркивания, точки, дефисы и символ '@'",
"username_must_start_with": "Имя пользователя должно начинаться с буквы или цифры", "username_must_start_with": "Имя пользователя должно начинаться с буквы или цифры",
"username_must_end_with": "Имя пользователя должно заканчиваться буквой или цифрой", "username_must_end_with": "Имя пользователя должно заканчиваться буквой или цифрой",
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Войдите, используя следующий код. Код истечет через 15 минут.", "sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Войдите, используя следующий код. Код истечет через 15 минут.",
@@ -134,29 +134,29 @@
"passkey_name_updated_successfully": "Имя пасскея успешно обновлено", "passkey_name_updated_successfully": "Имя пасскея успешно обновлено",
"name_passkey": "Имя пасскея", "name_passkey": "Имя пасскея",
"name_your_passkey_to_easily_identify_it_later": "Назовите ваш пасскей, чтобы легко идентифицировать его позже.", "name_your_passkey_to_easily_identify_it_later": "Назовите ваш пасскей, чтобы легко идентифицировать его позже.",
"create_api_key": "Создать API ключ", "create_api_key": "Создать ключ API",
"add_a_new_api_key_for_programmatic_access": "Добавь новый ключ API для программного доступа к <link href='https://pocket-id.org/docs/api'>Pocket ID API</link>.", "add_a_new_api_key_for_programmatic_access": "Добавить новый ключ API для программного доступа к <link href='https://pocket-id.org/docs/api'>Pocket ID API</link>.",
"add_api_key": "Добавить API ключ", "add_api_key": "Добавить ключ API",
"manage_api_keys": "Управление API ключами", "manage_api_keys": "Управление ключами API",
"api_key_created": "API ключ создан", "api_key_created": "Ключ API создан",
"for_security_reasons_this_key_will_only_be_shown_once": "По соображениям безопасности, этот ключ будет показан только один раз. Пожалуйста, храните его в безопасном месте.", "for_security_reasons_this_key_will_only_be_shown_once": "По соображениям безопасности, этот ключ будет показан только один раз. Пожалуйста, храните его в безопасном месте.",
"description": "Описание", "description": "Описание",
"api_key": "API ключ", "api_key": "Ключ API",
"close": "Закрыть", "close": "Закрыть",
"name_to_identify_this_api_key": "Имя для идентификации API ключа.", "name_to_identify_this_api_key": "Имя для идентификации ключа API.",
"expires_at": "Действителен до", "expires_at": "Действителен до",
"when_this_api_key_will_expire": "Когда срок действия этого API ключа истечет.", "when_this_api_key_will_expire": "Когда истечет срок действия этого ключа API.",
"optional_description_to_help_identify_this_keys_purpose": "Опциональное описание, чтобы помочь определить цель этого ключа.", "optional_description_to_help_identify_this_keys_purpose": "Необязательное описание, которое поможет определить назначение этого ключа.",
"expiration_date_must_be_in_the_future": "Дата истечения должна быть определена в будущем", "expiration_date_must_be_in_the_future": "Дата истечения срока действия должна быть в будущем",
"revoke_api_key": "Отозвать API ключ", "revoke_api_key": "Отозвать ключ API",
"never": "Никогда", "never": "Никогда",
"revoke": "Отозвать", "revoke": "Отозвать",
"api_key_revoked_successfully": "API ключ успешно отозван", "api_key_revoked_successfully": "Ключ API успешно отозван",
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Вы уверены, что хотите отозвать API ключ \"{apiKeyName}\"? Это разрушит интеграцию, использующую этот ключ.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Вы уверены, что хотите отозвать ключ API \"{apiKeyName}\"? Любые интеграции, использующие этот ключ, перестанут работать.",
"last_used": "Последнее использование", "last_used": "Последнее использование",
"actions": "Действия", "actions": "Действия",
"images_updated_successfully": "Изображения успешно обновлены", "images_updated_successfully": "Изображения обновились, но может занять пару минут.",
"general": "Основное", "general": "Общее",
"configure_smtp_to_send_emails": "Включить уведомления пользователей по электронной почте при обнаружении логина с нового устройства или локации.", "configure_smtp_to_send_emails": "Включить уведомления пользователей по электронной почте при обнаружении логина с нового устройства или локации.",
"ldap": "LDAP", "ldap": "LDAP",
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Настроить конфигурацию LDAP для синхронизации пользователей и групп с сервером LDAP.", "configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "Настроить конфигурацию LDAP для синхронизации пользователей и групп с сервером LDAP.",
@@ -274,10 +274,10 @@
"one_time_link": "Одноразовая ссылка", "one_time_link": "Одноразовая ссылка",
"use_this_link_to_sign_in_once": "Используйте эту ссылку, чтобы войти единожды. Это необходимо для пользователей, которые ещё не добавили пасскей или потеряли его.", "use_this_link_to_sign_in_once": "Используйте эту ссылку, чтобы войти единожды. Это необходимо для пользователей, которые ещё не добавили пасскей или потеряли его.",
"add": "Добавить", "add": "Добавить",
"callback_urls": "Callback URLs", "callback_urls": "URL-адреса обратного вызова",
"logout_callback_urls": "Logout Callback URLs", "logout_callback_urls": "URL-адреса обратного вызова при выходе",
"public_client": "Публичный клиент", "public_client": "Публичный клиент",
"public_clients_description": "Публичные клиенты не имеют клиентского секрета. Они предназначены для мобильных, SPA и нативных приложений, где секретные данные нельзя надежно хранить.", "public_clients_description": "Публичные клиенты не имеют секрета клиента. Они предназначены для мобильных, SPA и нативных приложений, где секреты нельзя надежно хранить.",
"pkce": "PKCE", "pkce": "PKCE",
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Public Key Code Exchange — это функция безопасности для предотвращения атак CSRF и перехвата кода авторизации.", "public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Public Key Code Exchange — это функция безопасности для предотвращения атак CSRF и перехвата кода авторизации.",
"requires_reauthentication": "Требуется повторная аутентификация", "requires_reauthentication": "Требуется повторная аутентификация",
@@ -286,31 +286,31 @@
"change_logo": "Изменить логотип", "change_logo": "Изменить логотип",
"upload_logo": "Загрузить логотип", "upload_logo": "Загрузить логотип",
"remove_logo": "Удалить логотип", "remove_logo": "Удалить логотип",
"are_you_sure_you_want_to_delete_this_oidc_client": "Вы уверены, что хотите удалить этот OIDC клиент?", "are_you_sure_you_want_to_delete_this_oidc_client": "Вы уверены, что хотите удалить этого клиента OIDC?",
"oidc_client_deleted_successfully": "Клиент OIDC успешно удален", "oidc_client_deleted_successfully": "Клиент OIDC успешно удален",
"authorization_url": "Authorization URL", "authorization_url": "URL-адрес авторизации",
"oidc_discovery_url": "OIDC Discovery URL", "oidc_discovery_url": "URL-адрес обнаружения OIDC",
"token_url": "Token URL", "token_url": "URL-адрес токена",
"userinfo_url": "Userinfo URL", "userinfo_url": "URL-адрес информации о пользователе",
"logout_url": "Logout URL", "logout_url": "URL-адрес выхода",
"certificate_url": "Certificate URL", "certificate_url": "URL-адрес сертификата",
"enabled": "Включен", "enabled": "Включено",
"disabled": "Отключено", "disabled": "Отключено",
"oidc_client_updated_successfully": "OIDC клиент успешно обновлен", "oidc_client_updated_successfully": "Клиент OIDC успешно обновлен",
"create_new_client_secret": "Создать новый клиентский секрет", "create_new_client_secret": "Создать новый секрет клиента",
"are_you_sure_you_want_to_create_a_new_client_secret": "Вы уверены, что хотите создать новый клиентский секрет? Старый будет аннулирован.", "are_you_sure_you_want_to_create_a_new_client_secret": "Вы уверены, что хотите создать новый секрет клиента? Старый будет аннулирован.",
"generate": "Сгенерировать", "generate": "Сгенерировать",
"new_client_secret_created_successfully": "Новый клиентский секрет успешно сгенерирован", "new_client_secret_created_successfully": "Новый секрет клиента успешно сгенерирован",
"allowed_user_groups_updated_successfully": "Разрешенные группы пользователей успешно обновлены", "allowed_user_groups_updated_successfully": "Разрешенные группы пользователей успешно обновлены",
"oidc_client_name": "OIDC клиент {name}", "oidc_client_name": "Клиент OIDC {name}",
"client_id": "ID клиента", "client_id": "Идентификатор клиента",
"client_secret": "Клиентский секрет", "client_secret": "Секрет клиента",
"show_more_details": "Показать больше деталей", "show_more_details": "Показать больше деталей",
"allowed_user_groups": "Разрешенные группы пользователей", "allowed_user_groups": "Разрешенные группы пользователей",
"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": "Добавить группы пользователей к этому клиенту для ограничения доступа пользователей в этих группах. Если группы пользователей не выбраны, все пользователи будут иметь доступ к этому клиенту.",
"favicon": "Иконка", "favicon": "Значок",
"light_mode_logo": "Логотип для светлой темы", "light_mode_logo": "Логотип светлого режима",
"dark_mode_logo": "Логотип для темной темы", "dark_mode_logo": "Логотип темного режима",
"background_image": "Фоновое изображение", "background_image": "Фоновое изображение",
"language": "Язык", "language": "Язык",
"reset_profile_picture_question": "Сбросить изображение профиля?", "reset_profile_picture_question": "Сбросить изображение профиля?",
@@ -318,63 +318,63 @@
"reset": "Сбросить", "reset": "Сбросить",
"reset_to_default": "Сбросить по умолчанию", "reset_to_default": "Сбросить по умолчанию",
"profile_picture_has_been_reset": "Изображение профиля было сброшено. Обновление может занять несколько минут.", "profile_picture_has_been_reset": "Изображение профиля было сброшено. Обновление может занять несколько минут.",
"select_the_language_you_want_to_use": "Выберите язык, который вы хотите использовать. Обратите внимание на то, что часть текста может быть переведена автоматически и содержать неточности.", "select_the_language_you_want_to_use": "Выберите язык, который вы хотите использовать. Обратите внимание, что часть текста может быть переведена автоматически и содержать неточности.",
"contribute_to_translation": "Если вы нашли ошибку, приглашаем вас помочь с переводом на <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.", "contribute_to_translation": "Если вы нашли ошибку, приглашаем вас помочь с переводом на <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.",
"personal": "Персональный", "personal": "Личный",
"global": "Глобальный", "global": "Глобальный",
"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_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": "Пользователь успешно отключен.",
"user_enabled_successfully": "Пользователь успешно включен.", "user_enabled_successfully": "Пользователь успешно включен.",
"status": "Статус", "status": "Статус",
"disable_firstname_lastname": "Отключить {firstName} {lastName}", "disable_firstname_lastname": "Отключить {firstName} {lastName}",
"are_you_sure_you_want_to_disable_this_user": "Вы уверены, что хотите отключить этого пользователя? Они не смогут войти в систему или получить доступ к любым сервисам.", "are_you_sure_you_want_to_disable_this_user": "Вы уверены, что хотите отключить этого пользователя? Он не сможет войти или получить доступ к каким-либо сервисам.",
"ldap_soft_delete_users": "Оставить отключенных пользователей от LDAP.", "ldap_soft_delete_users": "Сохранять отключенных пользователей из LDAP.",
"ldap_soft_delete_users_description": "Когда включено, пользователи удалённые из LDAP будут отключены вместо удаления из системы.", "ldap_soft_delete_users_description": "Если включено, пользователи, удаленные из LDAP, будут отключены, а не удалены из системы.",
"login_code_email_success": "Код входа был отправлен пользователю.", "login_code_email_success": "Код входа был отправлен пользователю.",
"send_email": "Отправить письмо", "send_email": "Отправить письмо",
"show_code": "Показать код", "show_code": "Показать код",
"callback_url_description": "URL-адрес(а) предоставленные вашим клиентом. Будет автоматически добавлен если оставить пустым. Маски (*) поддерживаются, но лучше избегайте их для повышения безопасности.", "callback_url_description": "URL-адреса, предоставленные клиентом. Будет автоматически добавлен, если оставить пустым. Подстановочные знаки (*) поддерживаются, но их лучше избегать для большей безопасности.",
"logout_callback_url_description": "URL-адрес(а), предоставленный вашим клиентом для выхода. Маски (*) поддерживаются, но лучше избегайте их для повышения безопасности.", "logout_callback_url_description": "URL-адреса, предоставленные клиентом. Подстановочные знаки (*) поддерживаются, но их лучше избегать для большей безопасности.",
"api_key_expiration": "Истечение срока действия API ключа", "api_key_expiration": "Истечение срока действия ключа API",
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Отправлять пользователю письмо, когда истечет срок действия API ключа.", "send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Отправлять пользователю письмо, когда срок действия его ключа API истекает.",
"authorize_device": "Авторизовать устройство", "authorize_device": "Авторизовать устройство",
"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_description": "Используя федеративные учетные данные клиента, вы можете авторизовывать OIDC клиентов, используя JWT токены, выпущенные третьими сторонами.", "federated_client_credentials_description": "Используя федеративные учетные данные клиента, вы можете аутентифицировать клиентов OIDC с помощью токенов JWT, выпущенных сторонними поставщиками удостоверений.",
"add_federated_client_credential": "Добавить федеративные учетные данные клиента", "add_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": "Показать дополнительные параметры",
"hide_advanced_options": "Скрыть дополнительные опции", "hide_advanced_options": "Скрыть дополнительные параметры",
"oidc_data_preview": "Предпросмотр данных OIDC", "oidc_data_preview": "Предварительный просмотр данных OIDC",
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Предпросмотр данных OIDC, которые будут отправлены разным пользователям", "preview_the_oidc_data_that_would_be_sent_for_different_users": "Предварительный просмотр данных OIDC, которые будут отправлены для различных пользователей",
"id_token": "ID Token", "id_token": "Токен идентификации",
"access_token": "Access Token", "access_token": "Токен доступа",
"userinfo": "Userinfo", "userinfo": "Информация о пользователе",
"id_token_payload": "Содержимое ID Token", "id_token_payload": "Полезная нагрузка токена идентификации",
"access_token_payload": "Содержимое Access Token", "access_token_payload": "Полезная нагрузка токена доступа",
"userinfo_endpoint_response": "Ответ Userinfo эндпоинта", "userinfo_endpoint_response": "Ответ конечной точки информации о пользователе",
"copy": "Копировать", "copy": "Копировать",
"no_preview_data_available": "Предварительный просмотр данных не доступен", "no_preview_data_available": "Нет данных предварительного просмотра",
"copy_all": "Копировать все", "copy_all": "Копировать все",
"preview": "Предпросмотр", "preview": "Предварительный просмотр",
"preview_for_user": "Предпросмотр для {name}", "preview_for_user": "Предпросмотр для {name}",
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Предпросмотр данных OIDC, которые будут отправлены для этого пользователя", "preview_the_oidc_data_that_would_be_sent_for_this_user": "Предварительный просмотр данных OIDC, которые будут отправлены для этого пользователя",
"show": "Показать", "show": "Показать",
"select_an_option": "Выберите опцию", "select_an_option": "Выберите опцию",
"select_user": "Выбрать пользователя", "select_user": "Выбрать пользователя",
@@ -442,18 +442,25 @@
"revoke_access_description": "Отозвать доступ к <b>{clientName}</b>. <b>{clientName}</b> больше не сможет получить доступ к информации вашей учетной записи.", "revoke_access_description": "Отозвать доступ к <b>{clientName}</b>. <b>{clientName}</b> больше не сможет получить доступ к информации вашей учетной записи.",
"revoke_access_successful": "Доступ к {clientName} успешно отозван.", "revoke_access_successful": "Доступ к {clientName} успешно отозван.",
"last_signed_in_ago": "Последний вход {time} назад", "last_signed_in_ago": "Последний вход {time} назад",
"invalid_client_id": "ID клиента может содержать только буквы, цифры, подчеркивания и дефисы", "invalid_client_id": "Идентификатор клиента может содержать только буквы, цифры, подчеркивания и дефисы",
"custom_client_id_description": "Установите пользовательский ID клиента, если это нужно для вашего приложения. Если нет, оставьте поле пустым, чтобы он был сгенерирован случайным образом.", "custom_client_id_description": "Укажите собственный идентификатор клиента, если это требуется для вашего приложения. В противном случае оставьте поле пустым, чтобы сгенерировать случайный идентификатор.",
"generated": "Сгенерированный", "generated": "Сгенерированный",
"administration": "Администрирование", "administration": "Администрирование",
"group_rdn_attribute_description": "Атрибут, который используется в различающемся имени группы (DN).", "group_rdn_attribute_description": "Атрибут, который используется в отличительном имени (DN) группы.",
"display_name_attribute": "Атрибут отображаемого имени", "display_name_attribute": "Атрибут отображаемого имени",
"display_name": "Отображаемое имя", "display_name": "Отображаемое имя",
"configure_application_images": "Настройка изображений приложения", "configure_application_images": "Настройка изображений приложения",
"ui_config_disabled_info_title": "Конфигурация пользовательского интерфейса отключена", "ui_config_disabled_info_title": "Конфигурация пользовательского интерфейса отключена",
"ui_config_disabled_info_description": "Конфигурация пользовательского интерфейса отключена, потому что настройки приложения управляются через переменные среды. Некоторые настройки могут быть недоступны для редактирования.", "ui_config_disabled_info_description": "Конфигурация пользовательского интерфейса отключена, потому что настройки приложения управляются через переменные среды. Некоторые настройки могут быть недоступны для редактирования.",
"logo_from_url_description": "Вставьте прямой URL-адрес изображения (svg, png, webp). Ищите иконки на <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> или <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Укажите прямой URL-адрес изображения (svg, png, webp). Значки можно найти на <link href=\\\"https://selfh.st/icons\\\">Selfh.st Icons</link> или <link href=\\\"https://dashboardicons.com\\\">Dashboard Icons</link>.",
"invalid_url": "Недопустимый URL", "invalid_url": "Неверный URL-адрес",
"require_user_email": "Требовать адрес электронной почты", "require_user_email": "Обязательный адрес электронной почты",
"require_user_email_description": "Требует, чтобы у пользователей был адрес электронной почты. Если эта функция отключена, пользователи без адреса электронной почты не смогут пользоваться функциями, для которых нужен адрес электронной почты." "require_user_email_description": "Требует наличия адреса электронной почты у пользователей. Если отключено, пользователи без адреса электронной почты не смогут использовать функции, требующие адреса электронной почты.",
"view": "Вид",
"toggle_columns": "Показывать столбцы",
"locale": "Язык",
"ldap_id": "LDAP ID",
"reauthentication": "Повторная аутентификация",
"clear_filters": "Сбросить фильтры",
"default_profile_picture": "Изображение профиля по умолчанию"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Är du säker på att du vill återkalla API-nyckeln ”{apiKeyName}”? Alla integrationer som använder nyckeln slutar att fungera.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Är du säker på att du vill återkalla API-nyckeln ”{apiKeyName}”? Alla integrationer som använder nyckeln slutar att fungera.",
"last_used": "Senast använd", "last_used": "Senast använd",
"actions": "Åtgärder", "actions": "Åtgärder",
"images_updated_successfully": "Bilderna har uppdaterats", "images_updated_successfully": "Bilderna har uppdaterats. Det kan ta några minuter att uppdatera.",
"general": "Allmänt", "general": "Allmänt",
"configure_smtp_to_send_emails": "Aktivera e-postaviseringar för att varna användare när en inloggning upptäcks från en ny enhet eller plats.", "configure_smtp_to_send_emails": "Aktivera e-postaviseringar för att varna användare när en inloggning upptäcks från en ny enhet eller plats.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Klistra in en direkt bild-URL (svg, png, webp). Hitta ikoner på <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> eller <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Klistra in en direkt bild-URL (svg, png, webp). Hitta ikoner på <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> eller <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Ogiltig URL", "invalid_url": "Ogiltig URL",
"require_user_email": "Kräver e-postadress", "require_user_email": "Kräver e-postadress",
"require_user_email_description": "Kräver att användarna har en e-postadress. Om funktionen är inaktiverad kan användare utan e-postadress inte använda funktioner som kräver en e-postadress." "require_user_email_description": "Kräver att användarna har en e-postadress. Om funktionen är inaktiverad kan användare utan e-postadress inte använda funktioner som kräver en e-postadress.",
"view": "Visa",
"toggle_columns": "Växla kolumner",
"locale": "Lokalisering",
"ldap_id": "LDAP-ID",
"reauthentication": "Omverifiering",
"clear_filters": "Rensa filter",
"default_profile_picture": "Standardprofilbild"
} }

466
frontend/messages/tr.json Normal file
View File

@@ -0,0 +1,466 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"my_account": "Hesabım",
"logout": ıkış Yap",
"confirm": "Onayla",
"docs": "Belgeler",
"key": "Anahtar",
"value": "Değer",
"remove_custom_claim": "Özel yetkiyi kaldır",
"add_custom_claim": "Özel yetki ekle",
"add_another": "Başka bir tane ekle",
"select_a_date": "Bir tarih seçin",
"select_file": "Dosya Seç",
"profile_picture": "Profil resmi",
"profile_picture_is_managed_by_ldap_server": "Profil resmi LDAP sunucusu tarafından yönetilmektedir ve burada değiştirilemez.",
"click_profile_picture_to_upload_custom": "Özel bir resim yüklemek için profil resmine tıklayın.",
"image_should_be_in_format": "Resim PNG veya JPEG formatında olmalıdır.",
"items_per_page": "Sayfa başına öğe sayısı",
"no_items_found": "Hiçbir öğe bulunamadı",
"select_items": "Öğeleri seçin...",
"search": "Ara...",
"expand_card": "Kartı genişlet",
"copied": "Kopyalandı",
"click_to_copy": "Kopyalamak için tıklayın",
"something_went_wrong": "Bir şeyler ters gitti",
"go_back_to_home": "Ana sayfaya geri dön",
"alternative_sign_in_methods": "Alternatif Giriş Yöntemleri",
"login_background": "Giriş arka planı",
"logo": "Logo",
"login_code": "Giriş Kodu",
"create_a_login_code_to_sign_in_without_a_passkey_once": "Kullanıcının bir kez parolasız oturum açmak için kullanabileceği bir giriş kodu oluşturun.",
"one_hour": "1 saat",
"twelve_hours": "12 saat",
"one_day": "1 gün",
"one_week": "1 hafta",
"one_month": "1 ay",
"expiration": "Son Geçerlilik Tarihi",
"generate_code": "Kod Oluştur",
"name": "İsim",
"browser_unsupported": "Desteklenmeyen tarayıcı",
"this_browser_does_not_support_passkeys": "Bu tarayıcı geçiş anahtarlarını desteklemiyor. Lütfen alternatif bir oturum açma yöntemi kullanın.",
"an_unknown_error_occurred": "Bilinmeyen bir hata oluştu",
"authentication_process_was_aborted": "Kimlik doğrulama süreci iptal edildi",
"error_occurred_with_authenticator": "Kimlik doğrulayıcıda bir hata oluştu",
"authenticator_does_not_support_discoverable_credentials": "Kimlik doğrulayıcı, keşfedilebilir kimlik bilgilerini desteklemiyor",
"authenticator_does_not_support_resident_keys": "Kimlik doğrulayıcı yerleşik anahtarları desteklemiyor",
"passkey_was_previously_registered": "Bu geçiş anahtarı daha önce kaydedilmiştir",
"authenticator_does_not_support_any_of_the_requested_algorithms": "Kimlik doğrulayıcı, talep edilen algoritmalardan hiçbirini desteklemiyor",
"authenticator_timed_out": "Kimlik doğrulayıcı zaman aşımına uğradı",
"critical_error_occurred_contact_administrator": "Kritik bir hata oluştu. Lütfen sistem yöneticinizle iletişime geçin.",
"sign_in_to": "{name} hesabına giriş yap",
"client_not_found": "İstemci bulunamadı",
"client_wants_to_access_the_following_information": "<b>{client}</b> aşağıdaki bilgilere erişmek istiyor:",
"do_you_want_to_sign_in_to_client_with_your_app_name_account": "{appName} hesabınla <b>{client}</b> uygulamasına giriş yapmak istiyor musun?",
"email": "E-posta",
"view_your_email_address": "E-posta adresinizi görüntüleyin",
"profile": "Profil",
"view_your_profile_information": "Profil bilgilerinizi görüntüleyin",
"groups": "Gruplar",
"view_the_groups_you_are_a_member_of": "Üyesi olduğunuz grupları görüntüleyin",
"cancel": "İptal",
"sign_in": "Giriş yap",
"try_again": "Tekrar deneyin",
"client_logo": "İstemci Logosu",
"sign_out": ıkış yap",
"do_you_want_to_sign_out_of_pocketid_with_the_account": "<b>{username}</b> adlı hesapla {appName} uygulamasından çıkış yapmak istiyor musun?",
"sign_in_to_appname": "{appName} hesabına giriş yap",
"please_try_to_sign_in_again": "Lütfen tekrar giriş yapmayı deneyin.",
"authenticate_with_passkey_to_access_account": "Hesabınıza erişmek için geçiş anahtarınızla kimlik doğrulaması yapın.",
"authenticate": "Kimliğini Doğrula",
"please_try_again": "Lütfen tekrar deneyin.",
"continue": "Devam et",
"alternative_sign_in": "Alternatif Giriş",
"if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods": "Geçiş anahtarınıza erişiminiz yoksa, aşağıdaki yöntemlerden biriyle giriş yapabilirsiniz.",
"use_your_passkey_instead": "Geçiş anahtarınızı kullanmak ister misiniz?",
"email_login": "E-posta ile Giriş",
"enter_a_login_code_to_sign_in": "Giriş yapmak için bir kod girin.",
"sign_in_with_login_code": "Giriş kodu ile giriş yap",
"request_a_login_code_via_email": "E-posta ile giriş kodu isteyin.",
"go_back": "Geri dön",
"an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system": "Verilen e-posta adresine, sistemde mevcutsa bir e-posta gönderilmiştir.",
"enter_code": "Kodu girin",
"enter_your_email_address_to_receive_an_email_with_a_login_code": "Giriş kodunu içeren bir e-posta almak için e-posta adresinizi girin.",
"your_email": "E-posta adresiniz",
"submit": "Gönder",
"enter_the_code_you_received_to_sign_in": "Aldığınız kodu girin ve giriş yapın.",
"code": "Kod",
"invalid_redirect_url": "Geçersiz yönlendirme URL'si",
"audit_log": "Denetim Kaydı",
"users": "Kullanıcılar",
"user_groups": "Kullanıcı Grupları",
"oidc_clients": "OIDC İstemcileri",
"api_keys": "API Anahtarları",
"application_configuration": "Uygulama Yapılandırması",
"settings": "Ayarlar",
"update_pocket_id": "Pocket ID'yi Güncelle",
"powered_by": "Destekleyen",
"see_your_account_activities_from_the_last_3_months": "Son 3 aydaki hesap aktivitelerinizi görüntüleyin.",
"time": "Zaman",
"event": "Olay",
"approximate_location": "Yaklaşık Konum",
"ip_address": "IP Adresi",
"device": "Cihaz",
"client": "İstemci",
"unknown": "Bilinmeyen",
"account_details_updated_successfully": "Hesap bilgileri başarıyla güncellendi",
"profile_picture_updated_successfully": "Profil resmi başarıyla güncellendi. Güncellenmesi birkaç dakika sürebilir.",
"account_settings": "Hesap Ayarları",
"passkey_missing": "Geçiş anahtarı eksik",
"please_provide_a_passkey_to_prevent_losing_access_to_your_account": "Hesabınıza erişimi kaybetmemek için lütfen bir geçiş anahtarı ekleyin.",
"single_passkey_configured": "Tek geçiş anahtarı yapılandırıldı",
"it_is_recommended_to_add_more_than_one_passkey": "Birden fazla geçiş anahtarı eklemeniz önerilir, böylece hesabınıza erişimi kaybetmezsiniz.",
"account_details": "Hesap Bilgileri",
"passkeys": "Geçiş Anahtarları",
"manage_your_passkeys_that_you_can_use_to_authenticate_yourself": "Kendinizi doğrulamak için kullanabileceğiniz geçiş anahtarlarınızı yönetin.",
"add_passkey": "Geçiş Anahtarı Ekle",
"create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey": "Geçiş anahtarı olmadan farklı bir cihazdan giriş yapmak için tek kullanımlık bir giriş kodu oluşturun.",
"create": "Oluştur",
"first_name": "İsim",
"last_name": "Soyadı",
"username": "Kullanıcı Adı",
"save": "Kaydet",
"username_can_only_contain": "Kullanıcı adı yalnızca küçük harfler, sayılar, alt çizgiler, noktalar, tireler ve '@' sembolleri içerebilir",
"username_must_start_with": "Kullanıcı adı bir harf veya sayısal karakterle başlamalıdır",
"username_must_end_with": "Kullanıcı adı bir harf veya sayısal karakterle bitmelidir",
"sign_in_using_the_following_code_the_code_will_expire_in_minutes": "Aşağıdaki kodu kullanarak giriş yapın. Kod 15 dakika içinde süresi dolacaktır.",
"or_visit": "veya ziyaret edin",
"added_on": "Ekleme tarihi",
"rename": "Yeniden Adlandır",
"delete": "Sil",
"are_you_sure_you_want_to_delete_this_passkey": "Bu geçiş anahtarını silmek istediğinizden emin misiniz?",
"passkey_deleted_successfully": "Geçiş anahtarı başarıyla silindi",
"delete_passkey_name": "Sil {passkeyName}",
"passkey_name_updated_successfully": "Geçiş anahtarı adı başarıyla güncellendi",
"name_passkey": "Geçiş Anahtarı Adı",
"name_your_passkey_to_easily_identify_it_later": "Geçiş anahtarınızı daha sonra kolayca tanımlayabilmek için adlandırın.",
"create_api_key": "API Anahtarı Oluştur",
"add_a_new_api_key_for_programmatic_access": "Programatik erişim için <link href='https://pocket-id.org/docs/api'>Pocket ID API</link>ye yeni bir API anahtarı ekle.",
"add_api_key": "API Anahtarı Ekle",
"manage_api_keys": "API Anahtarlarını Yönet",
"api_key_created": "API Anahtarı Oluşturuldu",
"for_security_reasons_this_key_will_only_be_shown_once": "Güvenlik nedenleriyle, bu anahtar yalnızca bir kez gösterilecektir. Lütfen güvenli bir şekilde saklayın.",
"description": "Açıklama",
"api_key": "API Anahtarı",
"close": "Kapat",
"name_to_identify_this_api_key": "Bu API anahtarını tanımlamak için bir isim girin.",
"expires_at": "Son Geçerlilik Tarihi",
"when_this_api_key_will_expire": "Bu API anahtarının süresinin ne zaman dolacağını belirtin.",
"optional_description_to_help_identify_this_keys_purpose": "Bu anahtarın amacını tanımlamaya yardımcı olacak isteğe bağlııklama.",
"expiration_date_must_be_in_the_future": "Son geçerlilik tarihi gelecekte olmalıdır",
"revoke_api_key": "API Anahtarını İptal Et",
"never": "Asla",
"revoke": "İptal Et",
"api_key_revoked_successfully": "API anahtarı başarıyla iptal edildi",
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "API anahtarı \"{apiKeyName}\" iptal etmek istediğinizden emin misiniz? Bu, bu anahtarı kullanan tüm entegrasyonları bozacaktır.",
"last_used": "Son Kullanım",
"actions": "Eylemler",
"images_updated_successfully": "Görüntüler başarıyla güncellendi. Güncelleme işlemi birkaç dakika sürebilir.",
"general": "Genel",
"configure_smtp_to_send_emails": "Yeni bir cihaz veya konumdan giriş tespit edildiğinde kullanıcıları bilgilendirmek için e-posta bildirimlerini etkinleştirin.",
"ldap": "LDAP",
"configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server": "LDAP ayarlarını yapılandırarak kullanıcıları ve grupları bir LDAP sunucusundan senkronize edin.",
"images": "Görseller",
"update": "Güncelle",
"email_configuration_updated_successfully": "E-posta yapılandırması başarıyla güncellendi",
"save_changes_question": "Değişiklikleri kaydetmek istiyor musunuz?",
"you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now": "Test e-postası göndermeden önce değişiklikleri kaydetmelisiniz. Şimdi kaydetmek istiyor musunuz?",
"save_and_send": "Kaydet ve Gönder",
"test_email_sent_successfully": "Test e-postası, e-posta adresine başarıyla gönderildi.",
"failed_to_send_test_email": "Test e-postası gönderilemedi. Daha fazla bilgi için sunucu günlüklerini kontrol edin.",
"smtp_configuration": "SMTP Yapılandırması",
"smtp_host": "SMTP Sunucusu",
"smtp_port": "SMTP Portu",
"smtp_user": "SMTP kullanıcı adı",
"smtp_password": "SMTP Parolası",
"smtp_from": "SMTP Gönderen",
"smtp_tls_option": "SMTP TLS Seçeneği",
"email_tls_option": "E-posta TLS Seçeneği",
"skip_certificate_verification": "Sertifika Doğrulamasını Atla",
"this_can_be_useful_for_selfsigned_certificates": "Bu, kendi imzaladığınız sertifikalar için yararlı olabilir.",
"enabled_emails": "Etkin E-postalar",
"email_login_notification": "E-posta Giriş Bildirimi",
"send_an_email_to_the_user_when_they_log_in_from_a_new_device": "Kullanıcı yeni bir cihazdan oturum açtığında e-posta gönder.",
"emai_login_code_requested_by_user": "Kullanıcı tarafından talep edilen e-posta giriş kodu",
"allow_users_to_sign_in_with_a_login_code_sent_to_their_email": "Kullanıcıların e-posta ile gönderilen bir giriş kodu ile oturum açmalarına izin verin. Bu, güvenliği önemli ölçüde azaltır çünkü kullanıcının e-postasına erişimi olan herkes giriş yapabilir.",
"email_login_code_from_admin": "Yönetici tarafından gönderilen e-posta giriş kodu",
"allows_an_admin_to_send_a_login_code_to_the_user": "Bir yöneticinin kullanıcıya e-posta ile giriş kodu göndermesine izin verir.",
"send_test_email": "Test e-postası gönder",
"application_configuration_updated_successfully": "Uygulama yapılandırması başarıyla güncellendi",
"application_name": "Uygulama Adı",
"session_duration": "Oturum Süresi",
"the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again": "Kullanıcının tekrar oturum açması gereken süre, dakika cinsinden.",
"enable_self_account_editing": "Kullanıcının kendi hesabını düzenlemesini etkinleştir",
"whether_the_users_should_be_able_to_edit_their_own_account_details": "Kullanıcıların kendi hesap bilgilerini düzenlemesine izin verilsin mi.",
"emails_verified": "E-postalar doğrulandı",
"whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients": "Kullanıcının e-postasının OIDC istemcileri için doğrulanmış olarak işaretlenip işaretlenmeyeceği.",
"ldap_configuration_updated_successfully": "LDAP yapılandırması başarıyla güncellendi",
"ldap_disabled_successfully": "LDAP başarıyla devre dışı bırakıldı",
"ldap_sync_finished": "LDAP senkronizasyonu tamamlandı",
"client_configuration": "İstemci Yapılandırması",
"ldap_url": "LDAP URL",
"ldap_bind_dn": "LDAP Bind DN",
"ldap_bind_password": "LDAP Bind Parolası",
"ldap_base_dn": "LDAP Base DN",
"user_search_filter": "Kullanıcı Arama Filtresi",
"the_search_filter_to_use_to_search_or_sync_users": "Kullanıcıları aramak veya senkronize etmek için kullanılacak arama filtresi.",
"groups_search_filter": "Grupları Arama Filtresi",
"the_search_filter_to_use_to_search_or_sync_groups": "Grupları aramak veya senkronize etmek için kullanılacak arama filtresi.",
"attribute_mapping": "Öznitelik Haritalama",
"user_unique_identifier_attribute": "Kullanıcı Benzersiz Tanımlayıcı Özniteliği",
"the_value_of_this_attribute_should_never_change": "Bu özniteliğin değeri asla değişmemelidir.",
"username_attribute": "Kullanıcı Adı Özniteliği",
"user_mail_attribute": "Kullanıcı E-posta Özniteliği",
"user_first_name_attribute": "Kullanıcı Adı Özniteliği",
"user_last_name_attribute": "Kullanıcı Soyadı Özniteliği",
"user_profile_picture_attribute": "Kullanıcı Profil Resmi Özniteliği",
"the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image": "Bu özniteliğin değeri ya bir URL, binary ya da base64 kodlu bir resim olabilir.",
"group_members_attribute": "Grup Üyeleri Özniteliği",
"the_attribute_to_use_for_querying_members_of_a_group": "Bir grubun üyelerini sorgulamak için kullanılacak öznitelik.",
"group_unique_identifier_attribute": "Grup Benzersiz Tanımlayıcı Özniteliği",
"group_rdn_attribute": "Grup RDN Özniteliği (DN içinde)",
"admin_group_name": "Yönetici Grup Adı",
"members_of_this_group_will_have_admin_privileges_in_pocketid": "Bu grubun üyeleri Pocket ID'de yönetici ayrıcalıklarına sahip olacaktır.",
"disable": "Devre Dışı Bırak",
"sync_now": "Şimdi Senkronize et",
"enable": "Etkinleştir",
"user_created_successfully": "Kullanıcı başarıyla oluşturuldu",
"create_user": "Kullanıcı Oluştur",
"add_a_new_user_to_appname": "{appName} uygulamasına yeni bir kullanıcı ekleyin",
"add_user": "Kullanıcı Ekle",
"manage_users": "Kullanıcıları Yönet",
"admin_privileges": "Yönetici Ayrıcalıkları",
"admins_have_full_access_to_the_admin_panel": "Yöneticilerin yönetici paneline tam erişimi vardır.",
"delete_firstname_lastname": "Sil {firstName} {lastName}",
"are_you_sure_you_want_to_delete_this_user": "Bu kullanıcıyı silmek istediğinizden emin misiniz?",
"user_deleted_successfully": "Kullanıcı başarıyla silindi,",
"role": "Rol",
"source": "Kaynak",
"admin": "Yönetici",
"user": "Kullanıcı",
"local": "Yerel",
"toggle_menu": "Menüyü aç/kapat",
"edit": "Düzenle",
"user_groups_updated_successfully": "Kullanıcı grupları başarıyla güncellendi",
"user_updated_successfully": "Kullanıcı başarıyla güncellendi",
"custom_claims_updated_successfully": "Özel alanlar başarıyla güncellendi",
"back": "Geri",
"user_details_firstname_lastname": "Kullanıcı Detayları {firstName} {lastName}",
"manage_which_groups_this_user_belongs_to": "Bu kullanıcının ait olduğu grupları yönetin.",
"custom_claims": "Özel Alan",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user": "Özel alan, bir kullanıcı hakkında ek bilgi depolamak için kullanılabilecek anahtar-değer çiftleridir. Bu alanlar, 'profil' kapsamı istendiğinde ID belirtecine dahil edilecektir.",
"user_group_created_successfully": "Kullanıcı grubu başarıyla oluşturuldu",
"create_user_group": "Kullanıcı Grubu Oluştur",
"create_a_new_group_that_can_be_assigned_to_users": "Kullanıcılara atanabilecek yeni bir grup oluşturun.",
"add_group": "Grup Ekle",
"manage_user_groups": "Kullanıcı Gruplarını Yönet",
"friendly_name": "Kullanıcı Dostu İsim",
"name_that_will_be_displayed_in_the_ui": "Kullanıcı arayüzünde görüntülenecek isim",
"name_that_will_be_in_the_groups_claim": "“Gruplar” alanında bulunacak ad",
"delete_name": "Sil {name}",
"are_you_sure_you_want_to_delete_this_user_group": "Bu kullanıcı grubunu silmek istediğinizden emin misiniz?",
"user_group_deleted_successfully": "Kullanıcı grubu başarıyla silindi",
"user_count": "Kullanıcı Sayısı",
"user_group_updated_successfully": "Kullanıcı grubu başarıyla güncellendi",
"users_updated_successfully": "Kullanıcılar başarıyla güncellendi",
"user_group_details_name": "Kullanıcı Grubu Detayları {name}",
"assign_users_to_this_group": "Bu gruba kullanıcı atayın.",
"custom_claims_are_key_value_pairs_that_can_be_used_to_store_additional_information_about_a_user_prioritized": "Özel alanlar, bir kullanıcı hakkında ek bilgileri depolamak için kullanılabilen anahtar-değer çiftleridir. Bu alanlar, 'profil' kapsamı talep edildiğinde kimlik belirtecine dahil edilecektir. Çakışma olması durumunda, kullanıcı üzerinde tanımlanan özel alanlar öncelikli olacaktır.",
"oidc_client_created_successfully": "OIDC istemcisi başarıyla oluşturuldu",
"create_oidc_client": "OIDC İstemcisi Oluştur",
"add_a_new_oidc_client_to_appname": "{appName} uygulamasına yeni bir oidc istemcisi ekleyin.",
"add_oidc_client": "OIDC İstemcisi Ekle",
"manage_oidc_clients": "OIDC İstemcilerini Yönet",
"one_time_link": "Tek Seferlik Bağlantı",
"use_this_link_to_sign_in_once": "Bu bağlantıyı bir kez oturum açmak için kullanın. Bu, henüz bir geçiş anahtarı eklememiş veya kaybetmiş kullanıcılar için gereklidir.",
"add": "Ekle",
"callback_urls": "Callback URL'leri",
"logout_callback_urls": ıkış Callback URL'leri",
"public_client": "Genel İstemci",
"public_clients_description": "Genel istemciler bir istemci sırrına sahip değildir. Güvenlik anahtarlarının güvenli bir şekilde saklanamayacağı mobil, web ve yerel uygulamalar için tasarlanmıştır.",
"pkce": "PKCE",
"public_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks": "Genel Anahtar Kod Değişimi, CSRF ve yetkilendirme kodu ele geçirme saldırılarını önlemek için bir güvenlik özelliğidir.",
"requires_reauthentication": "Yeniden Kimlik Doğrulama Gerekir",
"requires_users_to_authenticate_again_on_each_authorization": "Kullanıcıların her yetkilendirmede tekrar kimlik doğrulaması yapmasını gerektirir, oturum açmış olsalar bile",
"name_logo": "{name} logosu",
"change_logo": "Logoyu Değiştir",
"upload_logo": "Logo Yükle",
"remove_logo": "Logoyu Kaldır",
"are_you_sure_you_want_to_delete_this_oidc_client": "Bu OIDC istemcisini silmek istediğinizden emin misiniz?",
"oidc_client_deleted_successfully": "OIDC istemcisi başarıyla silindi",
"authorization_url": "Yetkilendirme URL'si",
"oidc_discovery_url": "OIDC Discovery URL'si",
"token_url": "Token URL'si",
"userinfo_url": "Kullanıcı bilgisi URL'si",
"logout_url": ıkış URL'si",
"certificate_url": "Sertifika URL'si",
"enabled": "Etkin",
"disabled": "Devre dışı",
"oidc_client_updated_successfully": "OIDC istemcisi başarıyla güncellendi",
"create_new_client_secret": "Yeni istemci sırrı oluştur",
"are_you_sure_you_want_to_create_a_new_client_secret": "Yeni bir istemci sırrı oluşturmak istediğinizden emin misiniz? Eskisi geçersiz kılınacaktır.",
"generate": "Üret",
"new_client_secret_created_successfully": "Yeni istemci sırrı başarıyla oluşturuldu",
"allowed_user_groups_updated_successfully": "İzin verilen kullanıcı grupları başarıyla güncellendi",
"oidc_client_name": "OIDC İstemcisi {name}",
"client_id": "İstemci kimliği",
"client_secret": "İstemci sırrı",
"show_more_details": "Daha fazla detay göster",
"allowed_user_groups": "İzin Verilen Kullanıcı Grupları",
"add_user_groups_to_this_client_to_restrict_access_to_users_in_these_groups": "Bu istemciye kullanıcı grupları ekleyerek bu gruplardaki kullanıcılara erişimi kısıtlayın. Hiçbir kullanıcı grubu seçilmezse, tüm kullanıcılar bu istemciye erişebilecektir.",
"favicon": "Favicon",
"light_mode_logo": "Açık Mod Logo",
"dark_mode_logo": "Karanlık Mod Logo",
"background_image": "Arkaplan Resmi",
"language": "Dil",
"reset_profile_picture_question": "Profil resmini sıfırlamak istiyor musunuz?",
"this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default": "Bu, yüklenen resmi kaldıracak ve profil resmini varsayılan ayarına sıfırlayacaktır. Devam etmek istiyor musunuz?",
"reset": "Sıfırla",
"reset_to_default": "Varsayılan Ayara Sıfırla",
"profile_picture_has_been_reset": "Profil resmi sıfırlandı. Güncellenmesi birkaç dakika sürebilir.",
"select_the_language_you_want_to_use": "Kullanmak istediğiniz dili seçin. Lütfen bazı metinlerin otomatik olarak çevrilebileceğini ve hatalı olabileceğini unutmayın.",
"contribute_to_translation": "Bir sorunla karşılaşırsan, <link href='https://crowdin.com/project/pocket-id'>Crowdin</link> üzerinden çeviriye katkı sağlayabilirsin.",
"personal": "Kişisel",
"global": "Genel",
"all_users": "Tüm Kullanıcılar",
"all_events": "Tüm Etkinlikler",
"all_clients": "Tüm İstemciler",
"all_locations": "Tüm Konumlar",
"global_audit_log": "Genel Denetim Kaydı",
"see_all_account_activities_from_the_last_3_months": "Son 3 ay içindeki tüm kullanıcı etkinliklerini görüntüle.",
"token_sign_in": "Token ile Giriş",
"client_authorization": "İstemci Yetkilendirmesi",
"new_client_authorization": "Yeni İstemci Yetkilendirmesi",
"disable_animations": "Animasyonları Devre Dışı Bırak",
"turn_off_ui_animations": "Kullanıcı arayüzü animasyonlarını kapatın.",
"user_disabled": "Hesap Devre Dışı",
"disabled_users_cannot_log_in_or_use_services": "Hesapları devre dışı olan kullanıcılar, giriş yapamaz veya hizmetleri kullanamaz.",
"user_disabled_successfully": "Kullanıcı başarıyla devre dışı bırakıldı.",
"user_enabled_successfully": "Kullanıcı başarıyla etkinleştirildi.",
"status": "Durum",
"disable_firstname_lastname": "{firstName} {lastName} hesabını devre dışı bırak",
"are_you_sure_you_want_to_disable_this_user": "Bu kullanıcıyı devre dışı bırakmak istediğinizden emin misiniz? Devre dışı bırakılan kullanıcılar giriş yapamaz veya hizmetleri kullanamaz.",
"ldap_soft_delete_users": "Hesapları devre dışı olan kullanıcıları LDAP'tan koru.",
"ldap_soft_delete_users_description": "Etkinleştirildiğinde, LDAP'tan kaldırılan kullanıcılar sistemden silinmek yerine hesapları devre dışı bırakılacaktır.",
"login_code_email_success": "Giriş kodu kullanıcıya gönderildi.",
"send_email": "E-posta Gönder",
"show_code": "Kodu Göster",
"callback_url_description": "URL'ler, istemciniz tarafından sağlanır. Boş bırakılırsa otomatik olarak eklenecektir. Wildcard (*) karakterleri desteklenmektedir, ancak daha iyi güvenlik için kaçınılması önerilir.",
"logout_callback_url_description": ıkış URL'leri, istemciniz tarafından sağlanır. Boş bırakılırsa otomatik olarak eklenecektir. Wildcard (*) karakterleri desteklenmektedir, ancak daha iyi güvenlik için kaçınılması önerilir.",
"api_key_expiration": "API Anahtarının Geçerlilik Süresi",
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "API anahtarları sona ermek üzere olduğunda kullanıcıya bir e-posta gönderin.",
"authorize_device": "Cihazı Yetkilendir",
"the_device_has_been_authorized": "Cihaz yetkilendirildi.",
"enter_code_displayed_in_previous_step": "Önceki adımda görüntülenen kodu girin.",
"authorize": "Yetkilendir",
"federated_client_credentials": "Birleştirilmiş İstemci Kimlik Bilgileri",
"federated_client_credentials_description": "Birleşik istemci kimlik bilgilerini kullanarak, üçüncü taraf otoriteleri tarafından verilen JWT token'ları kullanarak OIDC istemcilerinin kimliklerini doğrulayabilirsiniz.",
"add_federated_client_credential": "Birleştirilmiş İstemci Kimlik Bilgisi Ekle",
"add_another_federated_client_credential": "Başka bir birleştirilmiş istemci kimlik bilgisi ekle",
"oidc_allowed_group_count": "İzin Verilen Grup Sayısı",
"unrestricted": "Kısıtlanmamış",
"show_advanced_options": "Gelişmiş Seçenekleri Göster",
"hide_advanced_options": "Gelişmiş Seçenekleri Gizle",
"oidc_data_preview": "OIDC Veri Önizlemesi",
"preview_the_oidc_data_that_would_be_sent_for_different_users": "Farklı kullanıcılar için gönderilecek OIDC verilerini önizleyin",
"id_token": "Kimlik Tokeni",
"access_token": "Erişim Tokeni",
"userinfo": "Kullanıcı bilgisi",
"id_token_payload": "Kimlik Belirteci Yükü",
"access_token_payload": "Erişim Tokenı İçeriği",
"userinfo_endpoint_response": "Kullanıcı Bilgisi Uç Noktası Yanıtı",
"copy": "Kopyala",
"no_preview_data_available": "Önizleme verisi mevcut değil",
"copy_all": "Tümünü Kopyala",
"preview": "Önizleme",
"preview_for_user": "{name} için Önizleme",
"preview_the_oidc_data_that_would_be_sent_for_this_user": "Bu kullanıcı için gönderilecek OIDC verilerini önizleyin",
"show": "Göster",
"select_an_option": "Bir seçenek seçin",
"select_user": "Kullanıcı Seç",
"error": "Hata",
"select_an_accent_color_to_customize_the_appearance_of_pocket_id": "Pocket ID'nin görünümünü özelleştirmek için bir vurgu rengi seçin.",
"accent_color": "Vurgu Rengi",
"custom_accent_color": "Özel Vurgu Rengi",
"custom_accent_color_description": "Geçerli CSS renk formatlarını kullanarak (örn. hex, rgb, hsl) özel bir renk girin.",
"color_value": "Renk Değeri",
"apply": "Uygula",
"signup_token": "Kayıt Olma Tokeni",
"create_a_signup_token_to_allow_new_user_registration": "Yeni kullanıcı kaydına izin vermek için bir kayıt token'i oluşturun.",
"usage_limit": "Kullanım Limiti",
"number_of_times_token_can_be_used": "Kayıt token'inin kullanılabileceği maksimum sayıyı belirtin.",
"expires": "Sona Erme Tarihi",
"signup": "Kaydol",
"user_creation": "Kullanıcı Oluşturma",
"configure_user_creation": "Kullanıcı oluşturma ayarlarını yönetin, kayıt yöntemleri ve yeni kullanıcılar için varsayılan izinler dahil.",
"user_creation_groups_description": "Bu grupları otomatik olarak yeni kullanıcılara atayın.",
"user_creation_claims_description": "Bu özel alanları otomatik olarak yeni kullanıcılara atayın.",
"user_creation_updated_successfully": "Kullanıcı oluşturma ayarları başarıyla güncellendi.",
"signup_disabled_description": "Kullanıcı kayıtları tamamen devre dışı bırakılmıştır. Sadece yöneticiler yeni kullanıcı hesapları oluşturabilir.",
"signup_requires_valid_token": "Bir hesap oluşturmak için geçerli bir kayıt token'i gereklidir",
"validating_signup_token": "Kayıt token'i doğrulanıyor",
"go_to_login": "Giriş yap",
"signup_to_appname": "{appName} Kayıt Ol",
"create_your_account_to_get_started": "Başlamak için hesabınızı oluşturun.",
"initial_account_creation_description": "Başlamak için lütfen hesabınızı oluşturun. Daha sonra bir geçiş anahtarı ayarlayabileceksiniz.",
"setup_your_passkey": "Geçiş Anahtarınızı Ayarlayın",
"create_a_passkey_to_securely_access_your_account": "Hesabınıza güvenli bir şekilde erişmek için bir geçiş anahtarı oluşturun. Bu, oturum açmanın birincil yolu olacaktır.",
"skip_for_now": "Şimdilik Atla",
"account_created": "Hesap Oluşturuldu",
"enable_user_signups": "Kullanıcıların hesap oluşturmasını Etkinleştir",
"enable_user_signups_description": "Kullanıcıların Pocket ID'de yeni hesaplar için nasıl kaydolabileceğini belirleyin.",
"user_signups_are_disabled": "Kullanıcıların hesap oluşturma işlemleri şimdilik devre dışı",
"create_signup_token": "Kayıt Token'i Oluştur",
"view_active_signup_tokens": "Aktif Kayıt Token'lerini Görüntüle",
"manage_signup_tokens": "Kayıt Token'lerini Yönet",
"view_and_manage_active_signup_tokens": "Aktif kayıt token'lerini görüntüleyin ve yönetin.",
"signup_token_deleted_successfully": "Kayıt token'i başarıyla silindi.",
"expired": "Süresi Dolmuş",
"used_up": "Tükenmiş",
"active": "Aktif",
"usage": "Kullanım",
"created": "Oluşturuldu",
"token": "Token",
"loading": "Yükleniyor",
"delete_signup_token": "Kayıt Token'ini Sil",
"are_you_sure_you_want_to_delete_this_signup_token": "Bu kayıt token'ini silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"signup_with_token": "Kayıt olma token'i ile kaydol",
"signup_with_token_description": "Kullanıcılar yalnızca bir yönetici tarafından oluşturulan geçerli bir kayıt token'i ile kaydolabilir.",
"signup_open": "Hesap oluşturma açık",
"signup_open_description": "Herkes kısıtlama olmaksızın yeni bir hesap oluşturabilir.",
"of": "of",
"skip_passkey_setup": "Geçiş Anahtarı Kurulumunu Atla",
"skip_passkey_setup_description": "Bir geçiş anahtarı ayarlamanız şiddetle önerilir çünkü bir tane olmadan, oturum süresi dolduğunda hesabınıza erişiminiz engellenecektir.",
"my_apps": "Uygulamalarım",
"no_apps_available": "Kullanılabilir uygulama yok",
"contact_your_administrator_for_app_access": "Uygulamalara erişim için yöneticinizle iletişime geçin.",
"launch": "Başlat",
"client_launch_url": "İstemci Başlatma URL'si",
"client_launch_url_description": "Kullanıcıların Uygulamalarım sayfasından uygulamayı başlattığında açılacak URL.",
"client_name_description": "Pocket ID arayüzünde gösterilen istemci adı.",
"revoke_access": "Erişimi iptal et",
"revoke_access_description": "Erişimi <b>{clientName}</b> için iptal et. <b>{clientName}</b> artık hesap bilgilerinize erişemeyecek.",
"revoke_access_successful": "Erişim {clientName} başarıyla iptal edildi.",
"last_signed_in_ago": "Son oturum açma {time} önce",
"invalid_client_id": "İstemci kimliği yalnızca harfler, rakamlar, alt çizgi ve tire içerebilir",
"custom_client_id_description": "Uygulamanızın gerektirmesi durumunda özel bir istemci kimliği ayarlayın. Aksi takdirde, rastgele bir tane oluşturmak için boş bırakın.",
"generated": "Oluşturuldu",
"administration": "Yönetim",
"group_rdn_attribute_description": "Grupların ayırt edici adında (DN) kullanılan öznitelik.",
"display_name_attribute": "Görünen Ad Özniteliği",
"display_name": "Görünen Ad",
"configure_application_images": "Uygulama Görsellerini Yapılandır",
"ui_config_disabled_info_title": "UI Yapılandırması Devre Dışı",
"ui_config_disabled_info_description": "Kullanıcı arayüzü yapılandırması devre dışı bırakıldı çünkü uygulama yapılandırma ayarları ortam değişkenleri aracılığıyla yönetiliyor. Bazı ayarlar düzenlenemeyebilir.",
"logo_from_url_description": "Doğrudan bir resim URLsi (svg, png, webp) yapıştırın. İkonları <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> veya <link href=\"https://dashboardicons.com\">Dashboard Icons</link>adreslerinden bulabilirsiniz.",
"invalid_url": "Geçersiz URL",
"require_user_email": "E-posta Adresi Gerektir",
"require_user_email_description": "Kullanıcıların bir e-posta adresine sahip olmasını gerektirir. Devre dışı bırakılırsa, e-posta adresi olmayan kullanıcılar, bir e-posta adresi gerektiren özellikleri kullanamayacaktır.",
"view": "Görüntüle",
"toggle_columns": "Sütünları değiştir",
"locale": "Yerel",
"ldap_id": "LDAP Kimliği",
"reauthentication": "Yeniden Kimlik Doğrulama",
"clear_filters": "Filtreleri Temizle",
"default_profile_picture": "Varsayılan Profil Resmi"
}

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Ви впевнені, що хочете анулювати API-ключ «{apiKeyName}»? Це призведе до зупинки всіх інтеграцій, які використовують цей ключ.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Ви впевнені, що хочете анулювати API-ключ «{apiKeyName}»? Це призведе до зупинки всіх інтеграцій, які використовують цей ключ.",
"last_used": "Останнє використання", "last_used": "Останнє використання",
"actions": "Дії", "actions": "Дії",
"images_updated_successfully": "Зображення успішно оновлено", "images_updated_successfully": "Зображення успішно оновлено. Оновлення може зайняти кілька хвилин.",
"general": "Загальне", "general": "Загальне",
"configure_smtp_to_send_emails": "Увімкніть сповіщення електронною поштою, щоб повідомляти користувачів про вхід з нового пристрою або місцеперебування.", "configure_smtp_to_send_emails": "Увімкніть сповіщення електронною поштою, щоб повідомляти користувачів про вхід з нового пристрою або місцеперебування.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Вставте прямий URL-адресу зображення (svg, png, webp). Знайдіть іконки на <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> або <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Вставте прямий URL-адресу зображення (svg, png, webp). Знайдіть іконки на <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> або <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "Недійсний URL-адреса", "invalid_url": "Недійсний URL-адреса",
"require_user_email": "Потрібна адреса електронної пошти", "require_user_email": "Потрібна адреса електронної пошти",
"require_user_email_description": "Вимагає від користувачів наявність адреси електронної пошти. Якщо ця опція вимкнена, користувачі без адреси електронної пошти не зможуть користуватися функціями, для яких потрібна адреса електронної пошти." "require_user_email_description": "Вимагає від користувачів наявність адреси електронної пошти. Якщо ця опція вимкнена, користувачі без адреси електронної пошти не зможуть користуватися функціями, для яких потрібна адреса електронної пошти.",
"view": "Перегляд",
"toggle_columns": "Перемикання стовпців",
"locale": "Локаль",
"ldap_id": "LDAP-ідентифікатор",
"reauthentication": "Повторна аутентифікація",
"clear_filters": "Очистити фільтри",
"default_profile_picture": "Стандартне зображення профілю"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Bạn có chắc chắn muốn hủy API Key \"{apiKeyName}\" không? Việc này sẽ làm gián đoạn tất cả các tích hợp sử dụng khóa này.", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "Bạn có chắc chắn muốn hủy API Key \"{apiKeyName}\" không? Việc này sẽ làm gián đoạn tất cả các tích hợp sử dụng khóa này.",
"last_used": "Lần Sử Dụng Cuối", "last_used": "Lần Sử Dụng Cuối",
"actions": "Hành động", "actions": "Hành động",
"images_updated_successfully": "Đã cập nhật hình ảnh thành công", "images_updated_successfully": "Hình ảnh đã được cập nhật thành công. Quá trình cập nhật có thể mất vài phút.",
"general": "Tổng quan", "general": "Tổng quan",
"configure_smtp_to_send_emails": "Bật thông báo email để thông báo cho người dùng khi phát hiện đăng nhập từ thiết bị hoặc vị trí mới.", "configure_smtp_to_send_emails": "Bật thông báo email để thông báo cho người dùng khi phát hiện đăng nhập từ thiết bị hoặc vị trí mới.",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "Dán URL hình ảnh trực tiếp (svg, png, webp). Tìm biểu tượng tại <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> hoặc <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.", "logo_from_url_description": "Dán URL hình ảnh trực tiếp (svg, png, webp). Tìm biểu tượng tại <link href=\"https://selfh.st/icons\">Selfh.st Icons</link> hoặc <link href=\"https://dashboardicons.com\">Dashboard Icons</link>.",
"invalid_url": "URL không hợp lệ", "invalid_url": "URL không hợp lệ",
"require_user_email": "Yêu cầu địa chỉ email", "require_user_email": "Yêu cầu địa chỉ email",
"require_user_email_description": "Yêu cầu người dùng phải có địa chỉ email. Nếu tính năng này bị vô hiệu hóa, những người dùng không có địa chỉ email sẽ không thể sử dụng các tính năng yêu cầu địa chỉ email." "require_user_email_description": "Yêu cầu người dùng phải có địa chỉ email. Nếu tính năng này bị vô hiệu hóa, những người dùng không có địa chỉ email sẽ không thể sử dụng các tính năng yêu cầu địa chỉ email.",
"view": "Xem",
"toggle_columns": "Bật/tắt cột",
"locale": "Vùng",
"ldap_id": "ID LDAP",
"reauthentication": "Xác thực lại",
"clear_filters": "Xóa bộ lọc",
"default_profile_picture": "Ảnh đại diện mặc định"
} }

View File

@@ -19,7 +19,7 @@
"no_items_found": "这里暂时空空如也", "no_items_found": "这里暂时空空如也",
"select_items": "选择项目……", "select_items": "选择项目……",
"search": "搜索……", "search": "搜索……",
"expand_card": "展开卡片", "expand_card": "展开",
"copied": "已复制", "copied": "已复制",
"click_to_copy": "点击复制", "click_to_copy": "点击复制",
"something_went_wrong": "出了点问题", "something_went_wrong": "出了点问题",
@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "您确定要撤销 API 密钥 \"{apiKeyName}\" 吗?这将中断使用此密钥的任何集成。", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "您确定要撤销 API 密钥 \"{apiKeyName}\" 吗?这将中断使用此密钥的任何集成。",
"last_used": "上次使用时间", "last_used": "上次使用时间",
"actions": "操作", "actions": "操作",
"images_updated_successfully": "成功更新图片", "images_updated_successfully": "图片更新成功更新过程可能需要几分钟时间。",
"general": "常规", "general": "常规",
"configure_smtp_to_send_emails": "启用电子邮件通知,当检测到来自新设备或新位置的登录时提醒用户。", "configure_smtp_to_send_emails": "启用电子邮件通知,当检测到来自新设备或新位置的登录时提醒用户。",
"ldap": "LDAP", "ldap": "LDAP",
@@ -394,7 +394,7 @@
"user_creation": "用户创建", "user_creation": "用户创建",
"configure_user_creation": "管理用户创建设置,包括注册方式和新用户的默认权限。", "configure_user_creation": "管理用户创建设置,包括注册方式和新用户的默认权限。",
"user_creation_groups_description": "新用户注册时自动分配的群组。", "user_creation_groups_description": "新用户注册时自动分配的群组。",
"user_creation_claims_description": "用户注册时自动为新用户分配这些自定义声明。", "user_creation_claims_description": "用户注册时自动分配的自定义声明。",
"user_creation_updated_successfully": "用户创建设置已成功更新。", "user_creation_updated_successfully": "用户创建设置已成功更新。",
"signup_disabled_description": "已完全禁止新用户注册。只有管理员可以创建新账户。", "signup_disabled_description": "已完全禁止新用户注册。只有管理员可以创建新账户。",
"signup_requires_valid_token": "必须输入有效注册令牌才能注册新账户", "signup_requires_valid_token": "必须输入有效注册令牌才能注册新账户",
@@ -435,25 +435,32 @@
"no_apps_available": "没有可用应用", "no_apps_available": "没有可用应用",
"contact_your_administrator_for_app_access": "请联系您的管理员以获取应用的访问权限。", "contact_your_administrator_for_app_access": "请联系您的管理员以获取应用的访问权限。",
"launch": "启动", "launch": "启动",
"client_launch_url": "客户发布链接", "client_launch_url": "客户端启动网址",
"client_launch_url_description": "当用户从“我的应用”页面启动应用时将打开的 URL。", "client_launch_url_description": "当用户从“我的应用”页面启动应用时将打开的 URL。",
"client_name_description": "在Pocket ID用户界面中显示的客户端名称。", "client_name_description": "在Pocket ID用户界面中显示的客户端名称。",
"revoke_access": "撤销访问权限", "revoke_access": "撤销访问权限",
"revoke_access_description": "撤销 <b>{clientName}</b>. <b>{clientName}</b>将无法再访问您的账户信息。", "revoke_access_description": "撤销 <b>{clientName}</b>的访问权限。 <b>{clientName}</b>将无法再访问您的账户信息。",
"revoke_access_successful": "对 {clientName} 的访问权限已成功撤销。", "revoke_access_successful": "对 {clientName} 的访问权限已成功撤销。",
"last_signed_in_ago": "最后一次登录 {time} 前", "last_signed_in_ago": "最后一次登录 {time} 前",
"invalid_client_id": "客户端 ID 只能包含字母、数字、下划线和连字符", "invalid_client_id": "客户端 ID 只能包含字母、数字、下划线和连字符",
"custom_client_id_description": "此处可根据应用需要设置自定义客户端 ID。留空随机生成。", "custom_client_id_description": "此处可根据应用需要设置自定义客户端 ID。留空随机生成。",
"generated": "已生成", "generated": "已生成",
"administration": "管理员选项", "administration": "管理员选项",
"group_rdn_attribute_description": "在组的区分名称DN中使用的属性。", "group_rdn_attribute_description": "在组的区分名称DN中使用的属性。",
"display_name_attribute": "显示名称属性", "display_name_attribute": "显示名称属性",
"display_name": "显示名称", "display_name": "显示名称",
"configure_application_images": "配置应用程序图标", "configure_application_images": "配置应用图标",
"ui_config_disabled_info_title": "用户界面配置已禁用", "ui_config_disabled_info_title": "用户界面配置已禁用",
"ui_config_disabled_info_description": "由于应用配置设置已通过环境变量设定,用户界面配置已禁用。某些设置可能无法编辑。", "ui_config_disabled_info_description": "由于应用配置设置已通过环境变量设定,用户界面配置已禁用。某些设置可能无法编辑。",
"logo_from_url_description": "粘贴直接图片URLsvg、png、webp格式。<link href=\"https://selfh.st/icons\">可在Selfh.st图标库</link>或<link href=\"https://dashboardicons.com\">仪表盘图标库</link>中查找图标。", "logo_from_url_description": "粘贴直接图片URLsvg、png、webp格式。<link href=\"https://selfh.st/icons\">可在Selfh.st图标库</link>或<link href=\"https://dashboardicons.com\">仪表盘图标库</link>中查找图标。",
"invalid_url": "无效网址", "invalid_url": "无效网址",
"require_user_email": "需要电子邮件地址", "require_user_email": "需要电子邮件地址",
"require_user_email_description": "要求用户拥有电子邮件地址。若禁用此功能,没有电子邮件地址的用户将无法使用需要电子邮件地址的功能。" "require_user_email_description": "要求用户拥有电子邮件地址。若禁用此功能,没有电子邮件地址的用户将无法使用需要电子邮件地址的功能。",
"view": "查看",
"toggle_columns": "切换列",
"locale": "区域设置",
"ldap_id": "LDAP标识符",
"reauthentication": "重新认证",
"clear_filters": "清除筛选条件",
"default_profile_picture": "默认个人资料图片"
} }

View File

@@ -155,7 +155,7 @@
"are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "您確定要撤銷 API 金鑰「{apiKeyName}」嗎?這將會中斷所有使用此金鑰的整合。", "are_you_sure_you_want_to_revoke_the_api_key_apikeyname": "您確定要撤銷 API 金鑰「{apiKeyName}」嗎?這將會中斷所有使用此金鑰的整合。",
"last_used": "上次使用", "last_used": "上次使用",
"actions": "操作", "actions": "操作",
"images_updated_successfully": "圖片更新成功", "images_updated_successfully": "圖片已成功更新。更新過程可能需要數分鐘。",
"general": "一般", "general": "一般",
"configure_smtp_to_send_emails": "啟用電子郵件通知以提醒使用者有來自新裝置或位置的登入。", "configure_smtp_to_send_emails": "啟用電子郵件通知以提醒使用者有來自新裝置或位置的登入。",
"ldap": "LDAP", "ldap": "LDAP",
@@ -455,5 +455,12 @@
"logo_from_url_description": "貼上直接圖片網址svg、png、webp。在<link href=\"https://selfh.st/icons\">Selfh.st 圖示庫或</link> <link href=\"https://dashboardicons.com\">儀表板圖示庫中</link>尋找圖示。", "logo_from_url_description": "貼上直接圖片網址svg、png、webp。在<link href=\"https://selfh.st/icons\">Selfh.st 圖示庫或</link> <link href=\"https://dashboardicons.com\">儀表板圖示庫中</link>尋找圖示。",
"invalid_url": "無效網址", "invalid_url": "無效網址",
"require_user_email": "需要電子郵件地址", "require_user_email": "需要電子郵件地址",
"require_user_email_description": "要求使用者必須擁有電子郵件地址。若此功能被停用,沒有電子郵件地址的使用者將無法使用需要電子郵件地址的功能。" "require_user_email_description": "要求使用者必須擁有電子郵件地址。若此功能被停用,沒有電子郵件地址的使用者將無法使用需要電子郵件地址的功能。",
"view": "檢視",
"toggle_columns": "切換欄位",
"locale": "地區",
"ldap_id": "LDAP 識別碼",
"reauthentication": "重新驗證",
"clear_filters": "清除篩選條件",
"default_profile_picture": "預設個人資料照片"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "pocket-id-frontend", "name": "pocket-id-frontend",
"version": "1.13.1", "version": "1.15.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -14,50 +14,50 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/browser": "^13.2.2",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.16",
"axios": "^1.12.0", "axios": "^1.12.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"jose": "^5.10.0", "jose": "^5.10.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"runed": "^0.31.1", "runed": "^0.31.1",
"sveltekit-superforms": "^2.27.1", "sveltekit-superforms": "^2.28.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"zod": "^4.0.9" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@inlang/paraglide-js": "^2.2.0", "@inlang/paraglide-js": "^2.4.0",
"@inlang/plugin-m-function-matcher": "^2.1.0", "@inlang/plugin-m-function-matcher": "^2.1.0",
"@inlang/plugin-message-format": "^4.0.0", "@inlang/plugin-message-format": "^4.0.0",
"@internationalized/date": "^3.8.2", "@internationalized/date": "^3.10.0",
"@lucide/svelte": "^0.525.0", "@lucide/svelte": "^0.525.0",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.36.3", "@sveltejs/kit": "^2.47.3",
"@sveltejs/vite-plugin-svelte": "^6.1.0", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/eslint": "^9.6.1", "@types/eslint": "^9.6.1",
"@types/node": "^22.16.5", "@types/node": "^22.18.12",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.6",
"bits-ui": "^2.8.11", "bits-ui": "^2.14.1",
"eslint": "^9.31.0", "eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.11.0", "eslint-plugin-svelte": "^3.12.5",
"formsnap": "^2.0.1", "formsnap": "^2.0.1",
"globals": "^16.3.0", "globals": "^16.4.0",
"mode-watcher": "^1.1.0", "mode-watcher": "^1.1.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"rollup": "^4.46.3", "rollup": "^4.52.5",
"svelte": "^5.36.16", "svelte": "^5.41.3",
"svelte-check": "^4.3.0", "svelte-check": "^4.3.3",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.16",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"tw-animate-css": "^1.3.5", "tw-animate-css": "^1.4.0",
"typescript": "^5.8.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.38.0", "typescript-eslint": "^8.46.2",
"vite": "^7.0.7" "vite": "^7.1.12"
} }
} }

View File

@@ -15,6 +15,7 @@
"pt-BR", "pt-BR",
"ru", "ru",
"sv", "sv",
"tr",
"uk", "uk",
"vi", "vi",
"zh-CN", "zh-CN",

View File

@@ -1,12 +1,25 @@
<!doctype html> <!doctype html>
<html lang="%lang%"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="/api/application-images/favicon" /> <link rel="icon" href="/api/application-images/favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<link rel="manifest" href="/app.webmanifest" /> <link rel="manifest" href="/app.webmanifest" />
<link rel="apple-touch-icon" href="/img/static-logo-512.png"> <link rel="apple-touch-icon" href="/img/static-logo-512.png" />
<script>
try {
const mode = localStorage.getItem('mode-watcher-mode') || 'system';
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = mode === 'dark' || (mode === 'system' && prefersDark);
document.documentElement.classList.toggle('dark', isDark);
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
} catch (e) {
// Ignore errors
// Failing to set theme is not critical
}
</script>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View File

@@ -1,217 +0,0 @@
<script lang="ts" generics="T extends {id:string}">
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import { Input } from '$lib/components/ui/input/index.js';
import * as Pagination from '$lib/components/ui/pagination';
import * as Select from '$lib/components/ui/select';
import * as Table from '$lib/components/ui/table/index.js';
import Empty from '$lib/icons/empty.svelte';
import { m } from '$lib/paraglide/messages';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { debounced } from '$lib/utils/debounce-util';
import { cn } from '$lib/utils/style';
import { ChevronDown } from '@lucide/svelte';
import type { Snippet } from 'svelte';
import Button from './ui/button/button.svelte';
let {
items,
requestOptions = $bindable(),
selectedIds = $bindable(),
withoutSearch = false,
selectionDisabled = false,
onRefresh,
columns,
rows
}: {
items: Paginated<T>;
requestOptions: SearchPaginationSortRequest;
selectedIds?: string[];
withoutSearch?: boolean;
selectionDisabled?: boolean;
onRefresh: (requestOptions: SearchPaginationSortRequest) => Promise<Paginated<T>>;
columns: { label: string; hidden?: boolean; sortColumn?: string }[];
rows: Snippet<[{ item: T }]>;
} = $props();
let searchValue = $state('');
let availablePageSizes: number[] = [20, 50, 100];
let allChecked = $derived.by(() => {
if (!selectedIds || items.data.length === 0) return false;
for (const item of items.data) {
if (!selectedIds.includes(item.id)) {
return false;
}
}
return true;
});
const onSearch = debounced(async (search: string) => {
requestOptions.search = search;
await onRefresh(requestOptions);
searchValue = search;
}, 300);
async function onAllCheck(checked: boolean) {
const pageIds = items.data.map((item) => item.id);
const current = selectedIds ?? [];
if (checked) {
selectedIds = Array.from(new Set([...current, ...pageIds]));
} else {
selectedIds = current.filter((id) => !pageIds.includes(id));
}
}
async function onCheck(checked: boolean, id: string) {
const current = selectedIds ?? [];
if (checked) {
selectedIds = Array.from(new Set([...current, id]));
} else {
selectedIds = current.filter((selectedId) => selectedId !== id);
}
}
async function onPageChange(page: number) {
requestOptions.pagination = { limit: items.pagination.itemsPerPage, page };
onRefresh(requestOptions);
}
async function onPageSizeChange(size: number) {
requestOptions.pagination = { limit: size, page: 1 };
onRefresh(requestOptions);
}
async function onSort(column?: string, direction: 'asc' | 'desc' = 'asc') {
if (!column) return;
requestOptions.sort = { column, direction };
onRefresh(requestOptions);
}
</script>
{#if !withoutSearch}
<Input
value={searchValue}
class={cn(
'relative z-50 mb-4 max-w-sm',
items.data.length == 0 && searchValue == '' && 'hidden'
)}
placeholder={m.search()}
type="text"
oninput={(e: Event) => onSearch((e.currentTarget as HTMLInputElement).value)}
/>
{/if}
{#if items.data.length === 0 && searchValue === ''}
<div class="my-5 flex flex-col items-center">
<Empty class="text-muted-foreground h-20" />
<p class="text-muted-foreground mt-3 text-sm">{m.no_items_found()}</p>
</div>
{:else}
<Table.Root class="min-w-full table-auto overflow-x-auto">
<Table.Header>
<Table.Row>
{#if selectedIds}
<Table.Head class="w-12">
<Checkbox
disabled={selectionDisabled}
checked={allChecked}
onCheckedChange={(c: boolean) => onAllCheck(c as boolean)}
/>
</Table.Head>
{/if}
{#each columns as column}
<Table.Head class={cn(column.hidden && 'sr-only', column.sortColumn && 'px-0')}>
{#if column.sortColumn}
<Button
variant="ghost"
class="flex items-center"
onclick={() =>
onSort(
column.sortColumn,
requestOptions.sort?.direction === 'desc' ? 'asc' : 'desc'
)}
>
{column.label}
{#if requestOptions.sort?.column === column.sortColumn}
<ChevronDown
class={cn(
'ml-2 size-4',
requestOptions.sort?.direction === 'asc' ? 'rotate-180' : ''
)}
/>
{/if}
</Button>
{:else}
{column.label}
{/if}
</Table.Head>
{/each}
</Table.Row>
</Table.Header>
<Table.Body>
{#each items.data as item}
<Table.Row class={selectedIds?.includes(item.id) ? 'bg-muted/20' : ''}>
{#if selectedIds}
<Table.Cell class="w-12">
<Checkbox
disabled={selectionDisabled}
checked={selectedIds.includes(item.id)}
onCheckedChange={(c: boolean) => onCheck(c, item.id)}
/>
</Table.Cell>
{/if}
{@render rows({ item })}
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
<div class="mt-5 flex flex-col-reverse items-center justify-between gap-3 sm:flex-row">
<div class="flex items-center space-x-2">
<p class="text-sm font-medium">{m.items_per_page()}</p>
<Select.Root
type="single"
value={items.pagination.itemsPerPage.toString()}
onValueChange={(v) => onPageSizeChange(Number(v))}
>
<Select.Trigger class="h-9 w-[80px]">
{items.pagination.itemsPerPage}
</Select.Trigger>
<Select.Content>
{#each availablePageSizes as size}
<Select.Item value={size.toString()}>{size}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<Pagination.Root
class="mx-0 w-auto"
count={items.pagination.totalItems}
perPage={items.pagination.itemsPerPage}
{onPageChange}
page={items.pagination.currentPage}
>
{#snippet children({ pages })}
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type !== 'ellipsis' && page.value != 0}
<Pagination.Item>
<Pagination.Link {page} isActive={items.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}

View File

@@ -1,69 +1,111 @@
<script lang="ts"> <script lang="ts">
import AdvancedTable from '$lib/components/advanced-table.svelte'; import AdvancedTable from '$lib/components/table/advanced-table.svelte';
import { Badge } from '$lib/components/ui/badge'; import { Badge } from '$lib/components/ui/badge';
import * as Table from '$lib/components/ui/table';
import { m } from '$lib/paraglide/messages'; import { m } from '$lib/paraglide/messages';
import {translateAuditLogEvent} from "$lib/utils/audit-log-translator";
import AuditLogService from '$lib/services/audit-log-service'; import AuditLogService from '$lib/services/audit-log-service';
import type { AuditLog } from '$lib/types/audit-log.type'; import type { AdvancedTableColumn } from '$lib/types/advanced-table.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type { AuditLog, AuditLogFilter } from '$lib/types/audit-log.type';
import { translateAuditLogEvent } from '$lib/utils/audit-log-translator';
let { let {
auditLogs,
isAdmin = false, isAdmin = false,
requestOptions filters
}: { }: {
auditLogs: Paginated<AuditLog>;
isAdmin?: boolean; isAdmin?: boolean;
requestOptions: SearchPaginationSortRequest; filters?: AuditLogFilter;
} = $props(); } = $props();
const auditLogService = new AuditLogService(); const auditLogService = new AuditLogService();
let tableRef: AdvancedTable<AuditLog>;
const columns: AdvancedTableColumn<AuditLog>[] = [
{
label: m.time(),
column: 'createdAt',
sortable: true,
value: (item) => new Date(item.createdAt).toLocaleString()
},
{
label: m.username(),
column: 'username',
hidden: !isAdmin,
value: (item) => item.username ?? m.unknown()
},
{
label: m.event(),
column: 'event',
sortable: true,
cell: EventCell
},
{
label: m.approximate_location(),
key: 'location',
value: (item) => formatLocation(item)
},
{
label: m.ip_address(),
column: 'ipAddress',
sortable: true
},
{
label: m.device(),
column: 'device',
sortable: true
},
{
label: m.client(),
key: 'client',
value: (item) => item.data?.clientName
}
];
$effect(() => {
if (filters) {
tableRef?.refresh();
}
});
export async function refresh() {
await tableRef.refresh();
}
function formatLocation(log: AuditLog) {
if (log.city && log.country) {
return `${log.city}, ${log.country}`;
} else if (log.country) {
return log.country;
} else {
return m.unknown();
}
}
function wrapFilters(filters?: Record<string, string>) {
if (!filters) return undefined;
return Object.fromEntries(
Object.entries(filters)
.filter(([_, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => [key, [value]])
);
}
</script> </script>
{#snippet EventCell({ item }: { item: AuditLog })}
<Badge class="rounded-full" variant="outline">
{translateAuditLogEvent(item.event)}
</Badge>
{/snippet}
<AdvancedTable <AdvancedTable
items={auditLogs} id="audit-log-list-{isAdmin ? 'admin' : 'user'}"
{requestOptions} bind:this={tableRef}
onRefresh={async (options) => fetchCallback={async (options) =>
isAdmin isAdmin
? (auditLogs = await auditLogService.listAllLogs(options)) ? await auditLogService.listAllLogs({
: (auditLogs = await auditLogService.list(options))} ...options,
columns={[ filters: wrapFilters(filters)
{ label: m.time(), sortColumn: 'createdAt' }, })
...(isAdmin ? [{ label: 'Username' }] : []), : await auditLogService.list(options)}
{ label: m.event(), sortColumn: 'event' }, defaultSort={{ column: 'createdAt', direction: 'desc' }}
{ label: m.approximate_location(), sortColumn: 'city' },
{ label: m.ip_address(), sortColumn: 'ipAddress' },
{ label: m.device(), sortColumn: 'device' },
{ label: m.client() }
]}
withoutSearch withoutSearch
> {columns}
{#snippet rows({ item })} />
<Table.Cell>{new Date(item.createdAt).toLocaleString()}</Table.Cell>
{#if isAdmin}
<Table.Cell>
{#if item.username}
{item.username}
{:else}
Unknown User
{/if}
</Table.Cell>
{/if}
<Table.Cell>
<Badge class="rounded-full" variant="outline">{translateAuditLogEvent(item.event)}</Badge>
</Table.Cell>
<Table.Cell>
{#if item.city && item.country}
{item.city}, {item.country}
{:else if item.country}
{item.country}
{:else}
{m.unknown()}
{/if}
</Table.Cell>
<Table.Cell>{item.ipAddress}</Table.Cell>
<Table.Cell>{item.device}</Table.Cell>
<Table.Cell>{item.data.clientName}</Table.Cell>
{/snippet}
</AdvancedTable>

View File

@@ -12,11 +12,13 @@
let { let {
label, label,
accept, accept,
onchange onchange,
id = 'file-input'
}: { }: {
label: string; label: string;
accept?: string; accept?: string;
onchange: (file: File | string | null) => void; onchange: (file: File | string | null) => void;
id?: string;
} = $props(); } = $props();
let url = $state(''); let url = $state('');
@@ -47,7 +49,7 @@
<div class="flex"> <div class="flex">
<FileInput <FileInput
id="logo" {id}
variant="secondary" variant="secondary"
{accept} {accept}
onchange={handleFileChange} onchange={handleFileChange}
@@ -64,9 +66,9 @@
<LucideChevronDown class="size-4" /></Popover.Trigger <LucideChevronDown class="size-4" /></Popover.Trigger
> >
<Popover.Content class="w-80"> <Popover.Content class="w-80">
<Label for="file-url" class="text-xs">URL</Label> <Label for="{id}-url" class="text-xs">URL</Label>
<Input <Input
id="file-url" id="{id}-url"
placeholder="" placeholder=""
value={url} value={url}
oninput={(e) => (url = e.currentTarget.value)} oninput={(e) => (url = e.currentTarget.value)}

View File

@@ -12,14 +12,10 @@
}); });
</script> </script>
<div class={'bg-muted flex items-center justify-center rounded-2xl p-3'}> <div class={cn('bg-muted flex items-center justify-center rounded-2xl p-3', props.class)}>
{#if error} {#if error}
<LucideImageOff class={cn('text-muted-foreground p-5', props.class)} /> <LucideImageOff class="text-muted-foreground p-5" />
{:else} {:else}
<img <img {...props} class="aspect-square object-contain" onerror={() => (error = true)} />
{...props}
class={cn('object-contain aspect-square', props.class)}
onerror={() => (error = true)}
/>
{/if} {/if}
</div> </div>

View File

@@ -1,23 +1,29 @@
<script lang="ts"> <script lang="ts">
import { afterNavigate } from '$app/navigation';
import { page } from '$app/state'; import { page } from '$app/state';
import { m } from '$lib/paraglide/messages'; import { m } from '$lib/paraglide/messages';
import appConfigStore from '$lib/stores/application-configuration-store'; import appConfigStore from '$lib/stores/application-configuration-store';
import { cachedBackgroundImage } from '$lib/utils/cached-image-util'; 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';
import * as Card from './ui/card'; import * as Card from './ui/card';
let { let {
children, children,
showAlternativeSignInMethodButton = false, showAlternativeSignInMethodButton = false
animate = false
}: { }: {
children: Snippet; children: Snippet;
showAlternativeSignInMethodButton?: boolean; showAlternativeSignInMethodButton?: boolean;
animate?: boolean;
} = $props(); } = $props();
let isInitialLoad = $state(false);
let animate = $derived(isInitialLoad && !$appConfigStore.disableAnimations);
afterNavigate((e) => {
isInitialLoad = !e?.from?.url;
});
const isDesktop = new MediaQuery('min-width: 1024px'); const isDesktop = new MediaQuery('min-width: 1024px');
let alternativeSignInButton = $state({ let alternativeSignInButton = $state({
href: '/login/alternative', href: '/login/alternative',

View File

@@ -1,33 +1,29 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import AdvancedTable from '$lib/components/advanced-table.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog/'; import { openConfirmDialog } from '$lib/components/confirm-dialog/';
import AdvancedTable from '$lib/components/table/advanced-table.svelte';
import { Badge, type BadgeVariant } from '$lib/components/ui/badge'; import { Badge, type BadgeVariant } from '$lib/components/ui/badge';
import { Button, buttonVariants } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import * as Dialog from '$lib/components/ui/dialog'; 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 { m } from '$lib/paraglide/messages';
import UserService from '$lib/services/user-service'; import UserService from '$lib/services/user-service';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type {
AdvancedTableColumn,
CreateAdvancedTableActions
} from '$lib/types/advanced-table.type';
import type { SignupTokenDto } from '$lib/types/signup-token.type'; import type { SignupTokenDto } from '$lib/types/signup-token.type';
import { axiosErrorToast } from '$lib/utils/error-util'; import { axiosErrorToast } from '$lib/utils/error-util';
import { Copy, Ellipsis, Trash2 } from '@lucide/svelte'; import { Copy, Trash2 } from '@lucide/svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
let { let {
open = $bindable(), open = $bindable()
signupTokens = $bindable(),
signupTokensRequestOptions,
onTokenDeleted
}: { }: {
open: boolean; open: boolean;
signupTokens: Paginated<SignupTokenDto>;
signupTokensRequestOptions: SearchPaginationSortRequest;
onTokenDeleted?: () => Promise<void>;
} = $props(); } = $props();
const userService = new UserService(); const userService = new UserService();
let tableRef: AdvancedTable<SignupTokenDto>;
function formatDate(dateStr: string | undefined) { function formatDate(dateStr: string | undefined) {
if (!dateStr) return m.never(); if (!dateStr) return m.never();
@@ -44,12 +40,8 @@
action: async () => { action: async () => {
try { try {
await userService.deleteSignupToken(token.id); await userService.deleteSignupToken(token.id);
await tableRef.refresh();
toast.success(m.signup_token_deleted_successfully()); toast.success(m.signup_token_deleted_successfully());
// Refresh the tokens
if (onTokenDeleted) {
await onTokenDeleted();
}
} catch (e) { } catch (e) {
axiosErrorToast(e); axiosErrorToast(e);
} }
@@ -98,8 +90,69 @@
axiosErrorToast(err); axiosErrorToast(err);
}); });
} }
const columns: AdvancedTableColumn<SignupTokenDto>[] = [
{ label: m.token(), column: 'token', cell: TokenCell },
{ label: m.status(), key: 'status', cell: StatusCell },
{
label: m.usage(),
column: 'usageCount',
sortable: true,
cell: UsageCell
},
{
label: m.expires(),
column: 'expiresAt',
sortable: true,
value: (item) => formatDate(item.expiresAt)
},
{ label: 'Usage Limit', column: 'usageLimit' },
{
label: m.created(),
column: 'createdAt',
sortable: true,
hidden: true,
value: (item) => formatDate(item.createdAt)
}
];
const actions: CreateAdvancedTableActions<SignupTokenDto> = (_) => [
{
label: m.copy(),
icon: Copy,
onClick: (token) => copySignupLink(token)
},
{
label: m.delete(),
icon: Trash2,
variant: 'danger',
onClick: (token) => deleteToken(token)
}
];
</script> </script>
{#snippet TokenCell({ item }: { item: SignupTokenDto })}
<span class="font-mono text-xs">
{item.token.substring(0, 3)}...{item.token.substring(Math.max(item.token.length - 4, 0))}
</span>
{/snippet}
{#snippet StatusCell({ item }: { item: SignupTokenDto })}
{@const status = getTokenStatus(item)}
{@const statusBadge = getStatusBadge(status)}
<Badge class="rounded-full" variant={statusBadge.variant}>
{statusBadge.text}
</Badge>
{/snippet}
{#snippet UsageCell({ item }: { item: SignupTokenDto })}
<div class="flex items-center gap-1">
{item.usageCount}
{m.of()}
{item.usageLimit}
</div>
{/snippet}
<Dialog.Root {open} {onOpenChange}> <Dialog.Root {open} {onOpenChange}>
<Dialog.Content class="sm-min-w[500px] max-h-[90vh] min-w-[90vw] overflow-auto lg:min-w-[1000px]"> <Dialog.Content class="sm-min-w[500px] max-h-[90vh] min-w-[90vw] overflow-auto lg:min-w-[1000px]">
<Dialog.Header> <Dialog.Header>
@@ -111,70 +164,13 @@
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden">
<AdvancedTable <AdvancedTable
items={signupTokens} id="signup-token-list"
requestOptions={signupTokensRequestOptions}
withoutSearch={true} withoutSearch={true}
onRefresh={async (options) => { fetchCallback={userService.listSignupTokens}
const result = await userService.listSignupTokens(options); bind:this={tableRef}
signupTokens = result; {columns}
return result; {actions}
}} />
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> </div>
<Dialog.Footer class="mt-3"> <Dialog.Footer class="mt-3">
<Button onclick={() => (open = false)}> <Button onclick={() => (open = false)}>

View File

@@ -13,11 +13,9 @@
import { mode } from 'mode-watcher'; import { mode } from 'mode-watcher';
let { let {
open = $bindable(), open = $bindable()
onTokenCreated
}: { }: {
open: boolean; open: boolean;
onTokenCreated?: () => Promise<void>;
} = $props(); } = $props();
const userService = new UserService(); const userService = new UserService();
@@ -37,12 +35,11 @@
async function createSignupToken() { async function createSignupToken() {
try { try {
signupToken = await userService.createSignupToken(availableExpirations[selectedExpiration], usageLimit); signupToken = await userService.createSignupToken(
availableExpirations[selectedExpiration],
usageLimit
);
signupLink = `${page.url.origin}/st/${signupToken}`; signupLink = `${page.url.origin}/st/${signupToken}`;
if (onTokenCreated) {
await onTokenCreated();
}
} catch (e) { } catch (e) {
axiosErrorToast(e); axiosErrorToast(e);
} }

View File

@@ -0,0 +1,47 @@
<script lang="ts" generics="TData extends Record<string, any>">
import { buttonVariants } from '$lib/components/ui/button/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import { m } from '$lib/paraglide/messages';
import type { AdvancedTableColumn } from '$lib/types/advanced-table.type';
import Settings2Icon from '@lucide/svelte/icons/settings-2';
let {
columns,
selectedColumns = $bindable([])
}: { columns: AdvancedTableColumn<TData>[]; selectedColumns: string[] } = $props();
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger
class={buttonVariants({
variant: 'outline',
size: 'sm',
class: 'ml-auto h-8'
})}
>
<Settings2Icon />
<span class="hidden md:flex">{m.view()}</span>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>{m.toggle_columns()}</DropdownMenu.Label>
<DropdownMenu.Separator />
{#each columns as column (column)}
<DropdownMenu.CheckboxItem
closeOnSelect={false}
checked={selectedColumns.includes(column.column ?? column.key!)}
onCheckedChange={(v) => {
const key = column.column ?? column.key!;
if (v) {
selectedColumns = [...selectedColumns, key];
} else {
selectedColumns = selectedColumns.filter((c) => c !== key);
}
}}
>
{column.label}
</DropdownMenu.CheckboxItem>
{/each}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>

View File

@@ -0,0 +1,128 @@
<script lang="ts" generics="TData, TValue">
import { Badge } from '$lib/components/ui/badge/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import * as Command from '$lib/components/ui/command/index.js';
import * as Popover from '$lib/components/ui/popover/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
import { m } from '$lib/paraglide/messages';
import { cn } from '$lib/utils/style';
import CheckIcon from '@lucide/svelte/icons/check';
import ListFilterIcon from '@lucide/svelte/icons/list-filter';
import type { Component } from 'svelte';
let {
title,
options,
selectedValues = new Set<string | boolean>(),
showCheckboxes = true,
onChanged = (selected: Set<string | boolean>) => {}
}: {
title: string;
options: {
label: string;
value: string | boolean;
icon?: Component;
}[];
selectedValues?: Set<string | boolean>;
showCheckboxes?: boolean;
onChanged?: (selected: Set<string | boolean>) => void;
} = $props();
let open = $state(false);
</script>
<Popover.Root bind:open>
<Popover.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="outline"
size="sm"
class="h-8 border-dashed"
data-testid={`facet-${title.toLowerCase()}-trigger`}
>
<ListFilterIcon />
{title}
{#if selectedValues.size > 0}
<Separator orientation="vertical" class="mx-2 h-4" />
<Badge variant="secondary" class="rounded-sm px-1 font-normal lg:hidden">
{selectedValues.size}
</Badge>
<div class="hidden space-x-1 lg:flex">
{#if selectedValues.size > 2}
<Badge variant="secondary" class="rounded-sm px-1 font-normal">
Count: {selectedValues.size}
</Badge>
{:else}
{#each options.filter((opt) => selectedValues.has(opt.value)) as option (option)}
<Badge variant="secondary" class="rounded-sm px-1 font-normal">
{option.label}
</Badge>
{/each}
{/if}
</div>
{/if}
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content
class="w-[200px] p-0"
align="start"
data-testid={`facet-${title.toLowerCase()}-content`}
>
<Command.Root>
<Command.List>
<Command.Empty>{m.no_items_found()}</Command.Empty>
<Command.Group>
{#each options as option (option)}
{@const isSelected = selectedValues.has(option.value)}
<Command.Item
data-testid={`facet-${title.toLowerCase()}-option-${String(option.value)}`}
onSelect={() => {
if (isSelected) {
selectedValues = new Set([...selectedValues].filter((v) => v !== option.value));
} else {
selectedValues = new Set([...selectedValues, option.value]);
}
onChanged(selectedValues);
}}
>
{#if showCheckboxes}
<div
class={cn(
'border-primary mr-2 flex size-4 items-center justify-center rounded-sm border',
isSelected
? 'bg-primary text-primary-foreground'
: 'opacity-50 [&_svg]:invisible'
)}
>
<CheckIcon class="size-4" />
</div>
{/if}
{#if option.icon}
{@const Icon = option.icon}
<Icon class="text-muted-foreground" />
{/if}
<span>{option.label}</span>
</Command.Item>
{/each}
</Command.Group>
{#if selectedValues.size > 0}
<Command.Separator />
<Command.Group>
<Command.Item
onSelect={() => {
selectedValues = new Set();
onChanged(selectedValues);
open = false;
}}
>
{m.clear_filters()}
</Command.Item>
</Command.Group>
{/if}
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root>

View File

@@ -0,0 +1,67 @@
<script lang="ts" generics="TData extends Record<string, any>">
import { Input } from '$lib/components/ui/input/index.js';
import { m } from '$lib/paraglide/messages';
import type { AdvancedTableColumn } from '$lib/types/advanced-table.type';
import type { ListRequestOptions } from '$lib/types/list-request.type';
import { debounced } from '$lib/utils/debounce-util';
import AdvancedTableColumnSelection from './advanced-table-column-selection.svelte';
import AdvancedTableFilter from './advanced-table-filter.svelte';
let {
columns,
visibleColumns = $bindable(),
requestOptions,
searchValue = $bindable(),
withoutSearch = false,
onFilterChange,
refresh
}: {
columns: AdvancedTableColumn<TData>[];
visibleColumns: string[];
requestOptions: ListRequestOptions;
searchValue?: string;
withoutSearch?: boolean;
onFilterChange?: (selected: Set<string | boolean>, column: string) => void;
refresh: () => Promise<void>;
} = $props();
let filterableColumns = $derived(
columns
.filter((c) => c.filterableValues)
.map((c) => ({
name: c.label!,
column: c.column!,
options: c.filterableValues!
}))
);
const onSearch = debounced(async (search: string) => {
requestOptions.search = search;
await refresh();
searchValue = search;
}, 300);
</script>
<div class="mb-4 flex flex-wrap items-end justify-between gap-2">
<div class="flex flex-1 items-center gap-2 has-[>:nth-child(3)]:flex-wrap">
{#if !withoutSearch}
<Input
value={searchValue}
class="relative z-50 w-full sm:max-w-xs"
placeholder={m.search()}
type="text"
oninput={(e: Event) => onSearch((e.currentTarget as HTMLInputElement).value)}
/>
{/if}
{#each filterableColumns as col}
<AdvancedTableFilter
title={col.name}
options={col.options}
selectedValues={new Set(requestOptions.filters?.[col.column] || [])}
onChanged={(selected) => onFilterChange?.(selected, col.column)}
/>
{/each}
<AdvancedTableColumnSelection {columns} bind:selectedColumns={visibleColumns} />
</div>
</div>

View File

@@ -0,0 +1,368 @@
<script lang="ts" generics="T extends {id:string}">
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import * as Pagination from '$lib/components/ui/pagination';
import * as Select from '$lib/components/ui/select';
import * as Table from '$lib/components/ui/table/index.js';
import Empty from '$lib/icons/empty.svelte';
import { m } from '$lib/paraglide/messages';
import type {
AdvancedTableColumn,
CreateAdvancedTableActions
} from '$lib/types/advanced-table.type';
import type { ListRequestOptions, Paginated, SortRequest } from '$lib/types/list-request.type';
import { cn } from '$lib/utils/style';
import { ChevronDown, LucideEllipsis } from '@lucide/svelte';
import { PersistedState } from 'runed';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import Button, { buttonVariants } from '../ui/button/button.svelte';
import * as DropdownMenu from '../ui/dropdown-menu/index.js';
import { Skeleton } from '../ui/skeleton';
import AdvancedTableToolbar from './advanced-table-toolbar.svelte';
let {
id,
selectedIds = $bindable(),
withoutSearch = false,
selectionDisabled = false,
fetchCallback,
defaultSort,
columns,
actions
}: {
id: string;
selectedIds?: string[];
withoutSearch?: boolean;
selectionDisabled?: boolean;
fetchCallback: (requestOptions: ListRequestOptions) => Promise<Paginated<T>>;
defaultSort?: SortRequest;
columns: AdvancedTableColumn<T>[];
actions?: CreateAdvancedTableActions<T>;
} = $props();
let items: Paginated<T> | undefined = $state();
let searchValue = $state('');
const availablePageSizes: number[] = [20, 50, 100];
type TablePreferences = {
visibleColumns: string[];
paginationLimit: number;
sort?: SortRequest;
filters?: Record<string, (string | boolean)[]>;
length?: number;
};
const tablePreferences = new PersistedState<TablePreferences>(`table-${id}-preferences`, {
visibleColumns: columns.filter((c) => !c.hidden).map((c) => c.column ?? c.key!),
paginationLimit: 20,
filters: initializeFilters()
});
const requestOptions = $state<ListRequestOptions>({
sort: tablePreferences.current.sort ?? defaultSort,
pagination: { limit: tablePreferences.current.paginationLimit, page: 1 },
filters: tablePreferences.current.filters
});
let visibleColumns = $derived(
columns.filter(
(c) => tablePreferences.current.visibleColumns?.includes(c.column ?? c.key!) ?? []
)
);
onMount(async () => {
const urlParams = new URLSearchParams(window.location.search);
const page = parseInt(urlParams.get(`${id}-page`) ?? '') || undefined;
if (page) {
requestOptions.pagination!.page = page;
}
await refresh();
});
let allChecked = $derived.by(() => {
if (!selectedIds || !items || items.data.length === 0) return false;
for (const item of items!.data) {
if (!selectedIds.includes(item.id)) {
return false;
}
}
return true;
});
async function onAllCheck(checked: boolean) {
const pageIds = items!.data.map((item) => item.id);
const current = selectedIds ?? [];
if (checked) {
selectedIds = Array.from(new Set([...current, ...pageIds]));
} else {
selectedIds = current.filter((id) => !pageIds.includes(id));
}
}
async function onCheck(checked: boolean, id: string) {
const current = selectedIds ?? [];
if (checked) {
selectedIds = Array.from(new Set([...current, id]));
} else {
selectedIds = current.filter((selectedId) => selectedId !== id);
}
}
async function onPageChange(page: number) {
changePageState(page);
await refresh();
}
async function onPageSizeChange(size: number) {
requestOptions.pagination = { limit: size, page: 1 };
tablePreferences.current.paginationLimit = size;
await refresh();
}
async function onFilterChange(selected: Set<string | boolean>, column: string) {
requestOptions.filters = {
...requestOptions.filters,
[column]: Array.from(selected)
};
tablePreferences.current.filters = requestOptions.filters;
await refresh();
}
async function onSort(column?: string) {
if (!column) return;
const isSameColumn = requestOptions.sort?.column === column;
const nextDirection: 'asc' | 'desc' =
isSameColumn && requestOptions.sort?.direction === 'asc' ? 'desc' : 'asc';
requestOptions.sort = { column, direction: nextDirection };
tablePreferences.current.sort = requestOptions.sort;
await refresh();
}
function changePageState(page: number) {
const url = new URL(window.location.href);
url.searchParams.set(`${id}-page`, page.toString());
history.replaceState(history.state, '', url.toString());
requestOptions.pagination!.page = page;
}
function updateListLength(totalItems: number) {
tablePreferences.current.length =
totalItems > tablePreferences.current.paginationLimit
? tablePreferences.current.paginationLimit
: totalItems;
}
function initializeFilters() {
const filters: Record<string, (string | boolean)[]> = {};
columns.forEach((c) => {
if (c.filterableValues) {
filters[c.column!] = [];
}
});
return filters;
}
function getPrimaryAction(item: T) {
if (!actions) return null;
const availableActions = actions(item).filter((a) => a.primary);
return availableActions.length > 0 ? () => availableActions[0].onClick(item) : null;
}
export async function refresh() {
items = await fetchCallback(requestOptions);
changePageState(items.pagination.currentPage);
updateListLength(items.pagination.totalItems);
}
</script>
<AdvancedTableToolbar
{columns}
bind:visibleColumns={tablePreferences.current.visibleColumns}
{requestOptions}
{searchValue}
{withoutSearch}
{refresh}
{onFilterChange}
/>
{#if (items?.pagination.totalItems === 0 && searchValue === '') || tablePreferences.current.length === 0}
<div class="my-5 flex flex-col items-center">
<Empty class="text-muted-foreground h-20" />
<p class="text-muted-foreground mt-3 text-sm">{m.no_items_found()}</p>
</div>
{:else}
{#if !items}
<div>
{#each Array((tablePreferences.current.length || 10) + 1) as _}
<div>
<Skeleton class="mt-3 h-[45px] w-full rounded-lg" />
</div>
{/each}
</div>
{:else}
<div in:fade>
<Table.Root class="min-w-full table-auto overflow-x-auto">
<Table.Header>
<Table.Row>
{#if selectedIds}
<Table.Head class="w-12">
<Checkbox
disabled={selectionDisabled}
checked={allChecked}
onCheckedChange={(c: boolean) => onAllCheck(c as boolean)}
/>
</Table.Head>
{/if}
{#each visibleColumns as column}
<Table.Head class={cn(column.sortable && 'p-0')}>
{#if column.sortable}
<Button
variant="ghost"
class="h-12 w-full justify-start px-4 font-medium hover:bg-transparent"
onclick={() => onSort(column.column)}
>
<span class="flex items-center">
{column.label}
<ChevronDown
class={cn(
'ml-2 size-4 transition-all',
requestOptions.sort?.column === column.column
? requestOptions.sort?.direction === 'asc'
? 'rotate-180 opacity-100'
: 'opacity-100'
: 'opacity-0'
)}
/>
</span>
</Button>
{:else}
{column.label}
{/if}
</Table.Head>
{/each}
{#if actions}
<Table.Head align="right" class="w-12">
<span class="sr-only">{m.actions()}</span>
</Table.Head>
{/if}
</Table.Row>
</Table.Header>
<Table.Body>
{#each items.data as item}
<Table.Row
class={{
'bg-muted/20': selectedIds?.includes(item.id),
'cursor-pointer': getPrimaryAction(item)
}}
onclick={getPrimaryAction(item)}
>
{#if selectedIds}
<Table.Cell class="w-12">
<Checkbox
disabled={selectionDisabled}
checked={selectedIds.includes(item.id)}
onCheckedChange={(c: boolean) => onCheck(c, item.id)}
/>
</Table.Cell>
{/if}
{#each visibleColumns as column}
<Table.Cell>
{#if column.value}
{column.value(item)}
{:else if column.cell}
{@render column.cell({ item })}
{:else if column.column && typeof item[column.column] === 'boolean'}
{item[column.column] ? m.enabled() : m.disabled()}
{:else if column.column}
{item[column.column]}
{/if}
</Table.Cell>
{/each}
{#if actions}
<Table.Cell align="right" class="w-12 py-0">
<DropdownMenu.Root>
<DropdownMenu.Trigger
class={buttonVariants({ variant: 'ghost', size: 'icon' })}
>
<LucideEllipsis class="size-4" />
<span class="sr-only">{m.toggle_menu()}</span>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
{#each actions(item).filter((a) => !a.hidden) as action}
<DropdownMenu.Item
onclick={() => action.onClick(item)}
disabled={action.disabled}
class={action.variant === 'danger'
? 'text-red-500 focus:!text-red-700'
: ''}
>
{#if action.icon}
{@const Icon = action.icon}
<Icon class="mr-2 size-4" />
{/if}
{action.label}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
{/if}
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
{/if}
<div class="mt-5 flex flex-col-reverse items-center justify-between gap-3 sm:flex-row">
<div class="flex items-center space-x-2">
<p class="text-sm font-medium">{m.items_per_page()}</p>
<Select.Root
type="single"
value={items?.pagination.itemsPerPage.toString()}
onValueChange={(v) => onPageSizeChange(Number(v))}
>
<Select.Trigger class="h-9 w-[80px]">
{items?.pagination.itemsPerPage}
</Select.Trigger>
<Select.Content>
{#each availablePageSizes as size}
<Select.Item value={size.toString()}>{size}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<Pagination.Root
class="mx-0 w-auto"
count={items?.pagination.totalItems || 0}
perPage={items?.pagination.itemsPerPage}
{onPageChange}
page={items?.pagination.currentPage}
>
{#snippet children({ pages })}
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type !== 'ellipsis' && page.value != 0}
<Pagination.Item>
<Pagination.Link {page} isActive={items?.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}

View File

@@ -55,9 +55,13 @@
disabled, disabled,
isLoading = false, isLoading = false,
autofocus = false, autofocus = false,
onclick,
usePromiseLoading = false,
children, children,
...restProps ...restProps
}: ButtonProps = $props(); }: ButtonProps & {
usePromiseLoading?: boolean;
} = $props();
onMount(async () => { 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 // 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
@@ -66,6 +70,19 @@
setTimeout(() => ref?.focus(), 100); setTimeout(() => ref?.focus(), 100);
} }
}); });
async function handleOnClick(event: any) {
if (usePromiseLoading && onclick) {
isLoading = true;
try {
await onclick(event);
} finally {
isLoading = false;
}
} else {
onclick?.(event);
}
}
</script> </script>
{#if href} {#if href}
@@ -91,6 +108,7 @@
class={cn(buttonVariants({ variant, size }), className)} class={cn(buttonVariants({ variant, size }), className)}
{type} {type}
disabled={disabled || isLoading} disabled={disabled || isLoading}
onclick={handleOnClick}
{...restProps} {...restProps}
> >
{#if isLoading} {#if isLoading}

View File

@@ -0,0 +1,7 @@
import Root from "./skeleton.svelte";
export {
Root,
//
Root as Skeleton,
};

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils/style.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
</script>
<div
bind:this={ref}
data-slot="skeleton"
class={cn("bg-accent animate-pulse rounded-md", className)}
{...restProps}
></div>

View File

@@ -14,7 +14,7 @@
bind:this={ref} bind:this={ref}
data-slot="table-cell" data-slot="table-cell"
class={cn( class={cn(
'p-4 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', 'py-3 px-4 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className className
)} )}
{...restProps} {...restProps}

View File

@@ -1,11 +1,9 @@
<script lang="ts"> <script lang="ts">
import AdvancedTable from '$lib/components/advanced-table.svelte'; import AdvancedTable from '$lib/components/table/advanced-table.svelte';
import * as Table from '$lib/components/ui/table';
import { m } from '$lib/paraglide/messages'; import { m } from '$lib/paraglide/messages';
import UserGroupService from '$lib/services/user-group-service'; import UserGroupService from '$lib/services/user-group-service';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type { AdvancedTableColumn } from '$lib/types/advanced-table.type';
import type { UserGroup } from '$lib/types/user-group.type'; import type { UserGroupWithUserCount } from '$lib/types/user-group.type';
import { onMount } from 'svelte';
let { let {
selectionDisabled = false, selectionDisabled = false,
@@ -17,30 +15,27 @@
const userGroupService = new UserGroupService(); const userGroupService = new UserGroupService();
let groups: Paginated<UserGroup> | undefined = $state(); const columns: AdvancedTableColumn<UserGroupWithUserCount>[] = [
let requestOptions: SearchPaginationSortRequest = $state({ { label: 'ID', column: 'id', hidden: true },
sort: { { label: m.friendly_name(), column: 'friendlyName', sortable: true },
column: 'friendlyName', { label: m.name(), column: 'name', sortable: true },
direction: 'asc' { label: m.user_count(), column: 'userCount', sortable: true },
} {
}); label: m.created(),
column: 'createdAt',
onMount(async () => { sortable: true,
groups = await userGroupService.list(requestOptions); hidden: true,
}); value: (item) => new Date(item.createdAt).toLocaleString()
},
{ label: m.ldap_id(), column: 'ldapId', hidden: true }
];
</script> </script>
{#if groups} <AdvancedTable
<AdvancedTable id="user-group-selection"
items={groups} fetchCallback={userGroupService.list}
{requestOptions} defaultSort={{ column: 'friendlyName', direction: 'asc' }}
onRefresh={async (o) => (groups = await userGroupService.list(o))} bind:selectedIds={selectedGroupIds}
columns={[{ label: m.name(), sortColumn: 'friendlyName' }]} {selectionDisabled}
bind:selectedIds={selectedGroupIds} {columns}
{selectionDisabled} />
>
{#snippet rows({ item })}
<Table.Cell>{item.friendlyName}</Table.Cell>
{/snippet}
</AdvancedTable>
{/if}

View File

@@ -1,21 +1,19 @@
import type { ApiKey, ApiKeyCreate, ApiKeyResponse } from '$lib/types/api-key.type'; import type { ApiKey, ApiKeyCreate, ApiKeyResponse } from '$lib/types/api-key.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
import APIService from './api-service'; import APIService from './api-service';
export default class ApiKeyService extends APIService { export default class ApiKeyService extends APIService {
async list(options?: SearchPaginationSortRequest) { list = async (options?: ListRequestOptions) => {
const res = await this.api.get('/api-keys', { const res = await this.api.get('/api-keys', { params: options });
params: options
});
return res.data as Paginated<ApiKey>; return res.data as Paginated<ApiKey>;
} };
async create(data: ApiKeyCreate): Promise<ApiKeyResponse> { create = async (data: ApiKeyCreate): Promise<ApiKeyResponse> => {
const res = await this.api.post('/api-keys', data); const res = await this.api.post('/api-keys', data);
return res.data as ApiKeyResponse; return res.data as ApiKeyResponse;
} };
async revoke(id: string): Promise<void> { revoke = async (id: string): Promise<void> => {
await this.api.delete(`/api-keys/${id}`); await this.api.delete(`/api-keys/${id}`);
} };
} }

View File

@@ -1,15 +1,13 @@
import axios from 'axios'; import axios from 'axios';
abstract class APIService { abstract class APIService {
api = axios.create({ protected api = axios.create({ baseURL: '/api' });
baseURL: '/api'
});
constructor() { constructor() {
if (typeof process !== 'undefined' && process?.env?.DEVELOPMENT_BACKEND_URL) { if (typeof process !== 'undefined' && process?.env?.DEVELOPMENT_BACKEND_URL) {
this.api.defaults.baseURL = process.env.DEVELOPMENT_BACKEND_URL; this.api.defaults.baseURL = process.env.DEVELOPMENT_BACKEND_URL;
} }
} }
} }
export default APIService; export default APIService;

View File

@@ -1,94 +1,109 @@
import userStore from '$lib/stores/user-store';
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 {
cachedApplicationLogo,
cachedBackgroundImage,
cachedDefaultProfilePicture,
cachedProfilePicture
} from '$lib/utils/cached-image-util';
import { get } from 'svelte/store';
import APIService from './api-service'; import APIService from './api-service';
export default class AppConfigService extends APIService { export default class AppConfigService extends APIService {
async list(showAll = false) { list = async (showAll = false) => {
let url = '/application-configuration'; let url = '/application-configuration';
if (showAll) { if (showAll) url += '/all';
url += '/all';
}
const { data } = await this.api.get<AppConfigRawResponse>(url); const { data } = await this.api.get<AppConfigRawResponse>(url);
return this.parseConfigList(data); return parseConfigList(data);
} };
async update(appConfig: AllAppConfig) { update = async (appConfig: AllAppConfig) => {
// Convert all values to string, stringifying JSON where needed // Convert all values to string, stringifying JSON where needed
const appConfigConvertedToString: Record<string, string> = {}; const appConfigConvertedToString: Record<string, string> = {};
for (const key in appConfig) { for (const key in appConfig) {
const value = (appConfig as any)[key]; const value = (appConfig as any)[key];
if (typeof value === 'object' && value !== null) { appConfigConvertedToString[key] =
appConfigConvertedToString[key] = JSON.stringify(value); typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
} else {
appConfigConvertedToString[key] = String(value);
}
} }
const res = await this.api.put('/application-configuration', appConfigConvertedToString); const res = await this.api.put('/application-configuration', appConfigConvertedToString);
return this.parseConfigList(res.data); return parseConfigList(res.data);
} };
async updateFavicon(favicon: File) { updateFavicon = async (favicon: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', favicon!); formData.append('file', favicon);
await this.api.put(`/application-images/favicon`, formData); await this.api.put(`/application-images/favicon`, formData);
} };
async updateLogo(logo: File, light = true) { updateLogo = async (logo: File, light = true) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', logo!); formData.append('file', logo);
await this.api.put(`/application-images/logo`, formData, { await this.api.put(`/application-images/logo`, formData, {
params: { light } params: { light }
}); });
cachedApplicationLogo.bustCache(light); cachedApplicationLogo.bustCache(light);
} };
async updateBackgroundImage(backgroundImage: File) { updateDefaultProfilePicture = async (defaultProfilePicture: File) => {
const formData = new FormData();
formData.append('file', defaultProfilePicture);
await this.api.put(`/application-images/default-profile-picture`, formData);
cachedDefaultProfilePicture.bustCache();
};
updateBackgroundImage = async (backgroundImage: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', backgroundImage!); formData.append('file', backgroundImage!);
await this.api.put(`/application-images/background`, formData); await this.api.put(`/application-images/background`, formData);
cachedBackgroundImage.bustCache(); cachedBackgroundImage.bustCache();
} };
async sendTestEmail() { deleteDefaultProfilePicture = async () => {
await this.api.delete('/application-images/default-profile-picture');
cachedDefaultProfilePicture.bustCache();
cachedProfilePicture.bustCache(get(userStore)!.id);
};
sendTestEmail = async () => {
await this.api.post('/application-configuration/test-email'); await this.api.post('/application-configuration/test-email');
} };
async syncLdap() { syncLdap = async () => {
await this.api.post('/application-configuration/sync-ldap'); await this.api.post('/application-configuration/sync-ldap');
} };
}
private parseConfigList(data: AppConfigRawResponse) { function parseConfigList(data: AppConfigRawResponse) {
const appConfig: Partial<AllAppConfig> = {}; const appConfig: Partial<AllAppConfig> = {};
data.forEach(({ key, value }) => { data.forEach(({ key, value }) => {
(appConfig as any)[key] = this.parseValue(value); (appConfig as any)[key] = parseValue(value);
}); });
return appConfig as AllAppConfig; return appConfig as AllAppConfig;
} }
private parseValue(value: string) { function parseValue(value: string) {
// Try to parse JSON first // Try to parse JSON first
try { try {
const parsed = JSON.parse(value); const parsed = JSON.parse(value);
if (typeof parsed === 'object' && parsed !== null) { if (typeof parsed === 'object' && parsed !== null) {
return parsed; return parsed;
}
value = String(parsed);
} catch {}
// Handle rest of the types
if (value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else if (/^-?\d+(\.\d+)?$/.test(value)) {
return parseFloat(value);
} else {
return value;
} }
value = String(parsed);
} catch {}
// Handle rest of the types
if (value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else if (/^-?\d+(\.\d+)?$/.test(value)) {
return parseFloat(value);
} else {
return value;
} }
} }

View File

@@ -1,34 +1,25 @@
import type { AuditLog, AuditLogFilter } from '$lib/types/audit-log.type'; import type { AuditLog } from '$lib/types/audit-log.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
import APIService from './api-service'; import APIService from './api-service';
class AuditLogService extends APIService { export default class AuditLogService extends APIService {
async list(options?: SearchPaginationSortRequest) { list = async (options?: ListRequestOptions) => {
const res = await this.api.get('/audit-logs', { const res = await this.api.get('/audit-logs', { params: options });
params: options
});
return res.data as Paginated<AuditLog>; return res.data as Paginated<AuditLog>;
} };
async listAllLogs(options?: SearchPaginationSortRequest, filters?: AuditLogFilter) { listAllLogs = async (options?: ListRequestOptions) => {
const res = await this.api.get('/audit-logs/all', { const res = await this.api.get('/audit-logs/all', { params: options });
params: {
...options,
filters
}
});
return res.data as Paginated<AuditLog>; return res.data as Paginated<AuditLog>;
} };
async listClientNames() { listClientNames = async () => {
const res = await this.api.get<string[]>('/audit-logs/filters/client-names'); const res = await this.api.get<string[]>('/audit-logs/filters/client-names');
return res.data; return res.data;
} };
async listUsers() { listUsers = async () => {
const res = await this.api.get<Record<string, string>>('/audit-logs/filters/users'); const res = await this.api.get<Record<string, string>>('/audit-logs/filters/users');
return res.data; return res.data;
} };
} }
export default AuditLogService;

View File

@@ -2,18 +2,18 @@ import type { CustomClaim } from '$lib/types/custom-claim.type';
import APIService from './api-service'; import APIService from './api-service';
export default class CustomClaimService extends APIService { export default class CustomClaimService extends APIService {
async getSuggestions() { getSuggestions = async () => {
const res = await this.api.get('/custom-claims/suggestions'); const res = await this.api.get('/custom-claims/suggestions');
return res.data as string[]; return res.data as string[];
} };
async updateUserCustomClaims(userId: string, claims: CustomClaim[]) { updateUserCustomClaims = async (userId: string, claims: CustomClaim[]) => {
const res = await this.api.put(`/custom-claims/user/${userId}`, claims); const res = await this.api.put(`/custom-claims/user/${userId}`, claims);
return res.data as CustomClaim[]; return res.data as CustomClaim[];
} };
async updateUserGroupCustomClaims(userGroupId: string, claims: CustomClaim[]) { updateUserGroupCustomClaims = async (userGroupId: string, claims: CustomClaim[]) => {
const res = await this.api.put(`/custom-claims/user-group/${userGroupId}`, claims); const res = await this.api.put(`/custom-claims/user-group/${userGroupId}`, claims);
return res.data as CustomClaim[]; return res.data as CustomClaim[];
} };
} }

View File

@@ -1,3 +1,4 @@
import type { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
import type { import type {
AccessibleOidcClient, AccessibleOidcClient,
AuthorizeResponse, AuthorizeResponse,
@@ -9,12 +10,11 @@ import type {
OidcClientWithAllowedUserGroupsCount, OidcClientWithAllowedUserGroupsCount,
OidcDeviceCodeInfo OidcDeviceCodeInfo
} from '$lib/types/oidc.type'; } from '$lib/types/oidc.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util'; 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 {
async authorize( authorize = async (
clientId: string, clientId: string,
scope: string, scope: string,
callbackURL: string, callbackURL: string,
@@ -22,7 +22,7 @@ class OidcService extends APIService {
codeChallenge?: string, codeChallenge?: string,
codeChallengeMethod?: string, codeChallengeMethod?: string,
reauthenticationToken?: string reauthenticationToken?: string
) { ) => {
const res = await this.api.post('/oidc/authorize', { const res = await this.api.post('/oidc/authorize', {
scope, scope,
nonce, nonce,
@@ -34,101 +34,99 @@ class OidcService extends APIService {
}); });
return res.data as AuthorizeResponse; return res.data as AuthorizeResponse;
} };
async isAuthorizationRequired(clientId: string, scope: string) { isAuthorizationRequired = async (clientId: string, scope: string) => {
const res = await this.api.post('/oidc/authorization-required', { const res = await this.api.post('/oidc/authorization-required', {
scope, scope,
clientId clientId
}); });
return res.data.authorizationRequired as boolean; return res.data.authorizationRequired as boolean;
} };
async listClients(options?: SearchPaginationSortRequest) { listClients = async (options?: ListRequestOptions) => {
const res = await this.api.get('/oidc/clients', { const res = await this.api.get('/oidc/clients', {
params: options params: options
}); });
return res.data as Paginated<OidcClientWithAllowedUserGroupsCount>; return res.data as Paginated<OidcClientWithAllowedUserGroupsCount>;
} };
async createClient(client: OidcClientCreate) { createClient = async (client: OidcClientCreate) =>
return (await this.api.post('/oidc/clients', client)).data as OidcClient; (await this.api.post('/oidc/clients', client)).data as OidcClient;
}
async removeClient(id: string) { removeClient = async (id: string) => {
await this.api.delete(`/oidc/clients/${id}`); await this.api.delete(`/oidc/clients/${id}`);
} };
async getClient(id: string) { getClient = async (id: string) =>
return (await this.api.get(`/oidc/clients/${id}`)).data as OidcClientWithAllowedUserGroups; (await this.api.get(`/oidc/clients/${id}`)).data as OidcClientWithAllowedUserGroups;
}
async getClientMetaData(id: string) { getClientMetaData = async (id: string) =>
return (await this.api.get(`/oidc/clients/${id}/meta`)).data as OidcClientMetaData; (await this.api.get(`/oidc/clients/${id}/meta`)).data as OidcClientMetaData;
}
async updateClient(id: string, client: OidcClientUpdate) { updateClient = async (id: string, client: OidcClientUpdate) =>
return (await this.api.put(`/oidc/clients/${id}`, client)).data as OidcClient; (await this.api.put(`/oidc/clients/${id}`, client)).data as OidcClient;
}
async updateClientLogo(client: OidcClient, image: File | null) { updateClientLogo = async (client: OidcClient, image: File | null, light: boolean = true) => {
if (client.hasLogo && !image) { const hasLogo = light ? client.hasLogo : client.hasDarkLogo;
await this.removeClientLogo(client.id);
if (hasLogo && !image) {
await this.removeClientLogo(client.id, light);
return; return;
} }
if (!client.hasLogo && !image) { if (!hasLogo && !image) {
return; return;
} }
const formData = new FormData(); const formData = new FormData();
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); params: { light }
} });
cachedOidcClientLogo.bustCache(client.id, light);
};
async removeClientLogo(id: string) { removeClientLogo = async (id: string, light: boolean = true) => {
await this.api.delete(`/oidc/clients/${id}/logo`); await this.api.delete(`/oidc/clients/${id}/logo`, {
cachedOidcClientLogo.bustCache(id); params: { light }
} });
cachedOidcClientLogo.bustCache(id, light);
};
async createClientSecret(id: string) { createClientSecret = async (id: string) =>
return (await this.api.post(`/oidc/clients/${id}/secret`)).data.secret as string; (await this.api.post(`/oidc/clients/${id}/secret`)).data.secret as string;
}
async updateAllowedUserGroups(id: string, userGroupIds: string[]) { updateAllowedUserGroups = async (id: string, userGroupIds: string[]) => {
const res = await this.api.put(`/oidc/clients/${id}/allowed-user-groups`, { userGroupIds }); const res = await this.api.put(`/oidc/clients/${id}/allowed-user-groups`, { userGroupIds });
return res.data as OidcClientWithAllowedUserGroups; return res.data as OidcClientWithAllowedUserGroups;
} };
async verifyDeviceCode(userCode: string) { verifyDeviceCode = async (userCode: string) => {
return await this.api.post(`/oidc/device/verify?code=${userCode}`); return await this.api.post(`/oidc/device/verify?code=${userCode}`);
} };
async getDeviceCodeInfo(userCode: string): Promise<OidcDeviceCodeInfo> { getDeviceCodeInfo = async (userCode: string): Promise<OidcDeviceCodeInfo> => {
const response = await this.api.get(`/oidc/device/info?code=${userCode}`); const response = await this.api.get(`/oidc/device/info?code=${userCode}`);
return response.data; return response.data;
} };
async getClientPreview(id: string, userId: string, scopes: string) { getClientPreview = async (id: string, userId: string, scopes: string) => {
const response = await this.api.get(`/oidc/clients/${id}/preview/${userId}`, { const response = await this.api.get(`/oidc/clients/${id}/preview/${userId}`, {
params: { scopes } params: { scopes }
}); });
return response.data; return response.data;
} };
async listOwnAccessibleClients(options?: SearchPaginationSortRequest) {
const res = await this.api.get('/oidc/users/me/clients', {
params: options
});
listOwnAccessibleClients = async (options?: ListRequestOptions) => {
const res = await this.api.get('/oidc/users/me/clients', { params: options });
return res.data as Paginated<AccessibleOidcClient>; return res.data as Paginated<AccessibleOidcClient>;
} };
async revokeOwnAuthorizedClient(clientId: string) { revokeOwnAuthorizedClient = async (clientId: string) => {
await this.api.delete(`/oidc/users/me/authorized-clients/${clientId}`); await this.api.delete(`/oidc/users/me/authorized-clients/${clientId}`);
} };
} }
export default OidcService; export default OidcService;

View File

@@ -1,4 +1,4 @@
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type'; import type { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
import type { import type {
UserGroupCreate, UserGroupCreate,
UserGroupWithUserCount, UserGroupWithUserCount,
@@ -7,34 +7,32 @@ import type {
import APIService from './api-service'; import APIService from './api-service';
export default class UserGroupService extends APIService { export default class UserGroupService extends APIService {
async list(options?: SearchPaginationSortRequest) { list = async (options?: ListRequestOptions) => {
const res = await this.api.get('/user-groups', { const res = await this.api.get('/user-groups', { params: options });
params: options
});
return res.data as Paginated<UserGroupWithUserCount>; return res.data as Paginated<UserGroupWithUserCount>;
} };
async get(id: string) { get = async (id: string) => {
const res = await this.api.get(`/user-groups/${id}`); const res = await this.api.get(`/user-groups/${id}`);
return res.data as UserGroupWithUsers; return res.data as UserGroupWithUsers;
} };
async create(user: UserGroupCreate) { create = async (user: UserGroupCreate) => {
const res = await this.api.post('/user-groups', user); const res = await this.api.post('/user-groups', user);
return res.data as UserGroupWithUsers; return res.data as UserGroupWithUsers;
} };
async update(id: string, user: UserGroupCreate) { update = async (id: string, user: UserGroupCreate) => {
const res = await this.api.put(`/user-groups/${id}`, user); const res = await this.api.put(`/user-groups/${id}`, user);
return res.data as UserGroupWithUsers; return res.data as UserGroupWithUsers;
} };
async remove(id: string) { remove = async (id: string) => {
await this.api.delete(`/user-groups/${id}`); await this.api.delete(`/user-groups/${id}`);
} };
async updateUsers(id: string, userIds: string[]) { updateUsers = async (id: string, userIds: string[]) => {
const res = await this.api.put(`/user-groups/${id}/users`, { userIds }); const res = await this.api.put(`/user-groups/${id}/users`, { userIds });
return res.data as UserGroupWithUsers; return res.data as UserGroupWithUsers;
} };
} }

View File

@@ -1,5 +1,5 @@
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 { ListRequestOptions, Paginated } from '$lib/types/list-request.type';
import type { SignupTokenDto } from '$lib/types/signup-token.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, UserSignUp } from '$lib/types/user.type'; import type { User, UserCreate, UserSignUp } from '$lib/types/user.type';
@@ -8,125 +8,113 @@ import { get } from 'svelte/store';
import APIService from './api-service'; import APIService from './api-service';
export default class UserService extends APIService { export default class UserService extends APIService {
async list(options?: SearchPaginationSortRequest) { list = async (options?: ListRequestOptions) => {
const res = await this.api.get('/users', { const res = await this.api.get('/users', { params: options });
params: options
});
return res.data as Paginated<User>; return res.data as Paginated<User>;
} };
async get(id: string) { get = async (id: string) => {
const res = await this.api.get(`/users/${id}`); const res = await this.api.get(`/users/${id}`);
return res.data as User; return res.data as User;
} };
async getCurrent() { getCurrent = async () => {
const res = await this.api.get('/users/me'); const res = await this.api.get('/users/me');
return res.data as User; return res.data as User;
} };
async create(user: UserCreate) { create = async (user: UserCreate) => {
const res = await this.api.post('/users', user); const res = await this.api.post('/users', user);
return res.data as User; return res.data as User;
} };
async getUserGroups(userId: string) { getUserGroups = async (userId: string) => {
const res = await this.api.get(`/users/${userId}/groups`); const res = await this.api.get(`/users/${userId}/groups`);
return res.data as UserGroup[]; return res.data as UserGroup[];
} };
async update(id: string, user: UserCreate) { update = async (id: string, user: UserCreate) => {
const res = await this.api.put(`/users/${id}`, user); const res = await this.api.put(`/users/${id}`, user);
return res.data as User; return res.data as User;
} };
async updateCurrent(user: UserCreate) { updateCurrent = async (user: UserCreate) => {
const res = await this.api.put('/users/me', user); const res = await this.api.put('/users/me', user);
return res.data as User; return res.data as User;
} };
async remove(id: string) { remove = async (id: string) => {
await this.api.delete(`/users/${id}`); await this.api.delete(`/users/${id}`);
} };
async updateProfilePicture(userId: string, image: File) { updateProfilePicture = async (userId: string, image: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', image!); formData.append('file', image!);
await this.api.put(`/users/${userId}/profile-picture`, formData); await this.api.put(`/users/${userId}/profile-picture`, formData);
cachedProfilePicture.bustCache(userId); cachedProfilePicture.bustCache(userId);
} };
async updateCurrentUsersProfilePicture(image: File) { updateCurrentUsersProfilePicture = async (image: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', image!); formData.append('file', image!);
await this.api.put('/users/me/profile-picture', formData); await this.api.put('/users/me/profile-picture', formData);
cachedProfilePicture.bustCache(get(userStore)!.id); cachedProfilePicture.bustCache(get(userStore)!.id);
} };
async resetCurrentUserProfilePicture() { resetCurrentUserProfilePicture = async () => {
await this.api.delete(`/users/me/profile-picture`); await this.api.delete(`/users/me/profile-picture`);
cachedProfilePicture.bustCache(get(userStore)!.id); cachedProfilePicture.bustCache(get(userStore)!.id);
} };
async resetProfilePicture(userId: string) { resetProfilePicture = async (userId: string) => {
await this.api.delete(`/users/${userId}/profile-picture`); await this.api.delete(`/users/${userId}/profile-picture`);
cachedProfilePicture.bustCache(userId); cachedProfilePicture.bustCache(userId);
} };
async createOneTimeAccessToken(userId: string = 'me', ttl?: string|number) { createOneTimeAccessToken = async (userId: string = 'me', ttl?: string | number) => {
const res = await this.api.post(`/users/${userId}/one-time-access-token`, { const res = await this.api.post(`/users/${userId}/one-time-access-token`, { userId, ttl });
userId,
ttl,
});
return res.data.token; return res.data.token;
} };
async createSignupToken(ttl: string|number, usageLimit: number) { createSignupToken = async (ttl: string | number, usageLimit: number) => {
const res = await this.api.post(`/signup-tokens`, { const res = await this.api.post(`/signup-tokens`, { ttl, usageLimit });
ttl,
usageLimit
});
return res.data.token; return res.data.token;
} };
async exchangeOneTimeAccessToken(token: string) { exchangeOneTimeAccessToken = async (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;
} };
async requestOneTimeAccessEmailAsUnauthenticatedUser(email: string, redirectPath?: string) { requestOneTimeAccessEmailAsUnauthenticatedUser = async (email: string, redirectPath?: string) => {
await this.api.post('/one-time-access-email', { email, redirectPath }); await this.api.post('/one-time-access-email', { email, redirectPath });
} };
async requestOneTimeAccessEmailAsAdmin(userId: string, ttl: string|number) { requestOneTimeAccessEmailAsAdmin = async (userId: string, ttl: string | number) => {
await this.api.post(`/users/${userId}/one-time-access-email`, { ttl }); await this.api.post(`/users/${userId}/one-time-access-email`, { ttl });
} };
async updateUserGroups(id: string, userGroupIds: string[]) { updateUserGroups = async (id: string, userGroupIds: string[]) => {
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) { signup = async (data: UserSignUp) => {
const res = await this.api.post(`/signup`, data); const res = await this.api.post(`/signup`, data);
return res.data as User; return res.data as User;
} };
async signupInitialUser(data: UserSignUp) { signupInitialUser = async (data: UserSignUp) => {
const res = await this.api.post(`/signup/setup`, data); const res = await this.api.post(`/signup/setup`, data);
return res.data as User; return res.data as User;
} };
async listSignupTokens(options?: SearchPaginationSortRequest) { listSignupTokens = async (options?: ListRequestOptions) => {
const res = await this.api.get('/signup-tokens', { const res = await this.api.get('/signup-tokens', { params: options });
params: options
});
return res.data as Paginated<SignupTokenDto>; return res.data as Paginated<SignupTokenDto>;
} };
async deleteSignupToken(tokenId: string) { deleteSignupToken = async (tokenId: string) => {
await this.api.delete(`/signup-tokens/${tokenId}`); await this.api.delete(`/signup-tokens/${tokenId}`);
} };
} }

View File

@@ -1,21 +1,13 @@
import { version as currentVersion } from '$app/environment'; import { version as currentVersion } from '$app/environment';
import axios from 'axios'; import APIService from './api-service';
async function getNewestVersion() { export default class VersionService extends APIService {
const response = await axios getNewestVersion = async () => {
.get('/api/version/latest', { const response = await this.api
timeout: 2000 .get('/version/latest', { timeout: 2000 })
}) .then((res) => res.data);
.then((res) => res.data); return response.latestVersion;
};
return response.latestVersion; getCurrentVersion = () => currentVersion;
} }
function getCurrentVersion() {
return currentVersion;
}
export default {
getNewestVersion,
getCurrentVersion,
};

View File

@@ -3,45 +3,36 @@ import type { User } from '$lib/types/user.type';
import APIService from './api-service'; import APIService from './api-service';
import userStore from '$lib/stores/user-store'; import userStore from '$lib/stores/user-store';
import type { AuthenticationResponseJSON, RegistrationResponseJSON } from '@simplewebauthn/browser'; import type { AuthenticationResponseJSON, RegistrationResponseJSON } from '@simplewebauthn/browser';
class WebAuthnService extends APIService { class WebAuthnService extends APIService {
async getRegistrationOptions() { getRegistrationOptions = async () => (await this.api.get(`/webauthn/register/start`)).data;
return (await this.api.get(`/webauthn/register/start`)).data;
}
async finishRegistration(body: RegistrationResponseJSON) { finishRegistration = async (body: RegistrationResponseJSON) =>
return (await this.api.post(`/webauthn/register/finish`, body)).data as Passkey; (await this.api.post(`/webauthn/register/finish`, body)).data as Passkey;
}
async getLoginOptions() { getLoginOptions = async () => (await this.api.get(`/webauthn/login/start`)).data;
return (await this.api.get(`/webauthn/login/start`)).data;
}
async finishLogin(body: AuthenticationResponseJSON) { finishLogin = async (body: AuthenticationResponseJSON) =>
return (await this.api.post(`/webauthn/login/finish`, body)).data as User; (await this.api.post(`/webauthn/login/finish`, body)).data as User;
}
async logout() { logout = async () => {
await this.api.post(`/webauthn/logout`); await this.api.post(`/webauthn/logout`);
userStore.clearUser(); userStore.clearUser();
} };
async listCredentials() { listCredentials = async () => (await this.api.get(`/webauthn/credentials`)).data as Passkey[];
return (await this.api.get(`/webauthn/credentials`)).data as Passkey[];
}
async removeCredential(id: string) { removeCredential = async (id: string) => {
await this.api.delete(`/webauthn/credentials/${id}`); await this.api.delete(`/webauthn/credentials/${id}`);
} };
async updateCredentialName(id: string, name: string) { updateCredentialName = async (id: string, name: string) => {
await this.api.patch(`/webauthn/credentials/${id}`, { name }); await this.api.patch(`/webauthn/credentials/${id}`, { name });
} };
async reauthenticate(body?: AuthenticationResponseJSON) { reauthenticate = async (body?: AuthenticationResponseJSON) => {
const res = await this.api.post('/webauthn/reauthenticate', body); const res = await this.api.post('/webauthn/reauthenticate', body);
return res.data.reauthenticationToken as string; return res.data.reauthenticationToken as string;
} };
} }
export default WebAuthnService; export default WebAuthnService;

View File

@@ -0,0 +1,27 @@
import type { Component, Snippet } from 'svelte';
export type AdvancedTableColumn<T extends Record<string, any>> = {
label: string;
column?: keyof T & string;
key?: string;
value?: (item: T) => string | number | boolean | undefined;
cell?: Snippet<[{ item: T }]>;
sortable?: boolean;
filterableValues?: {
label: string;
value: string | boolean;
icon?: Component;
}[];
hidden?: boolean;
};
export type CreateAdvancedTableActions<T extends Record<string, any>> = (item: T) => AdvancedTableAction<T>[];
export type AdvancedTableAction<T> = {
label: string;
icon?: Component;
variant?: 'primary' | 'secondary' | 'danger' | 'outline' | 'ghost';
onClick: (item: T) => void;
hidden?: boolean;
primary?: boolean;
disabled?: boolean;
};

View File

@@ -8,13 +8,11 @@ export type SortRequest = {
direction: 'asc' | 'desc'; direction: 'asc' | 'desc';
}; };
export type FilterMap = Record<string, string>; export type ListRequestOptions = {
export type SearchPaginationSortRequest = {
search?: string; search?: string;
pagination?: PaginationRequest; pagination?: PaginationRequest;
sort?: SortRequest; sort?: SortRequest;
filters?: FilterMap; filters?: Record<string, (string | boolean)[]>;
}; };
export type PaginationResponse = { export type PaginationResponse = {

View File

@@ -4,6 +4,7 @@ export type OidcClientMetaData = {
id: string; id: string;
name: string; name: string;
hasLogo: boolean; hasLogo: boolean;
hasDarkLogo: boolean;
requiresReauthentication: boolean; requiresReauthentication: boolean;
launchURL?: string; launchURL?: string;
}; };
@@ -37,17 +38,20 @@ export type OidcClientWithAllowedUserGroupsCount = OidcClient & {
allowedUserGroupsCount: number; allowedUserGroupsCount: number;
}; };
export type OidcClientUpdate = Omit<OidcClient, 'id' | 'logoURL' | 'hasLogo'>; export type OidcClientUpdate = Omit<OidcClient, 'id' | 'logoURL' | 'hasLogo' | 'hasDarkLogo'>;
export type OidcClientCreate = OidcClientUpdate & { export type OidcClientCreate = OidcClientUpdate & {
id?: string; id?: string;
}; };
export type OidcClientUpdateWithLogo = OidcClientUpdate & { export type OidcClientUpdateWithLogo = OidcClientUpdate & {
logo: File | null | undefined; logo: File | null | undefined;
darkLogo: File | null | undefined;
}; };
export type OidcClientCreateWithLogo = OidcClientCreate & { export type OidcClientCreateWithLogo = OidcClientCreate & {
logo?: File | null; logo?: File | null;
logoUrl?: string; logoUrl?: string;
darkLogo?: File | null;
darkLogoUrl?: string;
}; };
export type OidcDeviceCodeInfo = { export type OidcDeviceCodeInfo = {

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