Compare commits

...

40 Commits

Author SHA1 Message Date
dependabot[bot]
5239e1400a Bump the prod-patch-updates group across 1 directory with 11 updates
Bumps the prod-patch-updates group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@headlessui/react](https://github.com/tailwindlabs/headlessui/tree/HEAD/packages/@headlessui-react) | `2.2.9` | `2.2.10` |
| [@react-email/components](https://github.com/resend/react-email/tree/HEAD/packages/components) | `1.0.8` | `1.0.12` |
| [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `2.0.4` | `2.0.8` |
| [@react-email/tailwind](https://github.com/resend/react-email/tree/HEAD/packages/tailwind) | `2.0.5` | `2.0.7` |
| [ioredis](https://github.com/luin/ioredis) | `5.10.0` | `5.10.1` |
| [maxmind](https://github.com/runk/node-maxmind) | `5.0.5` | `5.0.6` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.5` | `8.0.7` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.4` | `19.2.5` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.4` | `19.2.5` |
| [use-debounce](https://github.com/xnimorz/use-debounce) | `10.1.0` | `10.1.1` |
| [yaml](https://github.com/eemeli/yaml) | `2.8.3` | `2.8.4` |



Updates `@headlessui/react` from 2.2.9 to 2.2.10
- [Release notes](https://github.com/tailwindlabs/headlessui/releases)
- [Changelog](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-react/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/headlessui/commits/@headlessui/react@v2.2.10/packages/@headlessui-react)

Updates `@react-email/components` from 1.0.8 to 1.0.12
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/@react-email/components@1.0.12/packages/components/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/@react-email/components@1.0.12/packages/components)

Updates `@react-email/render` from 2.0.4 to 2.0.8
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/canary/packages/render/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/@react-email/render@2.0.8/packages/render)

Updates `@react-email/tailwind` from 2.0.5 to 2.0.7
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/@react-email/tailwind@2.0.7/packages/tailwind/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/@react-email/tailwind@2.0.7/packages/tailwind)

Updates `ioredis` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.10.0...v5.10.1)

Updates `maxmind` from 5.0.5 to 5.0.6
- [Release notes](https://github.com/runk/node-maxmind/releases)
- [Commits](https://github.com/runk/node-maxmind/compare/v5.0.5...v5.0.6)

Updates `nodemailer` from 8.0.5 to 8.0.7
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.5...v8.0.7)

Updates `react` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react)

Updates `react-dom` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom)

Updates `use-debounce` from 10.1.0 to 10.1.1
- [Release notes](https://github.com/xnimorz/use-debounce/releases)
- [Changelog](https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xnimorz/use-debounce/commits)

Updates `yaml` from 2.8.3 to 2.8.4
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.8.3...v2.8.4)

---
updated-dependencies:
- dependency-name: "@headlessui/react"
  dependency-version: 2.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@react-email/components"
  dependency-version: 1.0.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@react-email/render"
  dependency-version: 2.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@react-email/tailwind"
  dependency-version: 2.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: ioredis
  dependency-version: 5.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: maxmind
  dependency-version: 5.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: nodemailer
  dependency-version: 8.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: react
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: react-dom
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: use-debounce
  dependency-version: 10.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: yaml
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-06 01:38:27 +00:00
Owen Schwartz
432dc81875 Merge pull request #3006 from fosrl/dev
don't await second calculate func
2026-05-05 13:46:05 -07:00
miloschwartz
2ecf076c0f don't await second calculate func 2026-05-05 12:37:52 -07:00
Owen Schwartz
9b71c426c7 Merge pull request #3005 from fosrl/dev
1.18.2-s.4
2026-05-05 12:12:09 -07:00
miloschwartz
e06dda27cb dont wait rebuild 2026-05-05 12:10:55 -07:00
miloschwartz
18f6e0f75d add subscribed check back 2026-05-05 11:52:31 -07:00
miloschwartz
3b232bcc58 set orgId to undefined 2026-05-05 11:31:58 -07:00
Owen
c575bb76e7 Fix only using acme.json in dir
Ref #2978
2026-05-05 11:11:43 -07:00
Owen Schwartz
87e6c7ba36 Merge pull request #3003 from fosrl/dev
1.18.2-s.3
2026-05-05 10:54:48 -07:00
Owen
c8e7e0ee1e WAL off default ENABLE_SQLITE_WAL_MODE to enable 2026-05-04 17:54:28 -07:00
Owen Schwartz
0e7aafd364 Merge pull request #2998 from Josh-Voyles/mem-fix-2
fix: deterministically finalize SQLite prepared statements to prevent native memory leak (#2120)
2026-05-04 17:29:45 -07:00
miloschwartz
91f1bae3e9 fix alignement in info sections 2026-05-04 14:51:17 -07:00
miloschwartz
53c138ce3e use consistent button spacing 2026-05-04 14:34:32 -07:00
miloschwartz
969db14a3c remove delay in oidc validate 2026-05-04 13:14:35 -07:00
Owen Schwartz
9410a18404 Merge pull request #2997 from fosrl/dev
Translations
2026-05-04 11:49:26 -07:00
Owen Schwartz
c1c387bdd8 Merge pull request #2996 from fosrl/crowdin_dev
New Crowdin updates
2026-05-04 11:48:58 -07:00
Owen Schwartz
6e83d77a87 New translations en-us.json (Spanish)
[ci skip]
2026-05-04 11:48:00 -07:00
Owen Schwartz
ba9a1efa4c New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-04 11:47:58 -07:00
Owen Schwartz
9e046b9608 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-04 11:47:56 -07:00
Owen Schwartz
37794eb299 New translations en-us.json (Turkish)
[ci skip]
2026-05-04 11:47:55 -07:00
Owen Schwartz
4e66b0e74b New translations en-us.json (Russian)
[ci skip]
2026-05-04 11:47:53 -07:00
Owen Schwartz
44fa873977 New translations en-us.json (Portuguese)
[ci skip]
2026-05-04 11:47:51 -07:00
Owen Schwartz
505461a533 New translations en-us.json (Polish)
[ci skip]
2026-05-04 11:47:49 -07:00
Owen Schwartz
a88c5b1428 New translations en-us.json (Dutch)
[ci skip]
2026-05-04 11:47:47 -07:00
Owen Schwartz
97ef1d605c New translations en-us.json (Korean)
[ci skip]
2026-05-04 11:47:45 -07:00
Owen Schwartz
3fc1c9d948 New translations en-us.json (Italian)
[ci skip]
2026-05-04 11:47:44 -07:00
Owen Schwartz
68bd37ab6c New translations en-us.json (German)
[ci skip]
2026-05-04 11:47:42 -07:00
Owen Schwartz
5c317c535b New translations en-us.json (Czech)
[ci skip]
2026-05-04 11:47:40 -07:00
Owen Schwartz
37c6b11899 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-04 11:47:38 -07:00
Owen Schwartz
45c567ffa0 New translations en-us.json (French)
[ci skip]
2026-05-04 11:47:36 -07:00
Owen Schwartz
23f4302186 Merge pull request #2995 from fosrl/dev
1.18.2-s.2
2026-05-04 11:43:24 -07:00
Owen Schwartz
775ea64b55 Merge pull request #2977 from fosrl/newt-install-commands
fix(newt): update Helm install credentials and client flag handling
2026-05-04 11:40:19 -07:00
Owen
64ad7641af Add migration
Fixes #2968
Fixes #2990
2026-05-04 11:35:07 -07:00
Owen
d724f5bb5d Add missing redirects and threshold to api
Fixes #2987
2026-05-04 10:46:11 -07:00
Josh Voyles
2154811ffb removed possible introduced HA Redis bug; improved comment 2026-05-03 09:39:27 -04:00
Marc Schäfer
1772ac220f fix(newt): update Helm install credentials and client flag handling
Use a Kubernetes Secret for Newt Helm chart credentials and configure the chart
with auth.existingSecretName instead of passing credential values through
auth.keys.*.

Add Helm-specific acceptClients handling so the generated Kubernetes command sets
newtInstances[0].acceptClients=true when client connections are enabled.
2026-05-03 15:07:42 +02:00
Josh Voyles
9bd33072f4 cleaned comments - more concise 2026-05-03 00:00:11 -04:00
Josh Voyles
0655ba9423 fix: revert investigative changes, keep root cause fixes only
Reverts diagnostic instrumentation and defensive hardening added during
memory leak investigation. Only root cause fixes survive.

Root causes fixed:
- SQLite driver: auto-finalize wrapper + PRAGMAs
- WS routers: delete clientConfigVersions on disconnect (unbounded Map leak)
- WS private router: same + Redis key cleanup

Reverted:
- Memory monitor, rate limiting, request timeouts (diagnostic/hardening)
- shutdownAuditLogger wiring, audit re-queue change, debug logs (cleanup/secondary)
- package-lock.json drift
2026-05-02 16:33:13 -04:00
Josh Voyles
2c85bcd06b fix(db): deterministically finalize prepared statements after execution
Wrap Statement .all()/.get()/.run() via autoFinalizeStatement() with
try/finally calling stmt.finalize() post-execution, releasing native
sqlite3_stmt memory immediately instead of waiting for GC.

Safe because:
- Drizzle one-time queries invoke each statement once only
- Drizzle does not access statement after .all()/.get()/.run() returns
- Migration scripts use isolated new Database() instances (unpatched)
- No app code holds persistent .prepare() refs on main db
2026-05-02 15:50:54 -04:00
Josh Voyles
d6abe83fdc fix: memory improvements
- SQLite: enable WAL mode and PRAGMA performance settings

- ws.ts (public + private): fix clientConfigVersions memory leak

- internal server: add rate limiting and request timeouts

- audit log: fix flush re-queue feedback loop

- memory: add monitoring instrumentation

- security: remove debug log of full request body
2026-05-02 07:37:18 -04:00
40 changed files with 761 additions and 212 deletions

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Превишихте ограничението на текущия си план. Коригирайте проблема, като премахнете сайтове, потребители или други ресурси, за да оставате в рамките на плана си.",
"trialBannerMessage": "Пробният Ви период изтича след {countdown}. Актуализирайте за запазване на достъпа.",
"trialBannerExpired": "Пробният Ви период е изтекъл. Актуализирайте сега, за да възстановите достъпа.",
"billingTrialBannerTitle": "Пробният период е активен",
"billingTrialBannerDescription": "В момента сте в пробен период на бизнес ниво. След края на пробния период, вашият акаунт автоматично ще бъде върнат към функциите и ограниченията на основното ниво. Надградете по всяко време, за да запазите достъпа до текущите функции на плана.",
"billingTrialBannerUpgrade": "Надградете сега",
"billingTrialBadge": "Пробен период",
"trialActive": "Активен пробен период",
"trialExpired": "Пробният период е изтекъл",
"trialHasEnded": "Пробният Ви период е приключил.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Jste za hranicemi vašeho aktuálního plánu. Opravte problém odstraněním webů, uživatelů nebo jiných zdrojů, abyste zůstali ve vašem tarifu.",
"trialBannerMessage": "Vaše zkušební verze vyprší za {countdown}. Pro udržení přístupu upgraduje.",
"trialBannerExpired": "Vaše zkušební verze vypršela. Upgradujte nyní pro obnovu přístupu.",
"billingTrialBannerTitle": "Aktivní zkušební verze",
"billingTrialBannerDescription": "Právě používáte zkušební verzi na úrovni business. Po skončení zkušební verze se váš účet automaticky vrátí k funkcím a limitům úrovně Basic. Upgradujte kdykoli pro zachování přístupu k funkcím vašeho aktuálního plánu.",
"billingTrialBannerUpgrade": "Upgradovat nyní",
"billingTrialBadge": "Zkušební verze",
"trialActive": "Zkušební verze je aktivní",
"trialExpired": "Zkušební verze vypršela",
"trialHasEnded": "Vaše zkušební verze skončila.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Sie überschreiten Ihre Grenzen für Ihr aktuelles Paket. Korrigieren Sie das Problem, indem Sie Webseiten, Benutzer oder andere Ressourcen entfernen, um in Ihrem Paket zu bleiben.",
"trialBannerMessage": "Ihre Testversion läuft in {countdown} ab. Upgraden, um den Zugriff zu behalten.",
"trialBannerExpired": "Ihre Testversion ist abgelaufen. Jetzt upgraden, um den Zugriff wiederherzustellen.",
"billingTrialBannerTitle": "Kostenlose Testversion aktiv",
"billingTrialBannerDescription": "Sie nutzen derzeit eine kostenlose Testversion auf der Geschäftsstufe. Wenn die Testversion endet, wird Ihr Konto automatisch auf die Funktionen und Beschränkungen der Basisstufe zurückgesetzt. Upgraden Sie jederzeit, um weiterhin Zugriff auf die Funktionen Ihres aktuellen Plans zu behalten.",
"billingTrialBannerUpgrade": "Jetzt upgraden",
"billingTrialBadge": "Kostenlose Testversion",
"trialActive": "Kostenlose Testversion aktiv",
"trialExpired": "Testversion abgelaufen",
"trialHasEnded": "Ihre Testversion ist beendet.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Estás más allá de tus límites para tu plan actual. Corrija el problema eliminando sitios, usuarios u otros recursos para permanecer dentro de tu plan.",
"trialBannerMessage": "Su prueba expira en {countdown}. Actualice para mantener el acceso.",
"trialBannerExpired": "Su prueba ha expirado. Actualice ahora para restaurar el acceso.",
"billingTrialBannerTitle": "Prueba gratuita activada",
"billingTrialBannerDescription": "Actualmente estás en una prueba gratuita en el nivel empresarial. Cuando finalice la prueba, tu cuenta volverá automáticamente a las características y límites del nivel Básico. Mejora en cualquier momento para mantener el acceso a las características de tu plan actual.",
"billingTrialBannerUpgrade": "Actualizar ahora",
"billingTrialBadge": "Prueba Gratuita",
"trialActive": "Prueba gratuita activa",
"trialExpired": "Prueba expirada",
"trialHasEnded": "Su prueba ha terminado.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Vous dépassez vos limites pour votre forfait actuel. Corrigez le problème en supprimant des sites, des utilisateurs ou d'autres ressources pour rester dans votre forfait.",
"trialBannerMessage": "Votre essai expire dans {countdown}. Passez à l'abonnement pour garder l'accès.",
"trialBannerExpired": "Votre essai a expiré. Passez à l'abonnement maintenant pour restaurer l'accès.",
"billingTrialBannerTitle": "Essai gratuit actif",
"billingTrialBannerDescription": "Vous êtes actuellement en essai gratuit sur le niveau business. À la fin de l'essai, votre compte basculera automatiquement aux fonctionnalités et limites du niveau Basique. Mettez à jour à tout moment pour conserver l'accès aux fonctionnalités de votre plan actuel.",
"billingTrialBannerUpgrade": "Passer à la version supérieure maintenant",
"billingTrialBadge": "Essai gratuit",
"trialActive": "Essai gratuit actif",
"trialExpired": "Essai expiré",
"trialHasEnded": "Votre essai est terminé.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Hai superato i tuoi limiti per il tuo piano attuale. Correggi il problema rimuovendo siti, utenti o altre risorse per rimanere all'interno del tuo piano.",
"trialBannerMessage": "Il tuo periodo di prova scade tra {countdown}. Aggiorna per mantenere l'accesso.",
"trialBannerExpired": "Il tuo periodo di prova è scaduto. Aggiorna ora per ripristinare l'accesso.",
"billingTrialBannerTitle": "Prova Gratuita Attiva",
"billingTrialBannerDescription": "Attualmente sei in una prova gratuita sul livello business. Quando la prova terminerà, il tuo account tornerà automaticamente alle funzionalità e ai limiti del piano Basic. Effettua l'upgrade in qualsiasi momento per mantenere l'accesso alle funzionalità del tuo piano attuale.",
"billingTrialBannerUpgrade": "Effettua l'Upgrade Ora",
"billingTrialBadge": "Prova Gratuita",
"trialActive": "Prova Gratuita Attiva",
"trialExpired": "Prova scaduta",
"trialHasEnded": "La tua prova è terminata.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "현재 계획의 한계를 초과했습니다. 사이트, 사용자 또는 기타 리소스를 제거하여 계획 내에 머물도록 해결하세요.",
"trialBannerMessage": "시험 사용 기간이 {countdown} 안에 만료됩니다. 업그레이드하여 액세스를 유지하세요.",
"trialBannerExpired": "시험 사용 기간이 만료되었습니다. 지금 업그레이드하여 액세스를 복구하세요.",
"billingTrialBannerTitle": "무료 평가판 활성화",
"billingTrialBannerDescription": "현재 비즈니스 티어의 무료 평가판을 사용 중입니다. 평가판이 종료되면 계정은 자동으로 기본 티어 기능 및 제한으로 돌아갑니다. 현재 계획의 기능을 유지하려면 언제든지 업그레이드 하세요.",
"billingTrialBannerUpgrade": "지금 업그레이드",
"billingTrialBadge": "무료 평가판",
"trialActive": "무료 체험 활성화됨",
"trialExpired": "체험 만료됨",
"trialHasEnded": "시험 사용 기간이 종료되었습니다.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Du er utenfor grensen for gjeldende plan. Rett problemet ved å fjerne nettsteder, brukere eller andre ressurser for å bli innenfor planen din.",
"trialBannerMessage": "Din prøveperiode utløper om {countdown}. Oppgrader for å beholde tilgangen.",
"trialBannerExpired": "Prøveperioden din har utløpt. Oppgrader nå for å gjenopprette tilgangen.",
"billingTrialBannerTitle": "Prøveversjon Aktiv",
"billingTrialBannerDescription": "Du har for øyeblikket en gratis prøveversjon på forretningsnivået. Når prøven avsluttes, vil kontoen din automatisk gå tilbake til funksjoner og begrensninger på Basis-nivået. Oppgrader når som helst for å beholde tilgang til de nåværende planens funksjoner.",
"billingTrialBannerUpgrade": "Oppgrader nå",
"billingTrialBadge": "Prøveversjon",
"trialActive": "Gratis prøveversjon aktiv",
"trialExpired": "Prøveperioden er utløpt",
"trialHasEnded": "Din prøveperiode har avsluttet.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "U overschrijdt uw huidige abonnement. Corrigeer het probleem door sites, gebruikers of andere bronnen te verwijderen om binnen uw plan te blijven.",
"trialBannerMessage": "Uw proefversie verloopt over {countdown}. Upgrade om toegang te behouden.",
"trialBannerExpired": "Uw proefperiode is verlopen. Upgrade nu om toegang te herstellen.",
"billingTrialBannerTitle": "Proefperiode Actief",
"billingTrialBannerDescription": "Je bent momenteel bezig met een gratis proefperiode op het zakelijke niveau. Wanneer de proefperiode eindigt, wordt je account automatisch teruggezet naar de functies en limieten van het Basic-niveau. Upgrade op elk moment om toegang te houden tot de functies van je huidige plan.",
"billingTrialBannerUpgrade": "Nu Upgraden",
"billingTrialBadge": "Gratis Proefversie",
"trialActive": "Gratis proefversie actief",
"trialExpired": "Proefversie verlopen",
"trialHasEnded": "Uw proefperiode is geëindigd.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Nie masz ograniczeń dla aktualnego planu. Popraw problem poprzez usunięcie stron, użytkowników lub innych zasobów, aby pozostać w swoim planie.",
"trialBannerMessage": "Twój okres próbny wygasa za {countdown}. Uaktualnij, aby zachować dostęp.",
"trialBannerExpired": "Twój okres próbny wygasł. Uaktualnij teraz, aby przywrócić dostęp.",
"billingTrialBannerTitle": "Bezpłatna wersja próbna aktywna",
"billingTrialBannerDescription": "Obecnie korzystasz z bezpłatnej wersji próbnej na poziomie biznesowym. Po zakończeniu wersji próbnej, Twoje konto automatycznie powróci do funkcji i limitów poziomu Podstawowego. Możesz dokonać uaktualnienia w każdej chwili, aby zachować dostęp do funkcji obecnego planu.",
"billingTrialBannerUpgrade": "Uaktualnij teraz",
"billingTrialBadge": "Bezpłatna wersja próbna",
"trialActive": "Okres próbny aktywny",
"trialExpired": "Okres próbny wygasł",
"trialHasEnded": "Twój okres próbny dobiegł końca.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Você está além dos seus limites para o seu plano atual. Corrija o problema removendo sites, usuários, ou outros recursos para ficar em seu plano.",
"trialBannerMessage": "Sua avaliação termina em {countdown}. Faça o upgrade para manter o acesso.",
"trialBannerExpired": "Sua avaliação expirou. Faça o upgrade agora para restaurar o acesso.",
"billingTrialBannerTitle": "Teste Gratuito Ativo",
"billingTrialBannerDescription": "Atualmente, você está em um teste gratuito no nível empresarial. Quando o teste terminar, sua conta reverterá automaticamente para os recursos e limites do nível Básico. Atualize a qualquer momento para manter o acesso aos recursos do seu plano atual.",
"billingTrialBannerUpgrade": "Atualize Agora",
"billingTrialBadge": "Teste Gratuito",
"trialActive": "Avaliação Gratuita Ativa",
"trialExpired": "Avaliação Expirada",
"trialHasEnded": "Sua avaliação terminou.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Вы превысили лимиты для вашего текущего плана. Исправьте проблему, удалив сайты, пользователей или другие ресурсы, чтобы остаться в пределах вашего плана.",
"trialBannerMessage": "Ваш пробный период истекает через {countdown}. Обновите, чтобы сохранить доступ.",
"trialBannerExpired": "Ваш пробный период истек. Обновите сейчас, чтобы восстановить доступ.",
"billingTrialBannerTitle": "Бесплатная версия активна",
"billingTrialBannerDescription": "Вы в настоящее время находитесь на бесплатном пробном периоде бизнес-уровня. Когда пробный период закончится, ваш аккаунт автоматически вернётся к функциям и лимитам базового уровня. Обновите в любое время, чтобы сохранить доступ к функциям текущего плана.",
"billingTrialBannerUpgrade": "Обновить сейчас",
"billingTrialBadge": "Бесплатная версия",
"trialActive": "Бесплатный пробный период активен",
"trialExpired": "Пробный период истек",
"trialHasEnded": "Ваш пробный период окончен.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "Geçerli planınız için limitlerinizi aştınız. Planınız dahilinde kalmak için siteleri, kullanıcıları veya diğer kaynakları kaldırarak sorunu düzeltin.",
"trialBannerMessage": "Deneme süreniz {countdown} içinde sona eriyor. Erişimi sürdürmek için yükseltin.",
"trialBannerExpired": "Deneme süreniz sona erdi. Erişimi geri yüklemek için şimdi yükseltin.",
"billingTrialBannerTitle": "Ücretsiz Deneme Aktif",
"billingTrialBannerDescription": "Şu anda iş seviyesi için ücretsiz deneme sürümündesiniz. Deneme süresi sona erdiğinde, hesabınız otomatik olarak Temel seviye özelliklerine ve limitlerine geri dönecektir. Mevcut planınızın özelliklerine erişimi sürdürmek için istediğiniz zaman yükseltin.",
"billingTrialBannerUpgrade": "Şimdi Yükselt",
"billingTrialBadge": "Ücretsiz Deneme",
"trialActive": "Ücretsiz Deneme Aktif",
"trialExpired": "Deneme Süresi Doldu",
"trialHasEnded": "Deneme süreniz sona erdi.",

View File

@@ -25,6 +25,10 @@
"subscriptionViolationMessage": "您的当前计划超出了您的限制。通过移除站点、用户或其他资源以保持在您的计划范围内来纠正问题。",
"trialBannerMessage": "您的试用将在 {countdown} 到期。升级以保持访问。",
"trialBannerExpired": "您的试用已到期。立即升级以恢复访问。",
"billingTrialBannerTitle": "免费试用激活中",
"billingTrialBannerDescription": "您目前正在商用层进行免费试用。试用结束后,您的账户将自动回到基础层功能和限制。可随时升级以保持当前计划的功能访问。",
"billingTrialBannerUpgrade": "立即升级",
"billingTrialBadge": "免费试用",
"trialActive": "免费试用中",
"trialExpired": "试用到期",
"trialHasEnded": "您的试用已结束。",

239
package-lock.json generated
View File

@@ -12,7 +12,7 @@
"@asteasolutions/zod-to-openapi": "8.4.1",
"@aws-sdk/client-s3": "3.1011.0",
"@faker-js/faker": "10.3.0",
"@headlessui/react": "2.2.9",
"@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2",
@@ -36,9 +36,9 @@
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/components": "1.0.8",
"@react-email/render": "2.0.4",
"@react-email/tailwind": "2.0.5",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.8",
"@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.0",
"@tailwindcss/forms": "0.5.11",
@@ -62,26 +62,26 @@
"helmet": "8.1.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.10.0",
"ioredis": "5.10.1",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "0.577.0",
"maxmind": "5.0.5",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "15.5.15",
"next-intl": "4.8.3",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "8.0.5",
"nodemailer": "8.0.7",
"oslo": "1.2.1",
"pg": "8.20.0",
"posthog-node": "5.28.0",
"qrcode.react": "4.2.0",
"react": "19.2.4",
"react": "19.2.5",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-dom": "19.2.5",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.71.2",
"react-icons": "5.6.0",
@@ -95,14 +95,14 @@
"tailwind-merge": "3.5.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "10.1.0",
"use-debounce": "10.1.1",
"uuid": "13.0.0",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.19.0",
"yaml": "2.8.3",
"yaml": "2.8.4",
"yargs": "18.0.0",
"zod": "4.3.6",
"zod-validation-error": "5.0.0"
@@ -124,7 +124,7 @@
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.3.5",
"@types/nodemailer": "7.0.11",
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.18.0",
"@types/react": "19.2.14",
@@ -1058,7 +1058,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -2248,9 +2247,9 @@
}
},
"node_modules/@headlessui/react": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz",
"integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==",
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz",
"integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.26.16",
@@ -2354,7 +2353,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2377,7 +2375,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2400,7 +2397,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2417,7 +2413,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2434,7 +2429,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2451,7 +2445,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2468,7 +2461,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2485,7 +2477,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2502,7 +2493,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2519,7 +2509,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2536,7 +2525,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2553,7 +2541,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2576,7 +2563,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2599,7 +2585,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2622,7 +2607,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2645,7 +2629,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2668,7 +2651,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2691,7 +2673,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2714,7 +2695,6 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@@ -2734,7 +2714,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2754,7 +2733,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2774,7 +2752,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3034,7 +3011,6 @@
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": "^14.21.3 || >=16"
},
@@ -6410,18 +6386,6 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-email/body": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.2.1.tgz",
"integrity": "sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
}
},
"node_modules/@react-email/button": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.1.tgz",
@@ -6474,12 +6438,13 @@
}
},
"node_modules/@react-email/components": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.8.tgz",
"integrity": "sha512-zY81ED6o5MWMzBkr9uZFuT24lWarT+xIbOZxI6C9dsFmCWBczM8IE1BgOI8rhpUK4JcYVDy1uKxYAFqsx2Bc4w==",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-1.0.12.tgz",
"integrity": "sha512-tH18JhPDWgE+3jnYkzyB6ZrZdfNnEsFe4PwmuXmlOw4NGIysP8wPY5aXZg++pTG9qUabXg1nzX/FGHGkObH8xQ==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"dependencies": {
"@react-email/body": "0.2.1",
"@react-email/body": "0.3.0",
"@react-email/button": "0.2.1",
"@react-email/code-block": "0.2.1",
"@react-email/code-inline": "0.0.6",
@@ -6494,10 +6459,10 @@
"@react-email/link": "0.0.13",
"@react-email/markdown": "0.0.18",
"@react-email/preview": "0.0.14",
"@react-email/render": "2.0.4",
"@react-email/render": "2.0.6",
"@react-email/row": "0.0.13",
"@react-email/section": "0.0.17",
"@react-email/tailwind": "2.0.5",
"@react-email/tailwind": "2.0.7",
"@react-email/text": "0.1.6"
},
"engines": {
@@ -6507,6 +6472,36 @@
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
}
},
"node_modules/@react-email/components/node_modules/@react-email/body": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.3.0.tgz",
"integrity": "sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
}
},
"node_modules/@react-email/components/node_modules/@react-email/render": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.6.tgz",
"integrity": "sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog==",
"license": "MIT",
"dependencies": {
"html-to-text": "^9.0.5",
"prettier": "^3.5.3"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
}
},
"node_modules/@react-email/container": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.16.tgz",
@@ -6878,9 +6873,9 @@
}
},
"node_modules/@react-email/render": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.4.tgz",
"integrity": "sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==",
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-2.0.8.tgz",
"integrity": "sha512-5udvVr3U/WuGJZfLdLBOhkzrqRWd2Q5ZYmF7ppcy7FzWcwgshdqLMNqJOXcVzAXJXg/2bm7D+WGJzTtZOZMQnQ==",
"license": "MIT",
"dependencies": {
"html-to-text": "^9.0.5",
@@ -6919,9 +6914,10 @@
}
},
"node_modules/@react-email/tailwind": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.5.tgz",
"integrity": "sha512-7Ey+kiWliJdxPMCLYsdDts8ffp4idlP//w4Ui3q/A5kokVaLSNKG8DOg/8qAuzWmRiGwNQVOKBk7PXNlK5W+sg==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-2.0.7.tgz",
"integrity": "sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"dependencies": {
"tailwindcss": "^4.1.18"
@@ -6930,17 +6926,17 @@
"node": ">=20.0.0"
},
"peerDependencies": {
"@react-email/body": "0.2.1",
"@react-email/button": "0.2.1",
"@react-email/code-block": "0.2.1",
"@react-email/code-inline": "0.0.6",
"@react-email/container": "0.0.16",
"@react-email/heading": "0.0.16",
"@react-email/hr": "0.0.12",
"@react-email/img": "0.0.12",
"@react-email/link": "0.0.13",
"@react-email/preview": "0.0.14",
"@react-email/text": "0.1.6",
"@react-email/body": ">=0",
"@react-email/button": ">=0",
"@react-email/code-block": ">=0",
"@react-email/code-inline": ">=0",
"@react-email/container": ">=0",
"@react-email/heading": ">=0",
"@react-email/hr": ">=0",
"@react-email/img": ">=0",
"@react-email/link": ">=0",
"@react-email/preview": ">=0",
"@react-email/text": ">=0",
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
@@ -6981,7 +6977,6 @@
"resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz",
"integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.0.0"
},
@@ -8442,7 +8437,6 @@
"version": "5.90.21",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
"integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.20"
},
@@ -8558,7 +8552,6 @@
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*"
}
@@ -8906,7 +8899,6 @@
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
@@ -9002,15 +8994,14 @@
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/nodemailer": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.11.tgz",
"integrity": "sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.0.tgz",
"integrity": "sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9030,7 +9021,6 @@
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
@@ -9056,7 +9046,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -9067,7 +9056,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -9154,7 +9142,8 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/@types/ws": {
"version": "8.18.1",
@@ -9228,7 +9217,6 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
@@ -9702,7 +9690,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -10152,7 +10139,6 @@
"integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.26.0"
}
@@ -10224,7 +10210,6 @@
"integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
@@ -10353,7 +10338,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -11260,7 +11244,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -11701,6 +11684,7 @@
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz",
"integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"peer": true,
"engines": {
"node": ">=20"
},
@@ -12335,7 +12319,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -12421,7 +12404,6 @@
"integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -12558,7 +12540,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -12952,7 +12933,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -13991,9 +13971,9 @@
}
},
"node_modules/ioredis": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.0.tgz",
"integrity": "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA==",
"version": "5.10.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "1.5.1",
@@ -15139,13 +15119,13 @@
}
},
"node_modules/maxmind": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.5.tgz",
"integrity": "sha512-1lcH2kMjbBpCFhuHaMU32vz8CuOsKttRcWMQyXvtlklopCzN7NNHSVR/h9RYa8JPuFTGmkn2vYARm+7cIGuqDw==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.6.tgz",
"integrity": "sha512-5bvd/u+kIaTqaGM+xkXjatzQw1dQfSmlLggr2W1EKMyMxSgx2woZyusLpNpZ4DdPmL+1bbJWeo4LXsi6bC0Iew==",
"license": "MIT",
"dependencies": {
"mmdb-lib": "3.0.2",
"tiny-lru": "11.4.7"
"tiny-lru": "13.0.0"
},
"engines": {
"node": ">=12",
@@ -15370,6 +15350,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -15380,6 +15361,7 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"peer": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -15468,7 +15450,6 @@
"resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz",
"integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "15.5.15",
"@swc/helpers": "0.5.15",
@@ -15686,9 +15667,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
"integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz",
"integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -16428,7 +16409,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.12.0",
"pg-pool": "^3.13.0",
@@ -16932,11 +16912,10 @@
}
},
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16964,16 +16943,15 @@
}
},
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.4"
"react": "^19.2.5"
}
},
"node_modules/react-easy-sort": {
@@ -17261,7 +17239,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
"integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -18723,8 +18700,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.2",
@@ -18781,12 +18757,12 @@
"license": "MIT"
},
"node_modules/tiny-lru": {
"version": "11.4.7",
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz",
"integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==",
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-13.0.0.tgz",
"integrity": "sha512-xDHxKKS1FdF0Tv2P+QT7IeSEg74K/8cEDzbv3Tv6UyHHUgBOjOiQiBp818MGj66dhurQus/IBcoAbwIKtSGc6Q==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
"node": ">=14"
}
},
"node_modules/tinyexec": {
@@ -19199,7 +19175,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19378,9 +19353,9 @@
}
},
"node_modules/use-debounce": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.0.tgz",
"integrity": "sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.1.tgz",
"integrity": "sha512-kvds8BHR2k28cFsxW8k3nc/tRga2rs1RHYCqmmGqb90MEeE++oALwzh2COiuBLO1/QXiOuShXoSN2ZpWnMmvuQ==",
"license": "MIT",
"engines": {
"node": ">= 16.0.0"
@@ -19627,7 +19602,6 @@
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.8",
@@ -19740,9 +19714,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -19834,7 +19808,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@@ -35,7 +35,7 @@
"@asteasolutions/zod-to-openapi": "8.4.1",
"@aws-sdk/client-s3": "3.1011.0",
"@faker-js/faker": "10.3.0",
"@headlessui/react": "2.2.9",
"@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2",
@@ -59,9 +59,9 @@
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/components": "1.0.8",
"@react-email/render": "2.0.4",
"@react-email/tailwind": "2.0.5",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.8",
"@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.0",
"@tailwindcss/forms": "0.5.11",
@@ -85,26 +85,26 @@
"helmet": "8.1.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.10.0",
"ioredis": "5.10.1",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "0.577.0",
"maxmind": "5.0.5",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "15.5.15",
"next-intl": "4.8.3",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "8.0.5",
"nodemailer": "8.0.7",
"oslo": "1.2.1",
"pg": "8.20.0",
"posthog-node": "5.28.0",
"qrcode.react": "4.2.0",
"react": "19.2.4",
"react": "19.2.5",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-dom": "19.2.5",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.71.2",
"react-icons": "5.6.0",
@@ -118,14 +118,14 @@
"tailwind-merge": "3.5.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "10.1.0",
"use-debounce": "10.1.1",
"uuid": "13.0.0",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.19.0",
"yaml": "2.8.3",
"yaml": "2.8.4",
"yargs": "18.0.0",
"zod": "4.3.6",
"zod-validation-error": "5.0.0"
@@ -147,7 +147,7 @@
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.3.5",
"@types/nodemailer": "7.0.11",
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.18.0",
"@types/react": "19.2.14",

View File

@@ -1,5 +1,6 @@
import { drizzle as DrizzleSqlite } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import type BetterSqlite3 from "better-sqlite3";
import * as schema from "./schema/schema";
import path from "path";
import fs from "fs";
@@ -11,8 +12,69 @@ export const exists = checkFileExists(location);
bootstrapVolume();
/**
* Wraps better-sqlite3 Statement to call `finalize()` immediately after
* execution, freeing native sqlite3_stmt memory deterministically instead
* of waiting for GC. Fixes steady off-heap growth under load (#2120).
* WARNING: Finalizes after first execution — incompatible with drizzle's
* reusable .prepare() builders. No such usage exists in this codebase.
*/
function autoFinalizeStatement(
stmt: BetterSqlite3.Statement
): BetterSqlite3.Statement {
const wrapExec = <T extends (...args: any[]) => any>(fn: T): T => {
return function (this: any, ...args: any[]) {
try {
return fn.apply(this, args);
} finally {
try {
// finalize() exists on the native Statement at runtime but
// is missing from @types/better-sqlite3.
(stmt as any).finalize();
} catch {
// Already finalized — harmless
}
}
} as unknown as T;
};
stmt.run = wrapExec(stmt.run);
stmt.get = wrapExec(stmt.get);
stmt.all = wrapExec(stmt.all);
return stmt;
}
function createDb() {
const sqlite = new Database(location);
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
// Enable WAL mode — allows concurrent readers + single writer, preventing
// contention across subsystems (verifySession, Traefik, audit, ping).
sqlite.pragma("journal_mode = WAL");
// NORMAL sync mode: safe with WAL, reduces write lock hold time.
sqlite.pragma("synchronous = NORMAL");
}
// Wait up to 5s on SQLITE_BUSY instead of failing — prevents audit log
// retry loops that accumulate memory.
sqlite.pragma("busy_timeout = 5000");
// 64 MB page cache (default 2 MB) — reduces I/O round-trips on large
// TraefikConfigManager JOINs that block the event loop.
sqlite.pragma("cache_size = -65536");
// 256 MB memory-mapped I/O — OS serves reads from page cache directly,
// reducing event-loop blocking.
sqlite.pragma("mmap_size = 268435456");
// Wrap prepare() so every drizzle-orm statement is auto-finalized after
// first use, preventing sqlite3_stmt accumulation between GC cycles.
const originalPrepare = sqlite.prepare.bind(sqlite);
(sqlite as any).prepare = function autoFinalizePrepare(source: string) {
return autoFinalizeStatement(originalPrepare(source));
};
return DrizzleSqlite(sqlite, {
schema
});
@@ -23,7 +85,7 @@ export default db;
export const primaryDb = db;
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];
>[0];
export const DB_TYPE: "pg" | "sqlite" = "sqlite";
function checkFileExists(filePath: string): boolean {

View File

@@ -500,7 +500,30 @@ function findAcmeJsonFiles(dirPath: string): string[] {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
results.push(...findAcmeJsonFiles(fullPath));
} else if (entry.isFile() && entry.name === "acme.json") {
} else if (entry.isFile()) {
// check if it is a json file
if (entry.name.endsWith(".json")) {
let raw: string;
try {
raw = fs.readFileSync(fullPath, "utf8");
} catch (err) {
logger.warn(
`acmeCertSync: could not read file "${fullPath}": ${err}`
);
continue;
}
let parsed: any;
try {
parsed = JSON.parse(raw);
} catch (err) {
logger.warn(
`acmeCertSync: could not parse "${fullPath}" as JSON: ${err}`
);
continue;
}
}
results.push(fullPath);
}
}

View File

@@ -22,7 +22,7 @@ import {
Olm,
olms,
RemoteExitNode,
remoteExitNodes,
remoteExitNodes
} from "@server/db";
import { eq } from "drizzle-orm";
import { db } from "@server/db";
@@ -194,8 +194,6 @@ const connectedClients: Map<string, AuthenticatedWebSocket[]> = new Map();
// Config version tracking map (local to this node, resets on server restart)
const clientConfigVersions: Map<string, number> = new Map();
// Recovery tracking
let isRedisRecoveryInProgress = false;
@@ -406,6 +404,9 @@ const removeClient = async (
const updatedClients = existingClients.filter((client) => client !== ws);
if (updatedClients.length === 0) {
connectedClients.delete(mapKey);
// Remove clientId from clientConfigVersions on disconnect — prevents
// unbounded memory growth from stale entries.
clientConfigVersions.delete(clientId);
if (redisManager.isRedisEnabled()) {
try {
@@ -1097,6 +1098,11 @@ const disconnectClient = async (clientId: string): Promise<boolean> => {
}
});
// Eagerly remove client — close event may not fire if socket is already
// CLOSING, leaving zombie entries.
connectedClients.delete(mapKey);
clientConfigVersions.delete(clientId);
return true;
};

View File

@@ -333,23 +333,16 @@ export async function validateOidcCallback(
.innerJoin(orgs, eq(orgs.orgId, idpOrg.orgId));
allOrgs = idpOrgs.map((o) => o.orgs);
// for (const org of allOrgs) {
// const subscribed = await isSubscribed(
// org.orgId,
// tierMatrix.autoProvisioning
// );
// if (!subscribed) {
// // filter out the org
// allOrgs = allOrgs.filter((o) => o.orgId !== org.orgId);
// // return next(
// // createHttpError(
// // HttpCode.FORBIDDEN,
// // "This organization's current plan does not support this feature."
// // )
// // );
// }
// }
for (const org of allOrgs) {
const subscribed = await isSubscribed(
org.orgId,
tierMatrix.autoProvisioning
);
if (!subscribed) {
// filter out the org
allOrgs = allOrgs.filter((o) => o.orgId !== org.orgId);
}
}
} else {
allOrgs = await db.select().from(orgs);
}
@@ -490,7 +483,14 @@ export async function validateOidcCallback(
}
}
await calculateUserClientsForOrgs(existingUser.userId);
calculateUserClientsForOrgs(existingUser.userId).catch(
(err) => {
logger.error(
"Error calculating user clients after removing all orgs for user with no valid IdP mappings",
{ error: err }
);
}
);
return next(
createHttpError(
@@ -512,10 +512,9 @@ export async function validateOidcCallback(
const orgUserCounts: { orgId: string; userCount: number }[] = [];
let userId = existingUser?.userId;
// sync the user with the orgs and roles
await db.transaction(async (trx) => {
let userId = existingUser?.userId;
// create user if not exists
if (!existingUser) {
userId = generateId(15);
@@ -645,8 +644,15 @@ export async function validateOidcCallback(
userCount: userCount.length
});
}
});
db.transaction(async (trx) => {
await calculateUserClientsForOrgs(userId!, trx);
}).catch((err) => {
logger.error(
"Error calculating user clients after syncing orgs and roles for OIDC user",
{ error: err }
);
});
for (const orgCount of orgUserCounts) {

View File

@@ -56,6 +56,8 @@ function queryTargets(resourceId: number) {
hcStatus: targetHealthCheck.hcStatus,
hcHealth: targetHealthCheck.hcHealth,
hcTlsServerName: targetHealthCheck.hcTlsServerName,
hcHealthyThreshold: targetHealthCheck.hcHealthyThreshold,
hcUnhealthyThreshold: targetHealthCheck.hcUnhealthyThreshold,
path: targets.path,
pathMatchType: targets.pathMatchType,
rewritePath: targets.rewritePath,

View File

@@ -3,7 +3,15 @@ import zlib from "zlib";
import { Server as HttpServer } from "http";
import { WebSocket, WebSocketServer } from "ws";
import { Socket } from "net";
import { Newt, newts, NewtSession, olms, Olm, OlmSession, sites } from "@server/db";
import {
Newt,
newts,
NewtSession,
olms,
Olm,
OlmSession,
sites
} from "@server/db";
import { eq } from "drizzle-orm";
import { db } from "@server/db";
import { recordPing } from "@server/routers/newt/pingAccumulator";
@@ -80,6 +88,9 @@ const removeClient = async (
const updatedClients = existingClients.filter((client) => client !== ws);
if (updatedClients.length === 0) {
connectedClients.delete(mapKey);
// Remove clientId from clientConfigVersions — prevents unbounded growth
// from stale entries.
clientConfigVersions.delete(clientId);
logger.info(
`All connections removed for ${clientType.toUpperCase()} ID: ${clientId}`
@@ -218,9 +229,13 @@ const hasActiveConnections = async (clientId: string): Promise<boolean> => {
};
// Get the current config version for a client
const getClientConfigVersion = async (clientId: string): Promise<number | undefined> => {
const getClientConfigVersion = async (
clientId: string
): Promise<number | undefined> => {
const version = clientConfigVersions.get(clientId);
logger.debug(`getClientConfigVersion called for clientId: ${clientId}, returning: ${version} (type: ${typeof version})`);
logger.debug(
`getClientConfigVersion called for clientId: ${clientId}, returning: ${version} (type: ${typeof version})`
);
return version;
};
@@ -507,6 +522,11 @@ const disconnectClient = async (clientId: string): Promise<boolean> => {
}
});
// Eagerly remove client — close event may not fire if socket already
// CLOSING, leaving zombie entries.
connectedClients.delete(mapKey);
clientConfigVersions.delete(clientId);
return true;
};

View File

@@ -23,6 +23,7 @@ import m14 from "./scriptsPg/1.15.4";
import m15 from "./scriptsPg/1.16.0";
import m16 from "./scriptsPg/1.17.0";
import m17 from "./scriptsPg/1.18.0";
import m18 from "./scriptsPg/1.18.3";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -45,7 +46,8 @@ const migrations = [
{ version: "1.15.4", run: m14 },
{ version: "1.16.0", run: m15 },
{ version: "1.17.0", run: m16 },
{ version: "1.18.0", run: m17 }
{ version: "1.18.0", run: m17 },
{ version: "1.18.3", run: m18 }
// Add new migrations here as they are created
] as {
version: string;

View File

@@ -41,6 +41,7 @@ import m35 from "./scriptsSqlite/1.15.4";
import m36 from "./scriptsSqlite/1.16.0";
import m37 from "./scriptsSqlite/1.17.0";
import m38 from "./scriptsSqlite/1.18.0";
import m39 from "./scriptsSqlite/1.18.3";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -79,7 +80,8 @@ const migrations = [
{ version: "1.15.4", run: m35 },
{ version: "1.16.0", run: m36 },
{ version: "1.17.0", run: m37 },
{ version: "1.18.0", run: m38 }
{ version: "1.18.0", run: m38 },
{ version: "1.18.3", run: m39 }
// Add new migrations here as they are created
] as const;

View File

@@ -0,0 +1,173 @@
import { db } from "@server/db/pg/driver";
import { sql } from "drizzle-orm";
const version = "1.18.3";
export default async function migration() {
console.log(`Running setup script ${version}...`);
// Query existing targetHealthCheck data with joined siteId and orgId before
// the transaction adds the new columns (which start NULL for existing rows).
// We will delete all rows and reinsert them with targetHealthCheckId = targetId
// so the two IDs form a stable 1:1 mapping.
const healthChecksQuery = await db.execute(
sql`SELECT
thc."targetHealthCheckId",
thc."targetId",
t."siteId",
s."orgId",
r."name" AS "resourceName",
t."ip",
t."port"
FROM "targetHealthCheck" thc
JOIN "targets" t ON thc."targetId" = t."targetId"
JOIN "sites" s ON t."siteId" = s."siteId"
JOIN "resources" r ON t."resourceId" = r."resourceId"
WHERE thc."name" IS NULL OR thc."name" = ''`
);
const existingHealthChecks = healthChecksQuery.rows as {
targetHealthCheckId: number;
targetId: number;
siteId: number;
orgId: string;
resourceName: string;
ip: string;
port: number;
}[];
console.log(
`Found ${existingHealthChecks.length} existing targetHealthCheck row(s) to migrate`
);
try {
await db.execute(sql`BEGIN`);
await db.execute(sql`
CREATE TABLE "trialNotifications" (
"notificationId" serial PRIMARY KEY NOT NULL,
"subscriptionId" varchar(255) NOT NULL,
"notificationType" varchar(50) NOT NULL,
"sentAt" bigint NOT NULL
);
`);
await db.execute(sql`
ALTER TABLE "trialNotifications" ADD CONSTRAINT "trialNotifications_subscriptionId_subscriptions_subscriptionId_fk" FOREIGN KEY ("subscriptionId") REFERENCES "public"."subscriptions"("subscriptionId") ON DELETE cascade ON UPDATE no action;
`);
await db.execute(sql`COMMIT`);
console.log("Migrated database");
} catch (e) {
await db.execute(sql`ROLLBACK`);
console.log("Unable to migrate database");
console.log(e);
throw e;
}
if (existingHealthChecks.length > 0) {
// fix the name column
try {
for (const hc of existingHealthChecks) {
await db.execute(sql`
UPDATE "targetHealthCheck"
SET "name" = ${`Resource ${hc.resourceName} - ${hc.ip}:${hc.port}`}
WHERE "targetHealthCheckId" = ${hc.targetHealthCheckId}
`);
}
console.log(
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
);
} catch (e) {
console.error("Error while migrating targetHealthCheck rows:", e);
throw e;
}
}
// Recompute resource health by aggregating across the resource's targets'
// target health checks, then update the resources.health column to match.
try {
const resourceTargetHealthQuery = await db.execute(
sql`SELECT
r."resourceId" AS "resourceId",
r."orgId" AS "orgId",
r."health" AS "currentHealth",
thc."hcHealth" AS "hcHealth"
FROM "resources" r
LEFT JOIN "targets" t ON t."resourceId" = r."resourceId"
LEFT JOIN "targetHealthCheck" thc ON thc."targetId" = t."targetId"`
);
const resourceTargetHealthRows = resourceTargetHealthQuery.rows as {
resourceId: number;
orgId: string;
currentHealth: string | null;
hcHealth: string | null;
}[];
const resourceHealthMap = new Map<
number,
{
hasHealthy: boolean;
hasUnhealthy: boolean;
hasUnknown: boolean;
orgId: string;
currentHealth: string | null;
}
>();
for (const row of resourceTargetHealthRows) {
const entry = resourceHealthMap.get(row.resourceId) ?? {
hasHealthy: false,
hasUnhealthy: false,
hasUnknown: false,
orgId: row.orgId,
currentHealth: row.currentHealth
};
const status = row.hcHealth ?? "unknown";
if (status === "healthy") entry.hasHealthy = true;
else if (status === "unhealthy") entry.hasUnhealthy = true;
else entry.hasUnknown = true;
resourceHealthMap.set(row.resourceId, entry);
}
const now = Math.floor(Date.now() / 1000);
let updatedResourceCount = 0;
for (const [resourceId, entry] of resourceHealthMap.entries()) {
let aggregated: "healthy" | "unhealthy" | "degraded" | "unknown";
if (entry.hasHealthy && entry.hasUnhealthy) {
aggregated = "degraded";
} else if (entry.hasHealthy) {
aggregated = "healthy";
} else if (entry.hasUnhealthy) {
aggregated = "unhealthy";
} else {
aggregated = "unknown";
}
if (entry.currentHealth !== aggregated) {
await db.execute(sql`
UPDATE "resources"
SET "health" = ${aggregated}
WHERE "resourceId" = ${resourceId}
`);
await db.execute(sql`
INSERT INTO "statusHistory" ("entityType", "entityId", "orgId", "status", "timestamp")
VALUES ('resource', ${resourceId}, ${entry.orgId}, ${aggregated}, ${now})
`);
updatedResourceCount++;
}
}
console.log(
`Recomputed health for ${updatedResourceCount} resource(s) based on target health checks`
);
} catch (e) {
console.error(
"Error while recomputing resource health from target health checks:",
e
);
throw e;
}
console.log(`${version} migration complete`);
}

View File

@@ -0,0 +1,172 @@
import { APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.18.3";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.prepare(
`
CREATE TABLE 'trialNotifications' (
'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
'subscriptionId' text NOT NULL,
'notificationType' text NOT NULL,
'sentAt' integer NOT NULL,
FOREIGN KEY ('subscriptionId') REFERENCES 'subscriptions'('subscriptionId') ON UPDATE no action ON DELETE cascade
);
`
).run();
})();
db.pragma("foreign_keys = ON");
console.log("Migrated database");
// Fix names for health checks that don't have one
const healthChecksWithoutName = db
.prepare(
`SELECT
thc."targetHealthCheckId",
r."name" AS "resourceName",
t."ip",
t."port"
FROM 'targetHealthCheck' thc
JOIN 'targets' t ON thc."targetId" = t."targetId"
JOIN 'resources' r ON t."resourceId" = r."resourceId"
WHERE thc."name" IS NULL OR thc."name" = ''`
)
.all() as {
targetHealthCheckId: number;
resourceName: string;
ip: string;
port: number;
}[];
console.log(
`Found ${healthChecksWithoutName.length} targetHealthCheck row(s) with missing names`
);
if (healthChecksWithoutName.length > 0) {
const updateName = db.prepare(
`UPDATE 'targetHealthCheck' SET "name" = ? WHERE "targetHealthCheckId" = ?`
);
const updateAllNames = db.transaction(() => {
for (const hc of healthChecksWithoutName) {
updateName.run(
`Resource ${hc.resourceName} - ${hc.ip}:${hc.port}`,
hc.targetHealthCheckId
);
}
});
updateAllNames();
console.log(
`Updated names for ${healthChecksWithoutName.length} targetHealthCheck row(s)`
);
}
// Recompute resource health by aggregating across the resource's
// targets' target health checks, then update resources.health and
// insert a statusHistory entry for any resource whose health changed.
const resourceTargetHealthRows = db
.prepare(
`SELECT
r."resourceId" AS "resourceId",
r."orgId" AS "orgId",
r."health" AS "currentHealth",
thc."hcHealth" AS "hcHealth"
FROM 'resources' r
LEFT JOIN 'targets' t ON t."resourceId" = r."resourceId"
LEFT JOIN 'targetHealthCheck' thc ON thc."targetId" = t."targetId"`
)
.all() as {
resourceId: number;
orgId: string;
currentHealth: string | null;
hcHealth: string | null;
}[];
const resourceHealthMap = new Map<
number,
{
hasHealthy: boolean;
hasUnhealthy: boolean;
hasUnknown: boolean;
orgId: string;
currentHealth: string | null;
}
>();
for (const row of resourceTargetHealthRows) {
const entry = resourceHealthMap.get(row.resourceId) ?? {
hasHealthy: false,
hasUnhealthy: false,
hasUnknown: false,
orgId: row.orgId,
currentHealth: row.currentHealth
};
const status = row.hcHealth ?? "unknown";
if (status === "healthy") entry.hasHealthy = true;
else if (status === "unhealthy") entry.hasUnhealthy = true;
else entry.hasUnknown = true;
resourceHealthMap.set(row.resourceId, entry);
}
const updateResourceHealth = db.prepare(
`UPDATE 'resources' SET "health" = ? WHERE "resourceId" = ?`
);
const insertResourceHistory = db.prepare(
`INSERT INTO 'statusHistory' ("entityType", "entityId", "orgId", "status", "timestamp") VALUES (?, ?, ?, ?, ?)`
);
const now = Math.floor(Date.now() / 1000);
let updatedResourceCount = 0;
const recomputeAll = db.transaction(() => {
for (const [resourceId, entry] of resourceHealthMap.entries()) {
let aggregated:
| "healthy"
| "unhealthy"
| "degraded"
| "unknown";
if (entry.hasHealthy && entry.hasUnhealthy) {
aggregated = "degraded";
} else if (entry.hasHealthy) {
aggregated = "healthy";
} else if (entry.hasUnhealthy) {
aggregated = "unhealthy";
} else {
aggregated = "unknown";
}
if (entry.currentHealth !== aggregated) {
updateResourceHealth.run(aggregated, resourceId);
insertResourceHistory.run(
"resource",
resourceId,
entry.orgId,
aggregated,
now
);
updatedResourceCount++;
}
}
});
recomputeAll();
console.log(
`Recomputed health for ${updatedResourceCount} resource(s) based on target health checks`
);
} catch (e) {
console.log("Failed to migrate db:", e);
throw e;
}
console.log(`${version} migration complete`);
}

View File

@@ -652,6 +652,8 @@ function ProxyResourceTargetsForm({
hcMode: null,
hcUnhealthyInterval: null,
hcTlsServerName: null,
hcHealthyThreshold: null,
hcUnhealthyThreshold: null,
siteType: sites.length > 0 ? sites[0].type : null,
new: true,
updated: false
@@ -761,7 +763,9 @@ function ProxyResourceTargetsForm({
hcStatus: target.hcStatus || null,
hcUnhealthyInterval: target.hcUnhealthyInterval || null,
hcMode: target.hcMode || null,
hcTlsServerName: target.hcTlsServerName
hcTlsServerName: target.hcTlsServerName,
hcHealthyThreshold: target.hcHealthyThreshold || null,
hcUnhealthyThreshold: target.hcUnhealthyThreshold || null
};
// Only include path-related fields for HTTP resources
@@ -1018,7 +1022,13 @@ function ProxyResourceTargetsForm({
30,
hcTlsServerName:
selectedTargetForHealthCheck.hcTlsServerName ||
undefined
undefined,
hcHealthyThreshold:
selectedTargetForHealthCheck.hcHealthyThreshold ||
1,
hcUnhealthyThreshold:
selectedTargetForHealthCheck.hcUnhealthyThreshold ||
1
}}
onChanges={async (config) => {
if (selectedTargetForHealthCheck) {

View File

@@ -303,6 +303,8 @@ export default function Page() {
hcMode: null,
hcUnhealthyInterval: null,
hcTlsServerName: null,
hcHealthyThreshold: null,
hcUnhealthyThreshold: null,
siteType: sites.length > 0 ? sites[0].type : null,
new: true,
updated: false
@@ -552,7 +554,11 @@ export default function Page() {
hcUnhealthyInterval:
target.hcUnhealthyInterval || null,
hcMode: target.hcMode || null,
hcTlsServerName: target.hcTlsServerName
hcTlsServerName: target.hcTlsServerName,
hcHealthyThreshold:
target.hcHealthyThreshold || null,
hcUnhealthyThreshold:
target.hcUnhealthyThreshold || null
};
// Only include path-related fields for HTTP resources
@@ -1520,7 +1526,13 @@ export default function Page() {
30,
hcTlsServerName:
selectedTargetForHealthCheck.hcTlsServerName ||
undefined
undefined,
hcHealthyThreshold:
selectedTargetForHealthCheck.hcHealthyThreshold ||
1,
hcUnhealthyThreshold:
selectedTargetForHealthCheck.hcUnhealthyThreshold ||
1
}}
onChanges={async (config) => {
if (selectedTargetForHealthCheck) {

View File

@@ -31,8 +31,9 @@ export function CertificateStatusContent({
const t = useTranslations();
const labelClass =
"inline-flex shrink-0 items-center self-center text-sm font-medium leading-none";
const valueClass = "inline-flex items-center gap-2 text-sm leading-none";
"inline-flex shrink-0 items-center self-center text-sm font-medium leading-normal";
const valueClass =
"inline-flex items-center gap-2 text-sm leading-normal";
const handleRefresh = async () => {
await refreshCert();
@@ -133,14 +134,14 @@ export function CertificateStatusContent({
{isPending && !disableRestartButton ? (
<Button
variant="ghost"
className="h-auto min-h-0 shrink-0 p-0 text-sm font-normal leading-none inline-flex items-center self-center"
className="h-auto min-h-0 shrink-0 p-0 text-sm font-normal leading-normal inline-flex items-center self-center"
onClick={handleRefresh}
disabled={refreshing}
title={t("restartCertificate", {
defaultValue: "Restart Certificate"
})}
>
<span className="inline-flex items-center gap-2 leading-none">
<span className="inline-flex items-center gap-2 leading-normal">
<FileBadge
className={`h-4 w-4 shrink-0 ${getStatusColor(cert.status)}`}
aria-hidden
@@ -148,7 +149,7 @@ export function CertificateStatusContent({
{cert.status.charAt(0).toUpperCase() +
cert.status.slice(1)}
<RotateCw
className={`h-3 w-3 shrink-0 ${refreshing ? "animate-spin" : ""}`}
className={`h-4 w-4 shrink-0 ${refreshing ? "animate-spin" : ""}`}
/>
</span>
</Button>
@@ -164,7 +165,7 @@ export function CertificateStatusContent({
<Button
size="icon"
variant="ghost"
className="inline-flex h-auto min-h-0 w-3 shrink-0 items-center justify-center self-center p-0"
className="inline-flex h-4 w-4 min-h-0 shrink-0 items-center justify-center self-center p-0"
onClick={handleRefresh}
disabled={refreshing}
title={t("restartCertificate", {
@@ -172,7 +173,7 @@ export function CertificateStatusContent({
})}
>
<RotateCw
className={`h-3 w-3 shrink-0 ${refreshing ? "animate-spin" : ""}`}
className={`h-4 w-4 shrink-0 ${refreshing ? "animate-spin" : ""}`}
/>
</Button>
) : null}

View File

@@ -33,7 +33,7 @@ const CopyToClipboard = ({
<div className="flex items-center space-x-2 min-w-0 max-w-full">
<button
type="button"
className="h-6 w-6 p-0 flex items-center justify-center cursor-pointer flex-shrink-0"
className="h-4 w-4 p-0 flex items-center justify-center cursor-pointer flex-shrink-0"
onClick={handleCopy}
>
{!copied ? (

View File

@@ -104,7 +104,7 @@ export default function IdpLoginButtons({
</Alert>
)}
<div className="space-y-2">
<div className="space-y-4">
{params.get("gotoapp") ? (
<>
<Button

View File

@@ -19,7 +19,7 @@ export function InfoSections({
return (
<div
className={cn(
"grid grid-cols-2 md:grid-cols-(--columns) md:space-x-16 gap-4 md:items-start",
"grid w-full min-w-0 grid-cols-2 md:grid-cols-(--columns) md:space-x-16 gap-4 md:items-start",
columnSizing === "content" &&
"md:justify-items-start md:justify-start"
)}
@@ -41,7 +41,11 @@ export function InfoSection({
children: React.ReactNode;
className?: string;
}) {
return <div className={cn("space-y-1", className)}>{children}</div>;
return (
<div className={cn("min-w-0 w-full max-w-full space-y-1", className)}>
{children}
</div>
);
}
export function InfoSectionTitle({
@@ -51,7 +55,11 @@ export function InfoSectionTitle({
children: React.ReactNode;
className?: string;
}) {
return <div className={cn("font-semibold", className)}>{children}</div>;
return (
<div className={cn("min-w-0 truncate font-semibold", className)}>
{children}
</div>
);
}
export function InfoSectionContent({
@@ -62,8 +70,13 @@ export function InfoSectionContent({
className?: string;
}) {
return (
<div className={cn("min-w-0 overflow-hidden", className)}>
<div className="w-full truncate [&>div.flex]:min-w-0 [&>div.flex]:!whitespace-normal [&>div.flex>span]:truncate [&>div.flex>a]:truncate">
<div
className={cn(
"w-full min-w-0 max-w-full overflow-hidden",
className
)}
>
<div className="w-full min-w-0 max-w-full truncate [&>div.flex]:min-w-0 [&>div.flex]:!whitespace-normal [&>div.flex>span]:truncate [&>div.flex>a]:truncate">
{children}
</div>
</div>

View File

@@ -368,7 +368,7 @@ export default function LoginForm({
{hasIdp && (
<>
<div className="relative my-4">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator />
</div>

View File

@@ -145,7 +145,7 @@ export default function MfaInputForm({
</Alert>
)}
<div className="space-y-2">
<div className="space-y-4">
<Button
type="submit"
form={formId}

View File

@@ -528,7 +528,7 @@ export default function ResetPasswordForm({
)}
{state === "request" && (
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-4">
{env.email.emailEnabled && (
<Button
type="submit"

View File

@@ -40,7 +40,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
<InfoSection>
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
<InfoSectionContent>
{resource.niceId}
<span className="inline-flex items-center">
{resource.niceId}
</span>
</InfoSectionContent>
</InfoSection>
{resource.http ? (
@@ -49,7 +51,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
<InfoSectionTitle>URL</InfoSectionTitle>
<InfoSectionContent>
{resource.wildcard ? (
<span>{fullUrl}</span>
<span className="inline-flex items-center">
{fullUrl}
</span>
) : (
<CopyToClipboard
text={fullUrl}
@@ -68,7 +72,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
authInfo.sso ||
authInfo.whitelist ||
authInfo.headerAuth ? (
<div className="flex items-start space-x-2">
<div className="flex items-center space-x-2">
<ShieldCheck className="w-4 h-4 flex-shrink-0 text-green-500" />
<span>{t("protected")}</span>
</div>
@@ -106,7 +110,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
{t("protocol")}
</InfoSectionTitle>
<InfoSectionContent>
{resource.protocol.toUpperCase()}
<span className="inline-flex items-center">
{resource.protocol.toUpperCase()}
</span>
</InfoSectionContent>
</InfoSection>
<InfoSection>

View File

@@ -284,7 +284,7 @@ export default function SmartLoginForm({
{orgSignIn && (
<>
<div className="relative my-4">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator />
</div>

View File

@@ -147,7 +147,7 @@ export default function SmartLoginOrgSelector({
const response = await generateOidcUrlProxy(
idpId,
safeRedirect,
orgId,
undefined,
forceLogin
);
@@ -207,7 +207,7 @@ export default function SmartLoginOrgSelector({
/>
{hasInternalAccount && (
<div className="mt-3">
<div className="mt-4">
<Button
type="button"
className="w-full"
@@ -237,7 +237,7 @@ export default function SmartLoginOrgSelector({
</div>
</div>
<div className="space-y-2">
<div className="space-y-4">
{params.get("gotoapp") ? (
<Button
type="button"

View File

@@ -17,6 +17,7 @@ import { Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
import { validateOidcUrlCallbackProxy } from "@app/actions/server";
import { build } from "@server/build";
type ValidateOidcTokenParams = {
orgId: string;
@@ -96,7 +97,7 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) {
stateCookie: props.stateCookie
});
if (isLicenseViolation()) {
if (build === "enterprise" && isLicenseViolation()) {
await new Promise((resolve) => setTimeout(resolve, 5000));
}

View File

@@ -52,6 +52,10 @@ export function NewtSiteInstallCommands({
const acceptClientsEnv = !acceptClients
? "\n - DISABLE_CLIENTS=true"
: "";
const acceptClientsHelmValue = acceptClients
? ` \\
--set newtInstances[0].acceptClients=true`
: "";
const commandList: Record<Platform, Record<string, CommandItem[]>> = {
linux: {
@@ -162,13 +166,18 @@ sudo systemctl enable --now newt`
"Helm Chart": [
`helm repo add fossorial https://charts.fossorial.io`,
`helm repo update fossorial`,
`helm install newt fossorial/newt \\
--create-namespace \\
--set newtInstances[0].name="main-tunnel" \\
--set newtInstances[0].enabled=true \\
--set-string newtInstances[0].auth.keys.endpointKey="${endpoint}" \\
--set-string newtInstances[0].auth.keys.idKey="${id}" \\
--set-string newtInstances[0].auth.keys.secretKey="${secret}"`
`kubectl create namespace newt --dry-run=client -o yaml | kubectl apply -f -`,
`kubectl create secret generic newt-main-tunnel-auth \\
-n newt \\
--from-literal=PANGOLIN_ENDPOINT="${endpoint}" \\
--from-literal=NEWT_ID="${id}" \\
--from-literal=NEWT_SECRET="${secret}" \\
--dry-run=client -o yaml | kubectl apply -f -`,
`helm upgrade --install newt fossorial/newt \\
-n newt \\
--set newtInstances[0].name="main-tunnel" \\
--set newtInstances[0].enabled=true \\
--set-string newtInstances[0].auth.existingSecretName="newt-main-tunnel-auth"${acceptClientsHelmValue}`
]
},
podman: {