Compare commits

..

88 Commits

Author SHA1 Message Date
Owen Schwartz b1cd252ea6 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-17 13:56:08 -04:00
Owen Schwartz 0801c71cf2 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-17 13:56:06 -04:00
Owen Schwartz aea0a2779c New translations en-us.json (Turkish)
[ci skip]
2026-09-17 13:56:03 -04:00
Owen Schwartz 89909f3b35 New translations en-us.json (Russian)
[ci skip]
2026-09-17 13:56:01 -04:00
Owen Schwartz e54df0c592 New translations en-us.json (Portuguese)
[ci skip]
2026-09-17 13:55:58 -04:00
Owen Schwartz 8c579b69e2 New translations en-us.json (Polish)
[ci skip]
2026-09-17 13:55:56 -04:00
Owen Schwartz 1ab28781e3 New translations en-us.json (Dutch)
[ci skip]
2026-09-17 13:55:53 -04:00
Owen Schwartz 8c973e5455 New translations en-us.json (Korean)
[ci skip]
2026-09-17 13:55:51 -04:00
Owen Schwartz b1c26b700b New translations en-us.json (Italian)
[ci skip]
2026-09-17 13:55:49 -04:00
Owen Schwartz aada83e57f New translations en-us.json (German)
[ci skip]
2026-09-17 13:55:46 -04:00
Owen Schwartz 5fc03bc2e5 New translations en-us.json (Danish)
[ci skip]
2026-09-17 13:55:44 -04:00
Owen Schwartz a75cc9b723 New translations en-us.json (Czech)
[ci skip]
2026-09-17 13:55:42 -04:00
Owen Schwartz 6f1b3266c0 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-17 13:55:39 -04:00
Owen Schwartz 5681ec06cd New translations en-us.json (Spanish)
[ci skip]
2026-09-17 13:55:36 -04:00
Owen Schwartz f5b2058112 New translations en-us.json (French)
[ci skip]
2026-09-17 13:55:34 -04:00
Owen Schwartz 7f9fe3c67b New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-16 13:35:12 -04:00
Owen Schwartz b617670fd8 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-16 13:35:10 -04:00
Owen Schwartz 53d4433d87 New translations en-us.json (Turkish)
[ci skip]
2026-09-16 13:35:08 -04:00
Owen Schwartz 36bbb652cc New translations en-us.json (Russian)
[ci skip]
2026-09-16 13:35:06 -04:00
Owen Schwartz 0394289aab New translations en-us.json (Portuguese)
[ci skip]
2026-09-16 13:35:03 -04:00
Owen Schwartz 52e9a8d43f New translations en-us.json (Polish)
[ci skip]
2026-09-16 13:35:01 -04:00
Owen Schwartz 72cfe81727 New translations en-us.json (Dutch)
[ci skip]
2026-09-16 13:34:59 -04:00
Owen Schwartz ed1a7c2723 New translations en-us.json (Korean)
[ci skip]
2026-09-16 13:34:57 -04:00
Owen Schwartz 8a05885192 New translations en-us.json (Italian)
[ci skip]
2026-09-16 13:34:55 -04:00
Owen Schwartz fed9d4b4b5 New translations en-us.json (German)
[ci skip]
2026-09-16 13:34:52 -04:00
Owen Schwartz b0d338456f New translations en-us.json (Danish)
[ci skip]
2026-09-16 13:34:50 -04:00
Owen Schwartz 9442ad2214 New translations en-us.json (Czech)
[ci skip]
2026-09-16 13:34:48 -04:00
Owen Schwartz 3fbe8d0618 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-16 13:34:46 -04:00
Owen Schwartz 86a9eadcff New translations en-us.json (Spanish)
[ci skip]
2026-09-16 13:34:43 -04:00
Owen Schwartz 6a7be975ed New translations en-us.json (French)
[ci skip]
2026-09-16 13:34:41 -04:00
Owen ee3f9efa27 Update link 2026-09-15 17:16:57 -04:00
Owen c32ef54428 Install go 1.26 2026-09-15 16:56:46 -04:00
Owen Schwartz bda09a1b74 Merge pull request #3755 from fosrl/l10n_dev
New Crowdin updates
2026-09-15 16:43:18 -04:00
Owen Schwartz 178d1d9779 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-15 16:42:51 -04:00
Owen Schwartz d311eb6e8c New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-15 16:42:49 -04:00
Owen Schwartz 829680e984 New translations en-us.json (Turkish)
[ci skip]
2026-09-15 16:42:47 -04:00
Owen Schwartz be10dcd26e New translations en-us.json (Russian)
[ci skip]
2026-09-15 16:42:45 -04:00
Owen Schwartz cc23e8cf5d New translations en-us.json (Portuguese)
[ci skip]
2026-09-15 16:42:42 -04:00
Owen Schwartz fc77b5bb3e New translations en-us.json (Polish)
[ci skip]
2026-09-15 16:42:40 -04:00
Owen Schwartz 651acae326 New translations en-us.json (Dutch)
[ci skip]
2026-09-15 16:42:38 -04:00
Owen Schwartz 75332a1d1f New translations en-us.json (Korean)
[ci skip]
2026-09-15 16:42:36 -04:00
Owen Schwartz 0f1631f592 New translations en-us.json (Italian)
[ci skip]
2026-09-15 16:42:34 -04:00
Owen Schwartz f1a303de14 New translations en-us.json (German)
[ci skip]
2026-09-15 16:42:31 -04:00
Owen Schwartz 191c062cec New translations en-us.json (Danish)
[ci skip]
2026-09-15 16:42:29 -04:00
Owen Schwartz 1839ea7ca3 New translations en-us.json (Czech)
[ci skip]
2026-09-15 16:42:26 -04:00
Owen Schwartz a3c2c98c5e New translations en-us.json (Bulgarian)
[ci skip]
2026-09-15 16:42:24 -04:00
Owen Schwartz 43e42f90f8 New translations en-us.json (Spanish)
[ci skip]
2026-09-15 16:42:22 -04:00
Owen Schwartz a954ba349b New translations en-us.json (French)
[ci skip]
2026-09-15 16:42:20 -04:00
Owen Schwartz f07aef4ace New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-15 16:35:29 -04:00
Owen Schwartz 4afa01aba4 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-15 16:35:27 -04:00
Owen Schwartz 9b15e99cfb New translations en-us.json (Turkish)
[ci skip]
2026-09-15 16:35:24 -04:00
Owen Schwartz a322d0479c New translations en-us.json (Russian)
[ci skip]
2026-09-15 16:35:22 -04:00
Owen Schwartz bb9761a171 New translations en-us.json (Portuguese)
[ci skip]
2026-09-15 16:35:19 -04:00
Owen Schwartz af4d80edec New translations en-us.json (Polish)
[ci skip]
2026-09-15 16:35:17 -04:00
Owen Schwartz da434f63b4 New translations en-us.json (Dutch)
[ci skip]
2026-09-15 16:35:15 -04:00
Owen Schwartz 9718c3549b New translations en-us.json (Korean)
[ci skip]
2026-09-15 16:35:13 -04:00
Owen Schwartz d550eef6c2 New translations en-us.json (Italian)
[ci skip]
2026-09-15 16:35:11 -04:00
Owen Schwartz c76b591f9f New translations en-us.json (German)
[ci skip]
2026-09-15 16:35:08 -04:00
Owen Schwartz a1b7c69ba5 New translations en-us.json (Danish)
[ci skip]
2026-09-15 16:35:06 -04:00
Owen Schwartz 20e5d537b0 New translations en-us.json (Czech)
[ci skip]
2026-09-15 16:35:04 -04:00
Owen Schwartz 197cfdc339 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-15 16:35:01 -04:00
Owen Schwartz 418bb26ee6 New translations en-us.json (Spanish)
[ci skip]
2026-09-15 16:34:59 -04:00
Owen Schwartz 4dfa81416b New translations en-us.json (French)
[ci skip]
2026-09-15 16:34:57 -04:00
Owen 324f3e50ff Add license tiers 2026-09-15 16:25:53 -04:00
miloschwartz 64bb6d9f9c supress healthcheck log message 2026-09-15 15:02:37 -04:00
Owen e54d1aa73e Include .npmrc 2026-09-15 10:59:47 -04:00
Owen c6c12f1dcb Support _FILE env vars
Closes https://github.com/fosrl/docs-v2/issues/141
2026-09-15 10:56:39 -04:00
Owen 4b30911f06 Update renamed table to month_grid 2026-09-15 10:25:34 -04:00
Owen c6c443f0e0 Remove optional before prefault for zod v4 2026-09-15 10:25:21 -04:00
Owen 24a06dd8ca Rename package import 2026-09-15 10:25:06 -04:00
Owen c11bbe670f Remove optional() where unneeded for zod 4 2026-09-15 10:23:37 -04:00
Owen 7ed646c02b Resolve eslint old ts issues and revert tanstack table 2026-09-15 10:23:22 -04:00
Owen 9d5dd526bc Pin typescript 2026-09-15 10:06:16 -04:00
Owen 9e25650f22 Fix service missing traefik issues 2026-09-15 10:04:00 -04:00
Owen 143b4cf757 Atomically write files to avoid races with traefik 2026-09-15 10:04:00 -04:00
Owen Schwartz 25b151a021 Merge pull request #3748 from fosrl/dependabot/npm_and_yarn/npm-dependencies-eb7d53c756
Bump the npm-dependencies group across 1 directory with 29 updates
2026-09-15 10:03:44 -04:00
dependabot[bot] c62fb6cbe0 Bump the npm-dependencies group across 1 directory with 29 updates
Bumps the npm-dependencies group with 29 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1121.0` | `3.1131.0` |
| [@node-rs/argon2](https://github.com/napi-rs/node-rs) | `2.2.0` | `2.2.1` |
| [@simplewebauthn/browser](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/browser) | `13.3.0` | `14.0.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.3` | `14.0.1` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.2.4` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.38.0` | `1.45.0` |
| [next](https://github.com/vercel/next.js) | `16.3.4` | `16.3.5` |
| [next-intl](https://github.com/amannn/next-intl) | `4.14.1` | `4.14.4` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `9.1.1` | `10.0.9` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.51.4` | `5.52.1` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.8` | `19.3.0` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.18` | `19.3.0` |
| [react-day-picker](https://github.com/gpbl/react-day-picker/tree/HEAD/packages/react-day-picker) | `9.14.0` | `10.0.1` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.8` | `19.3.0` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.3.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.87.0` | `7.88.0` |
| [stripe](https://github.com/stripe/stripe-node) | `22.6.0` | `22.6.2` |
| [yaml](https://github.com/eemeli/yaml) | `2.9.0` | `2.9.1` |
| [zod](https://github.com/colinhacks/zod) | `4.5.4` | `4.6.2` |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `2.23.0` | `2.24.1` |
| [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui) | `6.9.3` | `6.9.5` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.4.0` | `26.5.1` |
| [eslint](https://github.com/eslint/eslint) | `10.9.1` | `10.10.0` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.3.3` | `16.3.5` |
| [postcss](https://github.com/postcss/postcss) | `8.5.26` | `8.5.28` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.9.3` | `6.9.5` |
| [tsc-alias](https://github.com/justkey007/tsc-alias) | `1.9.2` | `1.9.5` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.68.0` | `8.70.0` |



Updates `@aws-sdk/client-s3` from 3.1121.0 to 3.1131.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1131.0/clients/client-s3)

Updates `@node-rs/argon2` from 2.2.0 to 2.2.1
- [Release notes](https://github.com/napi-rs/node-rs/releases)
- [Commits](https://github.com/napi-rs/node-rs/compare/@node-rs/argon2@2.2.0...@node-rs/argon2@2.2.1)

Updates `@simplewebauthn/browser` from 13.3.0 to 14.0.0
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v14.0.0/packages/browser)

Updates `@simplewebauthn/server` from 13.3.3 to 14.0.1
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v14.0.1/packages/server)

Updates `@tanstack/react-table` from 8.21.3 to 9.2.4
- [Release notes](https://github.com/TanStack/table/releases)
- [Changelog](https://github.com/TanStack/table/blob/main/packages/react-table/CHANGELOG.md)
- [Commits](https://github.com/TanStack/table/commits/@tanstack/react-table@9.2.4/packages/react-table)

Updates `lucide-react` from 1.38.0 to 1.45.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.45.0/packages/lucide-react)

Updates `next` from 16.3.4 to 16.3.5
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.4...v16.3.5)

Updates `next-intl` from 4.14.1 to 4.14.4
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.14.1...v4.14.4)

Updates `nodemailer` from 9.1.1 to 10.0.9
- [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/v9.1.1...v10.0.9)

Updates `posthog-node` from 5.51.4 to 5.52.1
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.52.1/packages/node)

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

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-day-picker` from 9.14.0 to 10.0.1
- [Release notes](https://github.com/gpbl/react-day-picker/releases)
- [Changelog](https://github.com/gpbl/react-day-picker/blob/main/packages/react-day-picker/CHANGELOG.md)
- [Commits](https://github.com/gpbl/react-day-picker/commits/v10.0.1/packages/react-day-picker)

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

Updates `@types/react-dom` from 19.2.5 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `react-hook-form` from 7.87.0 to 7.88.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.87.0...v7.88.0)

Updates `stripe` from 22.6.0 to 22.6.2
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v22.6.0...v22.6.2)

Updates `yaml` from 2.9.0 to 2.9.1
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.9.0...v2.9.1)

Updates `zod` from 4.5.4 to 4.6.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.5.4...v4.6.2)

Updates `@dotenvx/dotenvx` from 2.23.0 to 2.24.1
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v2.23.0...v2.24.1)

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

Updates `@types/node` from 26.4.0 to 26.5.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.5 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `eslint` from 10.9.1 to 10.10.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.9.1...v10.10.0)

Updates `eslint-config-next` from 16.3.3 to 16.3.5
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next)

Updates `postcss` from 8.5.26 to 8.5.28
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.26...8.5.28)

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

Updates `tsc-alias` from 1.9.2 to 1.9.5
- [Release notes](https://github.com/justkey007/tsc-alias/releases)
- [Commits](https://github.com/justkey007/tsc-alias/compare/v1.9.2...v1.9.5)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2)

Updates `typescript-eslint` from 8.68.0 to 8.70.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1131.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 2.24.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@node-rs/argon2"
  dependency-version: 2.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@react-email/ui"
  dependency-version: 6.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/browser"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 14.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-table"
  dependency-version: 9.2.4
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-config-next
  dependency-version: 16.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: next
  dependency-version: 16.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 10.0.8
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: postcss
  dependency-version: 8.5.28
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.52.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-day-picker
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: react-dom
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-email
  dependency-version: 6.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.88.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: stripe
  dependency-version: 22.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tsc-alias
  dependency-version: 1.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.70.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: yaml
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: zod
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-15 14:00:26 +00:00
Owen Schwartz e561c9f334 Merge pull request #3744 from fosrl/dependabot/npm_and_yarn/next-16.3.4
Bump next from 16.3.3 to 16.3.4
2026-09-15 09:45:14 -04:00
dependabot[bot] 2b970ce6e7 Bump next from 16.3.3 to 16.3.4
Bumps [next](https://github.com/vercel/next.js) from 16.3.3 to 16.3.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.3...v16.3.4)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.3.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-15 13:41:09 +00:00
Owen Schwartz e8cb0297a1 Merge pull request #3735 from fosrl/dependabot/go_modules/install/go-install-dependencies-19034c9a63
Bump golang.org/x/term from 0.45.0 to 0.46.0 in /install in the go-install-dependencies group across 1 directory
2026-09-15 09:40:28 -04:00
Owen Schwartz 4f7e993e70 Merge pull request #3714 from aithal007/fix/ci-pin-node-runtime-version
fix(ci): pin Node to 24.18.1 to match runtime and fix flaky test job
2026-09-15 09:40:01 -04:00
Owen Schwartz 8dc3a1378a Merge pull request #3721 from fosrl/dependabot/npm_and_yarn/nodemailer-9.1.1
Bump nodemailer from 9.1.0 to 9.1.1
2026-09-15 09:38:47 -04:00
Owen Schwartz 3713b714e4 Merge pull request #3722 from fosrl/dependabot/npm_and_yarn/sharp-0.35.4
Bump sharp from 0.35.3 to 0.35.4
2026-09-15 09:38:17 -04:00
dependabot[bot] 098157f671 Bump golang.org/x/term
Bumps the go-install-dependencies group with 1 update in the /install directory: [golang.org/x/term](https://github.com/golang/term).


Updates `golang.org/x/term` from 0.45.0 to 0.46.0
- [Commits](https://github.com/golang/term/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-install-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-15 01:33:16 +00:00
dependabot[bot] ea1b3e5270 Bump nodemailer from 9.1.0 to 9.1.1
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 9.1.0 to 9.1.1.
- [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/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 9.1.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-14 21:55:28 +00:00
dependabot[bot] 899620a890 Bump sharp from 0.35.3 to 0.35.4
Bumps [sharp](https://github.com/lovell/sharp) from 0.35.3 to 0.35.4.
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: sharp
  dependency-version: 0.35.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-11 04:34:02 +00:00
Parikshith c19cb706e8 fix(ci): harden app-readiness PID check; clarify version-source comment
Address review feedback on the readiness step:
- Read the PID safely (`cat app.pid 2>/dev/null || true`) and treat a
  missing/empty PID as "not running", so a failed nohup no longer produces a
  confusing `kill` usage error and misleading message.
- Guard the diagnostic dump behind a helper so a missing app.log prints
  "(app.log not found)" instead of erroring.
- The exact 24.18.1 pin comes from the Dockerfiles; .nvmrc only pins the 24
  major. Corrected the comments to say so rather than citing .nvmrc as the
  source of the patch version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 16:28:35 +05:30
Parikshith 938d7f145c fix(ci): pin Node to 24.18.1 to match runtime; surface app startup logs
The `test` job intermittently fails at "Wait for app availability" because
`npm run dev` crashes on startup:

    node::RemoveEnvironmentCleanupHook(...) at ../src/api/hooks.cc:142
    Assertion failed: (env) != nullptr
      Statement::~Statement()  [better-sqlite3/build/Release/better_sqlite3.node]

Node >= 24.19.0 added node::ObjectWrap cleanup hooks that are incompatible
with NAN-style native addons such as better-sqlite3 (11.9.1, this project's
pinned version), tripping that assertion on teardown. It is intermittent (a
race during native cleanup), so the job fails on some runs and passes on
others.

The app ships on Node 24.18.1 (both Dockerfiles and .nvmrc), which predates
the breaking change, so production is unaffected. Only CI hit it, because
`setup-node` with `node-version: '24'` floats to the latest 24.x (24.20.0 at
time of failure). Pinning CI to 24.18.1 makes it test the version that
actually ships and avoids the regression.

Also capture `npm run dev` output to app.log and fail fast (printing it) if
the process exits early, so a future startup failure is diagnosable instead
of surfacing only as "App failed to start" after a 25s wait.

(Longer term, upgrading better-sqlite3 to v12+ restores Node 24.19+ support;
pinning the runtime is the minimal, prod-matching fix.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 16:22:39 +05:30
55 changed files with 3145 additions and 2218 deletions
+1 -1
View File
@@ -266,7 +266,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with: with:
go-version: 1.25 go-version: 1.26
- name: Update version in package.json - name: Update version in package.json
run: | run: |
+4 -1
View File
@@ -26,7 +26,10 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version: '24' # Match the version the app ships on (Dockerfile /
# Dockerfile.dev both use 24.18.1) rather than floating
# to the latest 24.x.
node-version: '24.18.1'
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
+24 -3
View File
@@ -19,7 +19,13 @@ jobs:
- name: Install Node - name: Install Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version: '24' # Pin to the version the app actually ships on (Dockerfile /
# Dockerfile.dev both use 24.18.1; .nvmrc pins the 24 major).
# A bare '24' floats to the latest 24.x; Node >= 24.19.0 added
# node::ObjectWrap cleanup hooks that crash better-sqlite3 on teardown
# ("Assertion failed: (env) != nullptr" in RemoveEnvironmentCleanupHook),
# which intermittently kills `npm run dev` in the step below.
node-version: '24.18.1'
- name: Copy config file - name: Copy config file
run: cp config/config.example.yml config/config.yml run: cp config/config.example.yml config/config.yml
@@ -43,19 +49,34 @@ jobs:
run: npx tsc --noEmit run: npx tsc --noEmit
- name: Start app in background - name: Start app in background
run: nohup npm run dev & run: |
nohup npm run dev > app.log 2>&1 &
echo $! > app.pid
- name: Wait for app availability - name: Wait for app availability
run: | run: |
print_log() {
if [ -f app.log ]; then cat app.log; else echo "(app.log not found)"; fi
}
for i in {1..5}; do for i in {1..5}; do
if curl --silent --fail http://localhost:3002/auth/login; then if curl --silent --fail http://localhost:3002/auth/login; then
echo "App is up" echo "App is up"
exit 0 exit 0
fi fi
# Fail fast (and show why) if the process is gone. Read the PID
# safely: if it's missing/empty (e.g. nohup never started), treat
# that as "not running" instead of passing a bad arg to kill.
pid="$(cat app.pid 2>/dev/null || true)"
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
echo "App process exited before becoming available. Output:"
print_log
exit 1
fi
echo "Waiting for the app... attempt $i" echo "Waiting for the app... attempt $i"
sleep 5 sleep 5
done done
echo "App failed to start" echo "App failed to start. Output:"
print_log
exit 1 exit 1
build-sqlite: build-sqlite:
+4
View File
@@ -0,0 +1,4 @@
# typescript-eslint@8.70.0 declares a peer range of typescript "<6.1.0" and
# hasn't caught up to typescript@7.x yet, even though it works fine against it
# in practice. Without this, `npm install`/`npm ci` fail with ERESOLVE.
legacy-peer-deps=true
+1 -1
View File
@@ -5,7 +5,7 @@ WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
COPY package*.json ./ COPY package*.json .npmrc ./
FROM base AS builder-dev FROM base AS builder-dev
+1 -1
View File
@@ -4,7 +4,7 @@ WORKDIR /app
RUN apk add --no-cache python3 make g++ RUN apk add --no-cache python3 make g++
COPY package*.json ./ COPY package*.json .npmrc ./
# Install dependencies # Install dependencies
RUN npm ci RUN npm ci
+1 -1
View File
@@ -1,3 +1,3 @@
## Example Docker Reference HA Deployment ## Example Docker Reference HA Deployment
This directory contains basic config for a highly available deployment of Pangolin with two nodes. For more information [refer to the docs](/self-host/clustering/understanding-clustering). This directory contains a basic reference config for a highly available deployment of Pangolin with two nodes. For more information [refer to the docs](https://docs.pangolin.net/self-host/clustering/understanding-clustering).
+3 -3
View File
@@ -1,11 +1,11 @@
module installer module installer
go 1.25.0 go 1.26.0
require ( require (
github.com/charmbracelet/huh v1.0.0 github.com/charmbracelet/huh v1.0.0
github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/lipgloss v1.1.0
golang.org/x/term v0.45.0 golang.org/x/term v0.46.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -33,6 +33,6 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.47.0 // indirect golang.org/x/sys v0.48.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
) )
+4 -4
View File
@@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Сигурни ли сте, че искате да рестартирате WireGuard тунела за <b>{name}</b>? Сайтът ще изгуби връзка за кратко.", "siteRestartDialogMessage": "Сигурни ли сте, че искате да рестартирате WireGuard тунела за <b>{name}</b>? Сайтът ще изгуби връзка за кратко.",
"siteRestartWarning": "Сайтът ще се изключи за кратко, докато тунелът се рестартира.", "siteRestartWarning": "Сайтът ще се изключи за кратко, докато тунелът се рестартира.",
"siteRestarted": "Сайтът е рестартиран", "siteRestarted": "Сайтът е рестартиран",
"siteRestartedDescription": "WireGuard тунелът е рестартиран.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Неуспешно рестартиране на сайта", "siteErrorRestart": "Неуспешно рестартиране на сайта",
"siteErrorRestartDescription": "Възникна грешка при рестартирането на сайта.", "siteErrorRestartDescription": "Възникна грешка при рестартирането на сайта.",
"siteSettingDescription": "Конфигурирайте настройките на сайта", "siteSettingDescription": "Конфигурирайте настройките на сайта",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.", "clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.",
"privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.", "privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.",
"privateResourcesBannerDescription": "Частните ресурси използват сигурност с нулево доверие, осигурявайки че потребителите и машините могат да имат само достъп до ресурси, които вие изрично предоставяте. Свържете потребителските устройства или машинните клиенти, за да получите достъп до тези ресурси чрез сигурна виртуална частна мрежа.", "privateResourcesBannerDescription": "Частните ресурси използват сигурност с нулево доверие, осигурявайки че потребителите и машините могат да имат само достъп до ресурси, които вие изрично предоставяте. Свържете потребителските устройства или машинните клиенти, за да получите достъп до тези ресурси чрез сигурна виртуална частна мрежа.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Търсене на ресурси...", "resourcesSearch": "Търсене на ресурси...",
"resourceAdd": "Добавете ресурс", "resourceAdd": "Добавете ресурс",
"resourceErrorDelte": "Грешка при изтриване на ресурс", "resourceErrorDelte": "Грешка при изтриване на ресурс",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Няма намерени сайтове.", "sitesNotFound": "Няма намерени сайтове.",
"pangolinServerAdmin": "Администратор на сървър - Панголин", "pangolinServerAdmin": "Администратор на сървър - Панголин",
"licenseTierProfessional": "Професионален лиценз", "licenseTierProfessional": "Професионален лиценз",
"licenseTierEnterprise": "Предприятие лиценз", "licenseTierEnterprise": "Предприятие",
"licenseTierPersonal": "Персонален лиценз", "licenseTierPersonal": "Личен",
"licenseTierTier1": "Начален",
"licenseTierTier2": "Мащаб",
"licensed": "Лицензиран", "licensed": "Лицензиран",
"yes": "Да", "yes": "Да",
"no": "Не", "no": "Не",
+9 -4
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Opravdu chcete restartovat WireGuard tunel pro <b>{name}</b>? Lokalita krátce ztratí konektivitu.", "siteRestartDialogMessage": "Opravdu chcete restartovat WireGuard tunel pro <b>{name}</b>? Lokalita krátce ztratí konektivitu.",
"siteRestartWarning": "Lokalita bude krátce odpojena, zatímco se tunel restartuje.", "siteRestartWarning": "Lokalita bude krátce odpojena, zatímco se tunel restartuje.",
"siteRestarted": "Lokalita restartována", "siteRestarted": "Lokalita restartována",
"siteRestartedDescription": "Tunel WireGuard byl restartován.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Nepodařilo se restartovat lokalitu", "siteErrorRestart": "Nepodařilo se restartovat lokalitu",
"siteErrorRestartDescription": "Při restartování lokality došlo k chybě.", "siteErrorRestartDescription": "Při restartování lokality došlo k chybě.",
"siteSettingDescription": "Konfigurace nastavení na webu", "siteSettingDescription": "Konfigurace nastavení na webu",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta", "clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta",
"privateResourcesBannerTitle": "Zero-Trust soukromý přístup", "privateResourcesBannerTitle": "Zero-Trust soukromý přístup",
"privateResourcesBannerDescription": "Soukromé prostředky používají zero-trust zabezpečení, což zajišťuje, že uživatelé a zařízení mohou získat přístup pouze k prostředkům, k nimž máte explicitní práva. Připojte zařízení uživatele nebo klientské stroje, abyste získali přístup k těmto prostředkům přes zabezpečenou virtuální soukromou síť.", "privateResourcesBannerDescription": "Soukromé prostředky používají zero-trust zabezpečení, což zajišťuje, že uživatelé a zařízení mohou získat přístup pouze k prostředkům, k nimž máte explicitní práva. Připojte zařízení uživatele nebo klientské stroje, abyste získali přístup k těmto prostředkům přes zabezpečenou virtuální soukromou síť.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Prohledat zdroje...", "resourcesSearch": "Prohledat zdroje...",
"resourceAdd": "Přidat zdroj", "resourceAdd": "Přidat zdroj",
"resourceErrorDelte": "Chyba při odstraňování zdroje", "resourceErrorDelte": "Chyba při odstraňování zdroje",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Nebyly nalezeny žádné stránky.", "sitesNotFound": "Nebyly nalezeny žádné stránky.",
"pangolinServerAdmin": "Správce serveru - Pangolin", "pangolinServerAdmin": "Správce serveru - Pangolin",
"licenseTierProfessional": "Profesionální licence", "licenseTierProfessional": "Profesionální licence",
"licenseTierEnterprise": "Podniková licence", "licenseTierEnterprise": "Podniky",
"licenseTierPersonal": "Osobní licence", "licenseTierPersonal": "Osobní",
"licenseTierTier1": "Počáteční",
"licenseTierTier2": "Měřítko",
"licensed": "Licencováno", "licensed": "Licencováno",
"yes": "Ano", "yes": "Ano",
"no": "Ne", "no": "Ne",
@@ -3979,7 +3984,7 @@
"tpmAvailable": "TPM k dispozici", "tpmAvailable": "TPM k dispozici",
"windowsAntivirusEnabled": "Antivirus povolen", "windowsAntivirusEnabled": "Antivirus povolen",
"macosSipEnabled": "Ochrana systémové integrity (SIP)", "macosSipEnabled": "Ochrana systémové integrity (SIP)",
"macosGatekeeperEnabled": "Gatekeeper", "macosGatekeeperEnabled": "Strážce",
"macosFirewallStealthMode": "Režim neviditelnosti firewallu", "macosFirewallStealthMode": "Režim neviditelnosti firewallu",
"linuxAppArmorEnabled": "Pancíř aplikace", "linuxAppArmorEnabled": "Pancíř aplikace",
"linuxSELinuxEnabled": "SELinux", "linuxSELinuxEnabled": "SELinux",
+9 -4
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Er du sikker på, at du vil genstarte WireGuard-tunnelen for <b>{name}</b>? Sitet vil kortvarigt miste forbindelsen.", "siteRestartDialogMessage": "Er du sikker på, at du vil genstarte WireGuard-tunnelen for <b>{name}</b>? Sitet vil kortvarigt miste forbindelsen.",
"siteRestartWarning": "Sitet vil kortvarigt afbryde forbindelse, mens tunnelen genstarter.", "siteRestartWarning": "Sitet vil kortvarigt afbryde forbindelse, mens tunnelen genstarter.",
"siteRestarted": "Site genstartet", "siteRestarted": "Site genstartet",
"siteRestartedDescription": "WireGuard-tunnelen er blevet genstartet.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Kunne ikke genstarte site", "siteErrorRestart": "Kunne ikke genstarte site",
"siteErrorRestartDescription": "En fejl opstod, mens sitet blev genstartet.", "siteErrorRestartDescription": "En fejl opstod, mens sitet blev genstartet.",
"siteSettingDescription": "Konfigurer indstillingerne for sitet", "siteSettingDescription": "Konfigurer indstillingerne for sitet",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Oprette og administrer ressourcer som kun er tilgængelige via en tilsluttet klient", "clientResourceDescription": "Oprette og administrer ressourcer som kun er tilgængelige via en tilsluttet klient",
"privateResourcesBannerTitle": "Zero-Trust privat adgang", "privateResourcesBannerTitle": "Zero-Trust privat adgang",
"privateResourcesBannerDescription": "Private ressourcer bruger Zero-Trust-sikkerhed og sikrer, at brugere og maskiner kun kan få adgang til ressourcer, du eksplicit giver tilladelse til. Tilslut brugerenheder eller maskinklienter for at få adgang til disse ressourcer via et sikkert virtuelt privat netværk.", "privateResourcesBannerDescription": "Private ressourcer bruger Zero-Trust-sikkerhed og sikrer, at brugere og maskiner kun kan få adgang til ressourcer, du eksplicit giver tilladelse til. Tilslut brugerenheder eller maskinklienter for at få adgang til disse ressourcer via et sikkert virtuelt privat netværk.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Søg i ressourcer...", "resourcesSearch": "Søg i ressourcer...",
"resourceAdd": "Tilføj ressource", "resourceAdd": "Tilføj ressource",
"resourceErrorDelte": "Fejl ved sletning af ressource", "resourceErrorDelte": "Fejl ved sletning af ressource",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Ingen sites fundet.", "sitesNotFound": "Ingen sites fundet.",
"pangolinServerAdmin": "Serveradmin - Pangolin", "pangolinServerAdmin": "Serveradmin - Pangolin",
"licenseTierProfessional": "Professionel licens", "licenseTierProfessional": "Professionel licens",
"licenseTierEnterprise": "Enterprise-licens", "licenseTierEnterprise": "Bedrift",
"licenseTierPersonal": "Personlig licens", "licenseTierPersonal": "Personlig",
"licenseTierTier1": "Begynder",
"licenseTierTier2": "Skala",
"licensed": "Licenseret", "licensed": "Licenseret",
"yes": "Ja", "yes": "Ja",
"no": "Nej", "no": "Nej",
@@ -2068,7 +2073,7 @@
"aiUsageFilterNotFound": "Ingen valg fundet", "aiUsageFilterNotFound": "Ingen valg fundet",
"aiUsageResetFilters": "Nulstil Filtre", "aiUsageResetFilters": "Nulstil Filtre",
"aiUsageRefresh": "Opdater", "aiUsageRefresh": "Opdater",
"aiUsageTokenTypePrompt": "Prompt", "aiUsageTokenTypePrompt": "Vis",
"aiUsageTokenTypeCacheRead": "Cache læs", "aiUsageTokenTypeCacheRead": "Cache læs",
"aiUsageTokenTypeCacheWrite": "Cache skriv", "aiUsageTokenTypeCacheWrite": "Cache skriv",
"aiUsageTokenTypeCompletion": "Komplettering", "aiUsageTokenTypeCompletion": "Komplettering",
+9 -4
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Sind Sie sicher, dass Sie den WireGuard-Tunnel für <b>{name}</b> neu starten möchten? Der Standort wird kurzzeitig die Konnektivität verlieren.", "siteRestartDialogMessage": "Sind Sie sicher, dass Sie den WireGuard-Tunnel für <b>{name}</b> neu starten möchten? Der Standort wird kurzzeitig die Konnektivität verlieren.",
"siteRestartWarning": "Der Standort wird kurzzeitig getrennt, während der Tunnel neu gestartet wird.", "siteRestartWarning": "Der Standort wird kurzzeitig getrennt, während der Tunnel neu gestartet wird.",
"siteRestarted": "Standort neu gestartet", "siteRestarted": "Standort neu gestartet",
"siteRestartedDescription": "Der WireGuard-Tunnel wurde neu gestartet.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Fehler beim Neustart des Standorts", "siteErrorRestart": "Fehler beim Neustart des Standorts",
"siteErrorRestartDescription": "Ein Fehler ist aufgetreten, während der Standort neu gestartet wurde.", "siteErrorRestartDescription": "Ein Fehler ist aufgetreten, während der Standort neu gestartet wurde.",
"siteSettingDescription": "Standorteinstellungen konfigurieren", "siteSettingDescription": "Standorteinstellungen konfigurieren",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind", "clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind",
"privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen", "privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen",
"privateResourcesBannerDescription": "Private Ressourcen nutzen Zero-Trust und stellen sicher, dass Benutzer und Maschinen nur auf Ressourcen zugreifen können, die Sie explizit gewähren. Verbinden Sie Benutzergeräte oder Maschinen-Clients, um auf diese Ressourcen über ein sicheres virtuelles privates Netzwerk zuzugreifen.", "privateResourcesBannerDescription": "Private Ressourcen nutzen Zero-Trust und stellen sicher, dass Benutzer und Maschinen nur auf Ressourcen zugreifen können, die Sie explizit gewähren. Verbinden Sie Benutzergeräte oder Maschinen-Clients, um auf diese Ressourcen über ein sicheres virtuelles privates Netzwerk zuzugreifen.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Suche Ressourcen...", "resourcesSearch": "Suche Ressourcen...",
"resourceAdd": "Ressource hinzufügen", "resourceAdd": "Ressource hinzufügen",
"resourceErrorDelte": "Fehler beim Löschen der Ressource", "resourceErrorDelte": "Fehler beim Löschen der Ressource",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Keine Standorte gefunden.", "sitesNotFound": "Keine Standorte gefunden.",
"pangolinServerAdmin": "Server-Admin - Pangolin", "pangolinServerAdmin": "Server-Admin - Pangolin",
"licenseTierProfessional": "Professional Lizenz", "licenseTierProfessional": "Professional Lizenz",
"licenseTierEnterprise": "Enterprise Lizenz", "licenseTierEnterprise": "Firma",
"licenseTierPersonal": "Persönliche Lizenz", "licenseTierPersonal": "Persönlich",
"licenseTierTier1": "Starter",
"licenseTierTier2": "Maßstab",
"licensed": "Lizenziert", "licensed": "Lizenziert",
"yes": "Ja", "yes": "Ja",
"no": "Nein", "no": "Nein",
@@ -3205,7 +3210,7 @@
"idpAzureClientIdDescription2": "Azure App Registration Client ID", "idpAzureClientIdDescription2": "Azure App Registration Client ID",
"idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis", "idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis",
"idpGoogleDescription": "Google OAuth2/OIDC Provider", "idpGoogleDescription": "Google OAuth2/OIDC Provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC-Anbieter",
"subnet": "Subnetz", "subnet": "Subnetz",
"utilitySubnet": "Nutzsubnetz", "utilitySubnet": "Nutzsubnetz",
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.", "subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
+4 -2
View File
@@ -1118,8 +1118,10 @@
"sitesNotFound": "No sites found.", "sitesNotFound": "No sites found.",
"pangolinServerAdmin": "Server Admin - Pangolin", "pangolinServerAdmin": "Server Admin - Pangolin",
"licenseTierProfessional": "Professional License", "licenseTierProfessional": "Professional License",
"licenseTierEnterprise": "Enterprise License", "licenseTierEnterprise": "Enterprise",
"licenseTierPersonal": "Personal License", "licenseTierPersonal": "Personal",
"licenseTierTier1": "Starter",
"licenseTierTier2": "Scale",
"licensed": "Licensed", "licensed": "Licensed",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "¿Estás seguro de que deseas reiniciar el túnel WireGuard para <b>{name}</b>? El sitio perderá conectividad brevemente.", "siteRestartDialogMessage": "¿Estás seguro de que deseas reiniciar el túnel WireGuard para <b>{name}</b>? El sitio perderá conectividad brevemente.",
"siteRestartWarning": "El sitio se desconectará brevemente mientras se reinicia el túnel.", "siteRestartWarning": "El sitio se desconectará brevemente mientras se reinicia el túnel.",
"siteRestarted": "Sitio reiniciado", "siteRestarted": "Sitio reiniciado",
"siteRestartedDescription": "El túnel WireGuard ha sido reiniciado.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Error al reiniciar el sitio", "siteErrorRestart": "Error al reiniciar el sitio",
"siteErrorRestartDescription": "Se ha producido un error al reiniciar el sitio.", "siteErrorRestartDescription": "Se ha producido un error al reiniciar el sitio.",
"siteSettingDescription": "Configurar los ajustes en el sitio", "siteSettingDescription": "Configurar los ajustes en el sitio",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado", "clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado",
"privateResourcesBannerTitle": "Acceso privado de confianza cero", "privateResourcesBannerTitle": "Acceso privado de confianza cero",
"privateResourcesBannerDescription": "Los recursos privados usan seguridad de confianza cero, asegurando que usuarios y máquinas solo puedan acceder a los recursos que usted conceda explícitamente. Conecte dispositivos de usuario o clientes de máquinas para acceder a estos recursos a través de una red privada virtual segura.", "privateResourcesBannerDescription": "Los recursos privados usan seguridad de confianza cero, asegurando que usuarios y máquinas solo puedan acceder a los recursos que usted conceda explícitamente. Conecte dispositivos de usuario o clientes de máquinas para acceder a estos recursos a través de una red privada virtual segura.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Buscar recursos...", "resourcesSearch": "Buscar recursos...",
"resourceAdd": "Añadir Recurso", "resourceAdd": "Añadir Recurso",
"resourceErrorDelte": "Error al eliminar el recurso", "resourceErrorDelte": "Error al eliminar el recurso",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Sitios no encontrados.", "sitesNotFound": "Sitios no encontrados.",
"pangolinServerAdmin": "Admin Servidor - Pangolin", "pangolinServerAdmin": "Admin Servidor - Pangolin",
"licenseTierProfessional": "Licencia profesional", "licenseTierProfessional": "Licencia profesional",
"licenseTierEnterprise": "Licencia Enterprise", "licenseTierEnterprise": "Empresa",
"licenseTierPersonal": "Licencia personal", "licenseTierPersonal": "Uso personal",
"licenseTierTier1": "Iniciador",
"licenseTierTier2": "Escala",
"licensed": "Licenciado", "licensed": "Licenciado",
"yes": "Sí", "yes": "Sí",
"no": "Nu", "no": "Nu",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Êtes-vous sûr de vouloir redémarrer le tunnel WireGuard pour <b>{name}</b>? Le site perdra brièvement sa connectivité.", "siteRestartDialogMessage": "Êtes-vous sûr de vouloir redémarrer le tunnel WireGuard pour <b>{name}</b>? Le site perdra brièvement sa connectivité.",
"siteRestartWarning": "Le site sera brièvement déconnecté pendant le redémarrage du tunnel.", "siteRestartWarning": "Le site sera brièvement déconnecté pendant le redémarrage du tunnel.",
"siteRestarted": "Site redémarré", "siteRestarted": "Site redémarré",
"siteRestartedDescription": "Le tunnel WireGuard a été redémarré.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Échec du redémarrage du site", "siteErrorRestart": "Échec du redémarrage du site",
"siteErrorRestartDescription": "Une erreur s'est produite lors du redémarrage du site.", "siteErrorRestartDescription": "Une erreur s'est produite lors du redémarrage du site.",
"siteSettingDescription": "Configurer les paramètres du site", "siteSettingDescription": "Configurer les paramètres du site",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté", "clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté",
"privateResourcesBannerTitle": "Accès privé sans confiance", "privateResourcesBannerTitle": "Accès privé sans confiance",
"privateResourcesBannerDescription": "Les ressources privées utilisent la sécurité sans confiance, garantissant que les utilisateurs et les machines ne peuvent accéder qu'aux ressources que vous accordez explicitement. Connectez les appareils utilisateur ou les clients machines à ces ressources via un réseau privé virtuel sécurisé.", "privateResourcesBannerDescription": "Les ressources privées utilisent la sécurité sans confiance, garantissant que les utilisateurs et les machines ne peuvent accéder qu'aux ressources que vous accordez explicitement. Connectez les appareils utilisateur ou les clients machines à ces ressources via un réseau privé virtuel sécurisé.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Chercher des ressources...", "resourcesSearch": "Chercher des ressources...",
"resourceAdd": "Ajouter une ressource", "resourceAdd": "Ajouter une ressource",
"resourceErrorDelte": "Erreur lors de la de suppression de la ressource", "resourceErrorDelte": "Erreur lors de la de suppression de la ressource",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Aucun site trouvé.", "sitesNotFound": "Aucun site trouvé.",
"pangolinServerAdmin": "Admin Serveur - Pangolin", "pangolinServerAdmin": "Admin Serveur - Pangolin",
"licenseTierProfessional": "Licence Professionnelle", "licenseTierProfessional": "Licence Professionnelle",
"licenseTierEnterprise": "Licence Entreprise", "licenseTierEnterprise": "Entreprise",
"licenseTierPersonal": "Licence personnelle", "licenseTierPersonal": "Personnel",
"licenseTierTier1": "Démarrage",
"licenseTierTier2": "Échelle",
"licensed": "Sous licence", "licensed": "Sous licence",
"yes": "Oui", "yes": "Oui",
"no": "Non", "no": "Non",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Sei sicuro di voler riavviare il tunnel WireGuard per <b>{name}</b>? Il sito perderà brevemente la connettività.", "siteRestartDialogMessage": "Sei sicuro di voler riavviare il tunnel WireGuard per <b>{name}</b>? Il sito perderà brevemente la connettività.",
"siteRestartWarning": "Il sito si disconnette brevemente mentre il tunnel si riavvia.", "siteRestartWarning": "Il sito si disconnette brevemente mentre il tunnel si riavvia.",
"siteRestarted": "Sito riavviato", "siteRestarted": "Sito riavviato",
"siteRestartedDescription": "Il tunnel WireGuard è stato riavviato.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Impossibile riavviare il sito", "siteErrorRestart": "Impossibile riavviare il sito",
"siteErrorRestartDescription": "Si è verificato un errore durante il riavvio del sito.", "siteErrorRestartDescription": "Si è verificato un errore durante il riavvio del sito.",
"siteSettingDescription": "Configura le impostazioni del sito", "siteSettingDescription": "Configura le impostazioni del sito",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso", "clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust", "privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
"privateResourcesBannerDescription": "Le risorse private utilizzano la sicurezza zero-trust, assicurandosi che gli utenti e le macchine possano accedere solo alle risorse a cui hai concesso esplicitamente l'accesso. Collega i dispositivi utente o i client macchina per accedere a queste risorse tramite una rete privata virtuale sicura.", "privateResourcesBannerDescription": "Le risorse private utilizzano la sicurezza zero-trust, assicurandosi che gli utenti e le macchine possano accedere solo alle risorse a cui hai concesso esplicitamente l'accesso. Collega i dispositivi utente o i client macchina per accedere a queste risorse tramite una rete privata virtuale sicura.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Cerca risorse...", "resourcesSearch": "Cerca risorse...",
"resourceAdd": "Aggiungi Risorsa", "resourceAdd": "Aggiungi Risorsa",
"resourceErrorDelte": "Errore nell'eliminare la risorsa", "resourceErrorDelte": "Errore nell'eliminare la risorsa",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Nessun sito trovato.", "sitesNotFound": "Nessun sito trovato.",
"pangolinServerAdmin": "Server Admin - Pangolina", "pangolinServerAdmin": "Server Admin - Pangolina",
"licenseTierProfessional": "Licenza Professional", "licenseTierProfessional": "Licenza Professional",
"licenseTierEnterprise": "Licenza Enterprise", "licenseTierEnterprise": "Impresa",
"licenseTierPersonal": "Licenza Personale", "licenseTierPersonal": "Personale",
"licenseTierTier1": "Avviatore",
"licenseTierTier2": "Scala",
"licensed": "Con Licenza", "licensed": "Con Licenza",
"yes": "Sì", "yes": "Sì",
"no": "No", "no": "No",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "<b>{name}</b>의 WireGuard 터널을 재시작하시겠습니까? 이 작업으로 인해 사이트의 연결이 일시적으로 중단될 수 있습니다.", "siteRestartDialogMessage": "<b>{name}</b>의 WireGuard 터널을 재시작하시겠습니까? 이 작업으로 인해 사이트의 연결이 일시적으로 중단될 수 있습니다.",
"siteRestartWarning": "터널을 재시작하는 동안 사이트가 일시적으로 연결이 끊깁니다.", "siteRestartWarning": "터널을 재시작하는 동안 사이트가 일시적으로 연결이 끊깁니다.",
"siteRestarted": "사이트가 재시작되었습니다", "siteRestarted": "사이트가 재시작되었습니다",
"siteRestartedDescription": "WireGuard 터널이 재시작되었습니다.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "사이트 재시작 실패", "siteErrorRestart": "사이트 재시작 실패",
"siteErrorRestartDescription": "사이트를 재시작하는 중 오류가 발생했습니다.", "siteErrorRestartDescription": "사이트를 재시작하는 중 오류가 발생했습니다.",
"siteSettingDescription": "사이트에서 설정을 구성하세요.", "siteSettingDescription": "사이트에서 설정을 구성하세요.",
@@ -234,6 +234,9 @@
"clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.", "clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.",
"privateResourcesBannerTitle": "제로 트러스트 개인 접근", "privateResourcesBannerTitle": "제로 트러스트 개인 접근",
"privateResourcesBannerDescription": "개인 리소스는 제로 트러스트 보안을 사용하여, 사용자와 장치가 명시적으로 허용된 리소스에만 접근할 수 있도록 보장합니다. 이러한 리소스에 접근하려면 안전한 가상 사설 네트워크를 통해 사용자 장치 또는 머신 클라이언트를 연결하십시오.", "privateResourcesBannerDescription": "개인 리소스는 제로 트러스트 보안을 사용하여, 사용자와 장치가 명시적으로 허용된 리소스에만 접근할 수 있도록 보장합니다. 이러한 리소스에 접근하려면 안전한 가상 사설 네트워크를 통해 사용자 장치 또는 머신 클라이언트를 연결하십시오.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "리소스 검색...", "resourcesSearch": "리소스 검색...",
"resourceAdd": "리소스 추가", "resourceAdd": "리소스 추가",
"resourceErrorDelte": "리소스 삭제 중 오류 발생", "resourceErrorDelte": "리소스 삭제 중 오류 발생",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "사이트를 찾을 수 없습니다.", "sitesNotFound": "사이트를 찾을 수 없습니다.",
"pangolinServerAdmin": "서버 관리자 - 판골린", "pangolinServerAdmin": "서버 관리자 - 판골린",
"licenseTierProfessional": "전문 라이센스", "licenseTierProfessional": "전문 라이센스",
"licenseTierEnterprise": "기업 라이선스", "licenseTierEnterprise": "기업",
"licenseTierPersonal": "개인 라이선스", "licenseTierPersonal": "개인",
"licenseTierTier1": "스타터",
"licenseTierTier2": "스케일",
"licensed": "라이센스", "licensed": "라이센스",
"yes": "예", "yes": "예",
"no": "아니요", "no": "아니요",
+24 -19
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Er du sikker på at du vil starte WireGuard-tunnelen for <b>{name}</b> på nytt? Området vil midlertidig miste tilkoblingen.", "siteRestartDialogMessage": "Er du sikker på at du vil starte WireGuard-tunnelen for <b>{name}</b> på nytt? Området vil midlertidig miste tilkoblingen.",
"siteRestartWarning": "Området vil kobles kort fra mens tunnelen starter om.", "siteRestartWarning": "Området vil kobles kort fra mens tunnelen starter om.",
"siteRestarted": "Område startet på nytt", "siteRestarted": "Område startet på nytt",
"siteRestartedDescription": "WireGuard-tunnelen er startet på nytt.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Kan ikke starte område på nytt", "siteErrorRestart": "Kan ikke starte område på nytt",
"siteErrorRestartDescription": "En feil oppstod ved omstart av området.", "siteErrorRestartDescription": "En feil oppstod ved omstart av området.",
"siteSettingDescription": "Konfigurere innstillingene på nettstedet", "siteSettingDescription": "Konfigurere innstillingene på nettstedet",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient", "clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient",
"privateResourcesBannerTitle": "Zero-Trust privat tilgang", "privateResourcesBannerTitle": "Zero-Trust privat tilgang",
"privateResourcesBannerDescription": "Private ressurser bruker Zero-Trust-sikkerhet, og sikrer at brukere og maskiner kun kan få tilgang til ressurser du eksplisitt gir tillatelse til. Koble bruker-enheter eller maskinklienter for å få tilgang til disse ressursene via et sikkert virtuelt privat nettverk.", "privateResourcesBannerDescription": "Private ressurser bruker Zero-Trust-sikkerhet, og sikrer at brukere og maskiner kun kan få tilgang til ressurser du eksplisitt gir tillatelse til. Koble bruker-enheter eller maskinklienter for å få tilgang til disse ressursene via et sikkert virtuelt privat nettverk.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Søk i ressurser...", "resourcesSearch": "Søk i ressurser...",
"resourceAdd": "Legg til ressurs", "resourceAdd": "Legg til ressurs",
"resourceErrorDelte": "Feil ved sletting av ressurs", "resourceErrorDelte": "Feil ved sletting av ressurs",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Ingen områder funnet.", "sitesNotFound": "Ingen områder funnet.",
"pangolinServerAdmin": "Server Admin - Pangolin", "pangolinServerAdmin": "Server Admin - Pangolin",
"licenseTierProfessional": "Profesjonell lisens", "licenseTierProfessional": "Profesjonell lisens",
"licenseTierEnterprise": "Bedriftslisens", "licenseTierEnterprise": "Bedrift",
"licenseTierPersonal": "Personlig lisens", "licenseTierPersonal": "Personlig",
"licenseTierTier1": "Begynner",
"licenseTierTier2": "Skala",
"licensed": "Lisensiert", "licensed": "Lisensiert",
"yes": "Ja", "yes": "Ja",
"no": "Nei", "no": "Nei",
@@ -2816,7 +2821,7 @@
"roleTextImportPreview": "Forhåndsvisning", "roleTextImportPreview": "Forhåndsvisning",
"roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}", "roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}",
"roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt", "roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt",
"roleTextImportConfirm": "Import", "roleTextImportConfirm": "Importer",
"roleTextImportInvalidFile": "Ustøttet filtype", "roleTextImportInvalidFile": "Ustøttet filtype",
"roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.", "roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.",
"roleTextImportEmpty": "Ingen elementer funnet i filen", "roleTextImportEmpty": "Ingen elementer funnet i filen",
@@ -3093,7 +3098,7 @@
"regionAfrica": "Afrika", "regionAfrica": "Afrika",
"regionNorthernAfrica": "[country name] Nord-Afrika", "regionNorthernAfrica": "[country name] Nord-Afrika",
"regionEasternAfrica": "Øst-Afrika", "regionEasternAfrica": "Øst-Afrika",
"regionMiddleAfrica": "Middle Africa", "regionMiddleAfrica": "Midt-Afrika",
"regionSouthernAfrica": "Sør-Afrika", "regionSouthernAfrica": "Sør-Afrika",
"regionWesternAfrica": "[country name] Vest-Afrika", "regionWesternAfrica": "[country name] Vest-Afrika",
"regionAmericas": "Amerika", "regionAmericas": "Amerika",
@@ -3112,10 +3117,10 @@
"regionNorthernEurope": "Nord-Europa", "regionNorthernEurope": "Nord-Europa",
"regionSouthernEurope": "Sørlige Europa", "regionSouthernEurope": "Sørlige Europa",
"regionWesternEurope": "Vest-Europa", "regionWesternEurope": "Vest-Europa",
"regionOceania": "Oceania", "regionOceania": "Oseania",
"regionAustraliaAndNewZealand": "Australia og New Zealand", "regionAustraliaAndNewZealand": "Australia og New Zealand",
"regionMelanesia": "Melanesia", "regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia", "regionMicronesia": "Mikronesia",
"regionPolynesia": "Polynesia", "regionPolynesia": "Polynesia",
"managedSelfHosted": { "managedSelfHosted": {
"title": "Administrert selv-hostet", "title": "Administrert selv-hostet",
@@ -3205,7 +3210,7 @@
"idpAzureClientIdDescription2": "Azure App registrerings klient-ID", "idpAzureClientIdDescription2": "Azure App registrerings klient-ID",
"idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig", "idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig",
"idpGoogleDescription": "Google OAuth2/OIDC leverandør", "idpGoogleDescription": "Google OAuth2/OIDC leverandør",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC-leverandør",
"subnet": "Subnett", "subnet": "Subnett",
"utilitySubnet": "Nyttesubnett", "utilitySubnet": "Nyttesubnett",
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.", "subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
@@ -3220,7 +3225,7 @@
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside", "authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?", "authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging", "authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
"brandingLogoURL": "Logo URL", "brandingLogoURL": "Logo-URL",
"brandingLogoURLOrPath": "Logoen URL eller sti", "brandingLogoURLOrPath": "Logoen URL eller sti",
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.", "brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.", "brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
@@ -3360,7 +3365,7 @@
"resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com", "resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com",
"resourceHeaderAuthSubmit": "Angi topptekst godkjenning", "resourceHeaderAuthSubmit": "Angi topptekst godkjenning",
"actionSetResourceHeaderAuth": "Angi topptekst godkjenning", "actionSetResourceHeaderAuth": "Angi topptekst godkjenning",
"enterpriseEdition": "Enterprise Edition", "enterpriseEdition": "Enterprise-utgave",
"unlicensed": "Ikke lisensiert", "unlicensed": "Ikke lisensiert",
"beta": "beta", "beta": "beta",
"manageUserDevices": "Bruker Enheter", "manageUserDevices": "Bruker Enheter",
@@ -3501,7 +3506,7 @@
"priority": "Prioritet", "priority": "Prioritet",
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.", "priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
"instanceName": "Forekomst navn", "instanceName": "Forekomst navn",
"clearInstanceName": "Reset Server Association", "clearInstanceName": "Tilbakestill server-assosiasjon",
"pathMatchModalTitle": "Konfigurere matching av sti", "pathMatchModalTitle": "Konfigurere matching av sti",
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.", "pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
"pathMatchType": "Trefftype", "pathMatchType": "Trefftype",
@@ -3557,11 +3562,11 @@
"allowedByRule": "Tillatt etter regel", "allowedByRule": "Tillatt etter regel",
"allowedNoAuth": "Tillatt Ingen Auth", "allowedNoAuth": "Tillatt Ingen Auth",
"validAccessToken": "Gyldig tilgangsnøkkel", "validAccessToken": "Gyldig tilgangsnøkkel",
"validHeaderAuth": "Valid header auth", "validHeaderAuth": "Gyldig header-autentisering",
"validPincode": "Gyldig PIN-kode", "validPincode": "Gyldig PIN-kode",
"validPassword": "Gyldig passord", "validPassword": "Gyldig passord",
"validEmail": "Valid email", "validEmail": "Gyldig e-post",
"validSSO": "Valid SSO", "validSSO": "Gyldig SSO",
"validVirtualAPIKey": "Gyldig Virtuell API-nøkkel", "validVirtualAPIKey": "Gyldig Virtuell API-nøkkel",
"view": "Vis", "view": "Vis",
"configManaged": "Konfigurasjon administrert", "configManaged": "Konfigurasjon administrert",
@@ -3570,7 +3575,7 @@
"droppedByRule": "Legg i regelen", "droppedByRule": "Legg i regelen",
"noSessions": "Ingen økter", "noSessions": "Ingen økter",
"temporaryRequestToken": "Midlertidig forespørsel Token", "temporaryRequestToken": "Midlertidig forespørsel Token",
"noMoreAuthMethods": "No Valid Auth", "noMoreAuthMethods": "Ingen gyldig autentisering",
"ip": "IP", "ip": "IP",
"reason": "Grunn", "reason": "Grunn",
"requestLogs": "HTTP-forespørselslogger", "requestLogs": "HTTP-forespørselslogger",
@@ -3784,14 +3789,14 @@
"niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.", "niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.",
"niceIdCannotBeEmpty": "God ID kan ikke være tom", "niceIdCannotBeEmpty": "God ID kan ikke være tom",
"enterIdentifier": "Angi identifikator", "enterIdentifier": "Angi identifikator",
"identifier": "Identifier", "identifier": "Identifikator",
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.", "deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.", "deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.", "loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
"noData": "Ingen data", "noData": "Ingen data",
"machineClients": "Maskinklienter", "machineClients": "Maskinklienter",
"install": "Installer", "install": "Installer",
"downloadInstaller": "Download Installer", "downloadInstaller": "Last ned installasjonsprogram",
"run": "Kjør", "run": "Kjør",
"envFile": "Miljøfil", "envFile": "Miljøfil",
"serviceFile": "Tjenestefil", "serviceFile": "Tjenestefil",
@@ -3969,7 +3974,7 @@
"kernelVersion": "Kjerne versjon", "kernelVersion": "Kjerne versjon",
"deviceModel": "Enhets modell", "deviceModel": "Enhets modell",
"serialNumber": "Serienummer", "serialNumber": "Serienummer",
"hostname": "Hostname", "hostname": "Vertsnavn",
"firstSeen": "Først sett", "firstSeen": "Først sett",
"lastSeen": "Sist sett", "lastSeen": "Sist sett",
"biometricsEnabled": "Biometri aktivert", "biometricsEnabled": "Biometri aktivert",
@@ -3999,7 +4004,7 @@
"disconnected": "Frakoblet", "disconnected": "Frakoblet",
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert", "approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.", "approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
"approvalsEmptyStateHowToTitle": "How to Enable", "approvalsEmptyStateHowToTitle": "Hvordan aktivere",
"approvalsEmptyStateStep1Title": "Gå til roller", "approvalsEmptyStateStep1Title": "Gå til roller",
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.", "approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger", "approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
+9 -4
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Weet u zeker dat u de WireGuard-tunnel voor <b>{name}</b> wilt herstarten? De site zal tijdelijk geen connectiviteit hebben.", "siteRestartDialogMessage": "Weet u zeker dat u de WireGuard-tunnel voor <b>{name}</b> wilt herstarten? De site zal tijdelijk geen connectiviteit hebben.",
"siteRestartWarning": "De site zal kort worden losgekoppeld terwijl de tunnel opnieuw wordt gestart.", "siteRestartWarning": "De site zal kort worden losgekoppeld terwijl de tunnel opnieuw wordt gestart.",
"siteRestarted": "Site herstart", "siteRestarted": "Site herstart",
"siteRestartedDescription": "De WireGuard-tunnel is opnieuw gestart.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Site herstarten mislukt", "siteErrorRestart": "Site herstarten mislukt",
"siteErrorRestartDescription": "Er is een fout opgetreden tijdens het herstarten van de site.", "siteErrorRestartDescription": "Er is een fout opgetreden tijdens het herstarten van de site.",
"siteSettingDescription": "Configureer de instellingen van de site", "siteSettingDescription": "Configureer de instellingen van de site",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client", "clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client",
"privateResourcesBannerTitle": "Privébronnen via Zero Trust", "privateResourcesBannerTitle": "Privébronnen via Zero Trust",
"privateResourcesBannerDescription": "Privébronnen maken gebruik van zero trust beveiliging, wat ervoor zorgt dat gebruikers en machines alleen toegang kunnen krijgen tot resources die je specifiek toestaat. Verbind gebruikersapparaten of machines om deze middelen te benaderen via een veilig, virtueel privénetwerk.", "privateResourcesBannerDescription": "Privébronnen maken gebruik van zero trust beveiliging, wat ervoor zorgt dat gebruikers en machines alleen toegang kunnen krijgen tot resources die je specifiek toestaat. Verbind gebruikersapparaten of machines om deze middelen te benaderen via een veilig, virtueel privénetwerk.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Zoek bronnen...", "resourcesSearch": "Zoek bronnen...",
"resourceAdd": "Bron toevoegen", "resourceAdd": "Bron toevoegen",
"resourceErrorDelte": "Fout bij verwijderen document", "resourceErrorDelte": "Fout bij verwijderen document",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Geen sites gevonden.", "sitesNotFound": "Geen sites gevonden.",
"pangolinServerAdmin": "Serverbeheer - Pangolin", "pangolinServerAdmin": "Serverbeheer - Pangolin",
"licenseTierProfessional": "Professionele licentie", "licenseTierProfessional": "Professionele licentie",
"licenseTierEnterprise": "Enterprise Licentie", "licenseTierEnterprise": "Onderneming",
"licenseTierPersonal": "Persoonlijke licentie", "licenseTierPersonal": "Persoonlijk",
"licenseTierTier1": "Beginner",
"licenseTierTier2": "Schaal",
"licensed": "Gelicentieerd", "licensed": "Gelicentieerd",
"yes": "ja", "yes": "ja",
"no": "Neen", "no": "Neen",
@@ -3205,7 +3210,7 @@
"idpAzureClientIdDescription2": "Azure App registratie Client ID", "idpAzureClientIdDescription2": "Azure App registratie Client ID",
"idpAzureClientSecretDescription2": "Azure App registratie client geheim", "idpAzureClientSecretDescription2": "Azure App registratie client geheim",
"idpGoogleDescription": "Algemene OAuth2/OIDC provider", "idpGoogleDescription": "Algemene OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", "idpAzureDescription": "Microsoft Azure OAuth2/OIDC-provider",
"subnet": "Subnet", "subnet": "Subnet",
"utilitySubnet": "Hulpmiddel Subnet", "utilitySubnet": "Hulpmiddel Subnet",
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.", "subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
+10 -5
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Czy na pewno chcesz uruchomić ponownie tunel WireGuard dla <b>{name}</b>? Strona tymczasowo straci łączność.", "siteRestartDialogMessage": "Czy na pewno chcesz uruchomić ponownie tunel WireGuard dla <b>{name}</b>? Strona tymczasowo straci łączność.",
"siteRestartWarning": "Strona tymczasowo rozłączy się podczas ponownego uruchamiania tunelu.", "siteRestartWarning": "Strona tymczasowo rozłączy się podczas ponownego uruchamiania tunelu.",
"siteRestarted": "Strona zrestartowana", "siteRestarted": "Strona zrestartowana",
"siteRestartedDescription": "Tunel WireGuard został ponownie uruchomiony.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Nie udało się zrestartować strony", "siteErrorRestart": "Nie udało się zrestartować strony",
"siteErrorRestartDescription": "Wystąpił błąd podczas ponownego uruchamiania strony.", "siteErrorRestartDescription": "Wystąpił błąd podczas ponownego uruchamiania strony.",
"siteSettingDescription": "Skonfiguruj ustawienia na stronie", "siteSettingDescription": "Skonfiguruj ustawienia na stronie",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta", "clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta",
"privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu", "privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu",
"privateResourcesBannerDescription": "Zasoby prywatne korzystają z zabezpieczeń zero-trust, zapewniając dostęp do zasobów użytkownikom i maszynom, którym wyraźnie udzielasz dostępu. Połącz urządzenia użytkowników lub klientów maszyn z tymi zasobami przez bezpieczną prywatną sieć wirtualną.", "privateResourcesBannerDescription": "Zasoby prywatne korzystają z zabezpieczeń zero-trust, zapewniając dostęp do zasobów użytkownikom i maszynom, którym wyraźnie udzielasz dostępu. Połącz urządzenia użytkowników lub klientów maszyn z tymi zasobami przez bezpieczną prywatną sieć wirtualną.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Szukaj zasobów...", "resourcesSearch": "Szukaj zasobów...",
"resourceAdd": "Dodaj zasób", "resourceAdd": "Dodaj zasób",
"resourceErrorDelte": "Błąd podczas usuwania zasobu", "resourceErrorDelte": "Błąd podczas usuwania zasobu",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Nie znaleziono witryn.", "sitesNotFound": "Nie znaleziono witryn.",
"pangolinServerAdmin": "Administrator serwera - Pangolin", "pangolinServerAdmin": "Administrator serwera - Pangolin",
"licenseTierProfessional": "Licencja Professional", "licenseTierProfessional": "Licencja Professional",
"licenseTierEnterprise": "Licencja Enterprise", "licenseTierEnterprise": "Przedsiębiorstwo",
"licenseTierPersonal": "Licencja osobista", "licenseTierPersonal": "Osobiste",
"licenseTierTier1": "Startowy",
"licenseTierTier2": "Skala",
"licensed": "Licencjonowany", "licensed": "Licencjonowany",
"yes": "Tak", "yes": "Tak",
"no": "Nie", "no": "Nie",
@@ -3791,7 +3796,7 @@
"noData": "Brak danych", "noData": "Brak danych",
"machineClients": "Klienci maszyn", "machineClients": "Klienci maszyn",
"install": "Zainstaluj", "install": "Zainstaluj",
"downloadInstaller": "Download Installer", "downloadInstaller": "Pobierz instalator",
"run": "Uruchom", "run": "Uruchom",
"envFile": "Plik środowiska", "envFile": "Plik środowiska",
"serviceFile": "Plik serwisu", "serviceFile": "Plik serwisu",
@@ -3999,7 +4004,7 @@
"disconnected": "Rozłączony", "disconnected": "Rozłączony",
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone", "approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.", "approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
"approvalsEmptyStateHowToTitle": "How to Enable", "approvalsEmptyStateHowToTitle": "Jak włączyć",
"approvalsEmptyStateStep1Title": "Przejdź do ról", "approvalsEmptyStateStep1Title": "Przejdź do ról",
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.", "approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia", "approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Tem certeza de que deseja reiniciar o túnel WireGuard para <b>{name}</b>? O site perderá brevemente a conectividade.", "siteRestartDialogMessage": "Tem certeza de que deseja reiniciar o túnel WireGuard para <b>{name}</b>? O site perderá brevemente a conectividade.",
"siteRestartWarning": "O site será desconectado brevemente enquanto o túnel reinicia.", "siteRestartWarning": "O site será desconectado brevemente enquanto o túnel reinicia.",
"siteRestarted": "Site reiniciado", "siteRestarted": "Site reiniciado",
"siteRestartedDescription": "O túnel WireGuard foi reiniciado.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Falha ao reiniciar o site", "siteErrorRestart": "Falha ao reiniciar o site",
"siteErrorRestartDescription": "Ocorreu um erro ao reiniciar o site.", "siteErrorRestartDescription": "Ocorreu um erro ao reiniciar o site.",
"siteSettingDescription": "Configurar as configurações no site", "siteSettingDescription": "Configurar as configurações no site",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado", "clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado",
"privateResourcesBannerTitle": "Acesso Privado com Confiança Zero", "privateResourcesBannerTitle": "Acesso Privado com Confiança Zero",
"privateResourcesBannerDescription": "Os recursos privados usam segurança de zero confiança, garantindo que usuários e máquinas possam acessar apenas os recursos que você concede explicitamente. Conecte dispositivos de usuários ou clientes de máquinas para acessar esses recursos por meio de uma rede privada virtual segura.", "privateResourcesBannerDescription": "Os recursos privados usam segurança de zero confiança, garantindo que usuários e máquinas possam acessar apenas os recursos que você concede explicitamente. Conecte dispositivos de usuários ou clientes de máquinas para acessar esses recursos por meio de uma rede privada virtual segura.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Procurar recursos...", "resourcesSearch": "Procurar recursos...",
"resourceAdd": "Adicionar Recurso", "resourceAdd": "Adicionar Recurso",
"resourceErrorDelte": "Erro ao apagar recurso", "resourceErrorDelte": "Erro ao apagar recurso",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Nenhum site encontrado.", "sitesNotFound": "Nenhum site encontrado.",
"pangolinServerAdmin": "Administrador do Servidor - Pangolin", "pangolinServerAdmin": "Administrador do Servidor - Pangolin",
"licenseTierProfessional": "Licença Profissional", "licenseTierProfessional": "Licença Profissional",
"licenseTierEnterprise": "Licença Empresarial", "licenseTierEnterprise": "Empresa",
"licenseTierPersonal": "Licença Pessoal", "licenseTierPersonal": "Pessoal",
"licenseTierTier1": "Iniciante",
"licenseTierTier2": "Escala",
"licensed": "Licenciado", "licensed": "Licenciado",
"yes": "Sim", "yes": "Sim",
"no": "Não", "no": "Não",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Вы уверены, что хотите перезапустить туннель WireGuard для <b>{name}</b>? Сайт кратковременно потеряет соединение.", "siteRestartDialogMessage": "Вы уверены, что хотите перезапустить туннель WireGuard для <b>{name}</b>? Сайт кратковременно потеряет соединение.",
"siteRestartWarning": "Сайт кратковременно отключится во время перезапуска туннеля.", "siteRestartWarning": "Сайт кратковременно отключится во время перезапуска туннеля.",
"siteRestarted": "Сайт перезапущен", "siteRestarted": "Сайт перезапущен",
"siteRestartedDescription": "Туннель WireGuard был перезапущен.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Не удалось перезапустить сайт", "siteErrorRestart": "Не удалось перезапустить сайт",
"siteErrorRestartDescription": "Произошла ошибка во время перезапуска сайта.", "siteErrorRestartDescription": "Произошла ошибка во время перезапуска сайта.",
"siteSettingDescription": "Настройка параметров на сайте", "siteSettingDescription": "Настройка параметров на сайте",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент", "clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент",
"privateResourcesBannerTitle": "Частный доступ с нулевым доверием", "privateResourcesBannerTitle": "Частный доступ с нулевым доверием",
"privateResourcesBannerDescription": "Частные ресурсы используют безопасность с нулевым доверием, обеспечивая доступ пользователей и устройств только к ресурсам, к которым вы явно предоставили доступ. Подключите пользовательские устройства или машинных клиентов, чтобы получить доступ к этим ресурсам через безопасную виртуальную частную сеть.", "privateResourcesBannerDescription": "Частные ресурсы используют безопасность с нулевым доверием, обеспечивая доступ пользователей и устройств только к ресурсам, к которым вы явно предоставили доступ. Подключите пользовательские устройства или машинных клиентов, чтобы получить доступ к этим ресурсам через безопасную виртуальную частную сеть.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Поиск ресурсов...", "resourcesSearch": "Поиск ресурсов...",
"resourceAdd": "Добавить ресурс", "resourceAdd": "Добавить ресурс",
"resourceErrorDelte": "Ошибка при удалении ресурса", "resourceErrorDelte": "Ошибка при удалении ресурса",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Сайты не найдены.", "sitesNotFound": "Сайты не найдены.",
"pangolinServerAdmin": "Администратор сервера - Pangolin", "pangolinServerAdmin": "Администратор сервера - Pangolin",
"licenseTierProfessional": "Профессиональная лицензия", "licenseTierProfessional": "Профессиональная лицензия",
"licenseTierEnterprise": "Корпоративная лицензия", "licenseTierEnterprise": "Предприятие",
"licenseTierPersonal": "Личная лицензия", "licenseTierPersonal": "Личное",
"licenseTierTier1": "Старт",
"licenseTierTier2": "Масштаб",
"licensed": "Лицензировано", "licensed": "Лицензировано",
"yes": "Да", "yes": "Да",
"no": "Нет", "no": "Нет",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "<b>{name}</b> için WireGuard tünelini yeniden başlatmak istediğinizden emin misiniz? Site kısa süreliğine bağlantıyı kaybedecektir.", "siteRestartDialogMessage": "<b>{name}</b> için WireGuard tünelini yeniden başlatmak istediğinizden emin misiniz? Site kısa süreliğine bağlantıyı kaybedecektir.",
"siteRestartWarning": "Tünel yeniden başlatılırken site kısa süreliğine kesintiye uğrar.", "siteRestartWarning": "Tünel yeniden başlatılırken site kısa süreliğine kesintiye uğrar.",
"siteRestarted": "Site yeniden başlatıldı", "siteRestarted": "Site yeniden başlatıldı",
"siteRestartedDescription": "WireGuard tüneli yeniden başlatıldı.", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "Sitenin yeniden başlatılması başarısız oldu", "siteErrorRestart": "Sitenin yeniden başlatılması başarısız oldu",
"siteErrorRestartDescription": "Site yeniden başlatılırken bir hata oluştu.", "siteErrorRestartDescription": "Site yeniden başlatılırken bir hata oluştu.",
"siteSettingDescription": "Sitenizdeki ayarları yapılandırın", "siteSettingDescription": "Sitenizdeki ayarları yapılandırın",
@@ -234,6 +234,9 @@
"clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin", "clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin",
"privateResourcesBannerTitle": "Sıfır Güven Özel Erişim", "privateResourcesBannerTitle": "Sıfır Güven Özel Erişim",
"privateResourcesBannerDescription": "Özel kaynaklar sıfır güven güvenliği kullanır, kullanıcılar ve makinelerin yalnızca açıkça izin verdiğiniz kaynaklara erişmesini sağlar. Bu kaynaklara güvenli bir sanal özel ağ üzerinden erişmek için kullanıcı cihazlarını veya makine müşterilerini bağlayın.", "privateResourcesBannerDescription": "Özel kaynaklar sıfır güven güvenliği kullanır, kullanıcılar ve makinelerin yalnızca açıkça izin verdiğiniz kaynaklara erişmesini sağlar. Bu kaynaklara güvenli bir sanal özel ağ üzerinden erişmek için kullanıcı cihazlarını veya makine müşterilerini bağlayın.",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "Kaynakları ara...", "resourcesSearch": "Kaynakları ara...",
"resourceAdd": "Kaynak Ekle", "resourceAdd": "Kaynak Ekle",
"resourceErrorDelte": "Kaynak silinirken hata", "resourceErrorDelte": "Kaynak silinirken hata",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "Site bulunamadı.", "sitesNotFound": "Site bulunamadı.",
"pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin", "pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin",
"licenseTierProfessional": "Profesyonel Lisans", "licenseTierProfessional": "Profesyonel Lisans",
"licenseTierEnterprise": "Kurumsal Lisans", "licenseTierEnterprise": "Kurumsal",
"licenseTierPersonal": "Kişisel Lisans", "licenseTierPersonal": "Kişisel",
"licenseTierTier1": "Başlangıç",
"licenseTierTier2": "Ölçek",
"licensed": "Lisanslı", "licensed": "Lisanslı",
"yes": "Evet", "yes": "Evet",
"no": "Hayır", "no": "Hayır",
+8 -3
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "确定要重启<b>{name}</b>的WireGuard隧道吗?站点将暂时断开连接。", "siteRestartDialogMessage": "确定要重启<b>{name}</b>的WireGuard隧道吗?站点将暂时断开连接。",
"siteRestartWarning": "隧道重启时,站点将暂时断开连接。", "siteRestartWarning": "隧道重启时,站点将暂时断开连接。",
"siteRestarted": "站点已重启", "siteRestarted": "站点已重启",
"siteRestartedDescription": "WireGuard隧道已重启。", "siteRestartedDescription": "The site has been restarted.",
"siteErrorRestart": "重启站点失败", "siteErrorRestart": "重启站点失败",
"siteErrorRestartDescription": "重启站点时发生错误。", "siteErrorRestartDescription": "重启站点时发生错误。",
"siteSettingDescription": "配置站点设置", "siteSettingDescription": "配置站点设置",
@@ -234,6 +234,9 @@
"clientResourceDescription": "创建和管理只能通过连接客户端访问的资源", "clientResourceDescription": "创建和管理只能通过连接客户端访问的资源",
"privateResourcesBannerTitle": "零信任私有访问", "privateResourcesBannerTitle": "零信任私有访问",
"privateResourcesBannerDescription": "私有资源采用零信任安全机制,确保只有获得明确授权的用户和机器才能访问。用户设备或机器客户端连接后,即可通过安全的虚拟专用网络访问这些资源。", "privateResourcesBannerDescription": "私有资源采用零信任安全机制,确保只有获得明确授权的用户和机器才能访问。用户设备或机器客户端连接后,即可通过安全的虚拟专用网络访问这些资源。",
"licenseBillingBannerTitle": "Manage License Billing",
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
"licenseBillingBannerButton": "Go to Billing",
"resourcesSearch": "搜索资源...", "resourcesSearch": "搜索资源...",
"resourceAdd": "添加资源", "resourceAdd": "添加资源",
"resourceErrorDelte": "删除资源时出错", "resourceErrorDelte": "删除资源时出错",
@@ -1118,8 +1121,10 @@
"sitesNotFound": "未找到站点。", "sitesNotFound": "未找到站点。",
"pangolinServerAdmin": "服务器管理 - Pangolin", "pangolinServerAdmin": "服务器管理 - Pangolin",
"licenseTierProfessional": "专业许可证", "licenseTierProfessional": "专业许可证",
"licenseTierEnterprise": "企业许可证", "licenseTierEnterprise": "企业",
"licenseTierPersonal": "个人许可证", "licenseTierPersonal": "个人",
"licenseTierTier1": "启动器",
"licenseTierTier2": "扩展",
"licensed": "已授权", "licensed": "已授权",
"yes": "是", "yes": "是",
"no": "否", "no": "否",
+2661 -1914
View File
File diff suppressed because it is too large Load Diff
+29 -29
View File
@@ -33,13 +33,13 @@
}, },
"dependencies": { "dependencies": {
"@asteasolutions/zod-to-openapi": "9.1.0", "@asteasolutions/zod-to-openapi": "9.1.0",
"@aws-sdk/client-s3": "3.1121.0", "@aws-sdk/client-s3": "3.1131.0",
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", "@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz", "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
"@headlessui/react": "2.2.10", "@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.9.1", "@hookform/resolvers": "5.9.1",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.2.0", "@node-rs/argon2": "2.2.1",
"@novnc/novnc": "^1.7.0", "@novnc/novnc": "^1.7.0",
"@oslojs/crypto": "1.0.1", "@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0", "@oslojs/encoding": "1.1.0",
@@ -65,8 +65,8 @@
"@react-email/components": "1.0.12", "@react-email/components": "1.0.12",
"@react-email/render": "2.1.0", "@react-email/render": "2.1.0",
"@react-email/tailwind": "2.0.7", "@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0", "@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "13.3.3", "@simplewebauthn/server": "14.0.1",
"@tailwindcss/forms": "0.5.11", "@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.102.8", "@tanstack/react-query": "5.102.8",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "8.21.3",
@@ -99,29 +99,29 @@
"js-yaml": "5.4.1", "js-yaml": "5.4.1",
"jsonwebtoken": "9.0.3", "jsonwebtoken": "9.0.3",
"lru-cache": "11.5.2", "lru-cache": "11.5.2",
"lucide-react": "1.38.0", "lucide-react": "1.45.0",
"maxmind": "5.0.7", "maxmind": "5.0.7",
"moment": "2.30.1", "moment": "2.30.1",
"next": "16.3.3", "next": "16.3.5",
"next-intl": "4.14.1", "next-intl": "4.14.4",
"next-themes": "0.4.6", "next-themes": "0.4.6",
"nextjs-toploader": "3.9.17", "nextjs-toploader": "3.9.17",
"nodemailer": "9.1.0", "nodemailer": "10.0.9",
"oslo": "1.2.1", "oslo": "1.2.1",
"pg": "8.23.0", "pg": "8.23.0",
"posthog-node": "5.51.4", "posthog-node": "5.52.1",
"qrcode.react": "4.2.0", "qrcode.react": "4.2.0",
"react": "19.2.8", "react": "19.3.0",
"react-day-picker": "9.14.0", "react-day-picker": "10.0.1",
"react-dom": "19.2.8", "react-dom": "19.3.0",
"react-easy-sort": "1.8.0", "react-easy-sort": "1.8.0",
"react-hook-form": "7.87.0", "react-hook-form": "7.88.0",
"react-icons": "5.7.0", "react-icons": "5.7.0",
"recharts": "3.10.1", "recharts": "3.10.1",
"reodotdev": "1.1.0", "reodotdev": "1.1.0",
"semver": "7.8.5", "semver": "7.8.5",
"sshpk": "1.18.0", "sshpk": "1.18.0",
"stripe": "22.6.0", "stripe": "22.6.2",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0", "tailwind-merge": "3.6.0",
"topojson-client": "3.1.0", "topojson-client": "3.1.0",
@@ -133,15 +133,15 @@
"winston": "3.19.0", "winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0", "winston-daily-rotate-file": "5.0.0",
"ws": "8.21.3", "ws": "8.21.3",
"yaml": "2.9.0", "yaml": "2.9.1",
"yargs": "18.1.0", "yargs": "18.1.0",
"zod": "4.5.4", "zod": "4.6.2",
"zod-validation-error": "5.0.0" "zod-validation-error": "5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@dotenvx/dotenvx": "2.23.0", "@dotenvx/dotenvx": "2.24.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2", "@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/ui": "^6.9.3", "@react-email/ui": "^6.9.5",
"@tailwindcss/postcss": "4.3.3", "@tailwindcss/postcss": "4.3.3",
"@tanstack/react-query-devtools": "5.102.8", "@tanstack/react-query-devtools": "5.102.8",
"@types/better-sqlite3": "7.6.13", "@types/better-sqlite3": "7.6.13",
@@ -155,12 +155,12 @@
"@types/jmespath": "0.15.2", "@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10", "@types/jsonwebtoken": "9.0.10",
"@types/node": "26.4.0", "@types/node": "26.5.1",
"@types/nodemailer": "8.0.1", "@types/nodemailer": "8.0.1",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@types/pg": "8.23.1", "@types/pg": "8.23.1",
"@types/react": "19.2.18", "@types/react": "19.3.0",
"@types/react-dom": "19.2.5", "@types/react-dom": "19.3.0",
"@types/semver": "7.8.0", "@types/semver": "7.8.0",
"@types/sshpk": "1.17.5", "@types/sshpk": "1.17.5",
"@types/swagger-ui-express": "4.1.8", "@types/swagger-ui-express": "4.1.8",
@@ -171,20 +171,20 @@
"drizzle-kit": "0.31.10", "drizzle-kit": "0.31.10",
"esbuild": "0.28.2", "esbuild": "0.28.2",
"esbuild-node-externals": "2.0.0", "esbuild-node-externals": "2.0.0",
"eslint": "10.9.1", "eslint": "10.10.0",
"eslint-config-next": "16.3.3", "eslint-config-next": "16.3.5",
"postcss": "8.5.26", "postcss": "8.5.28",
"prettier": "3.9.6", "prettier": "3.9.6",
"react-email": "6.9.3", "react-email": "6.9.5",
"tailwindcss": "4.3.3", "tailwindcss": "4.3.3",
"tsc-alias": "1.9.2", "tsc-alias": "1.9.5",
"tsx": "4.23.13", "tsx": "4.23.13",
"typescript": "6.0.3", "typescript": "7.0.2",
"typescript-eslint": "8.68.0" "typescript-eslint": "8.70.0"
}, },
"overrides": { "overrides": {
"esbuild": "0.28.2", "esbuild": "0.28.2",
"dompurify": "3.4.0", "dompurify": "3.4.0",
"postcss": "8.5.26" "postcss": "8.5.28"
} }
} }
+13 -9
View File
@@ -1,5 +1,6 @@
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres"; import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
import { readConfigFile } from "@server/lib/readConfigFile"; import { readConfigFile } from "@server/lib/readConfigFile";
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
import { withReplicas } from "drizzle-orm/pg-core"; import { withReplicas } from "drizzle-orm/pg-core";
import { createPool } from "./poolConfig"; import { createPool } from "./poolConfig";
@@ -7,17 +8,20 @@ function createDb() {
const config = readConfigFile(); const config = readConfigFile();
// check the environment variables for postgres config first before the config file // check the environment variables for postgres config first before the config file
if (process.env.POSTGRES_CONNECTION_STRING) { const envConnectionString = readEnvOrFile("POSTGRES_CONNECTION_STRING");
if (envConnectionString) {
config.postgres = { config.postgres = {
connection_string: process.env.POSTGRES_CONNECTION_STRING connection_string: envConnectionString
}; };
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) { const replicaConnectionStrings = readEnvOrFile(
const replicas = "POSTGRES_REPLICA_CONNECTION_STRINGS"
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map( );
(conn) => ({ if (replicaConnectionStrings) {
connection_string: conn.trim() const replicas = replicaConnectionStrings
}) .split(",")
); .map((conn) => ({
connection_string: conn.trim()
}));
config.postgres.replicas = replicas; config.postgres.replicas = replicas;
} }
} }
+11 -8
View File
@@ -1,5 +1,6 @@
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres"; import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
import { readConfigFile } from "@server/lib/readConfigFile"; import { readConfigFile } from "@server/lib/readConfigFile";
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
import { withReplicas } from "drizzle-orm/pg-core"; import { withReplicas } from "drizzle-orm/pg-core";
import { build } from "@server/build"; import { build } from "@server/build";
import { db as mainDb } from "./driver"; import { db as mainDb } from "./driver";
@@ -17,7 +18,7 @@ function createLogsDb() {
const logsConfig = config.postgres_logs; const logsConfig = config.postgres_logs;
// Check environment variable first // Check environment variable first
let connectionString = process.env.POSTGRES_LOGS_CONNECTION_STRING; let connectionString = readEnvOrFile("POSTGRES_LOGS_CONNECTION_STRING");
let replicaConnections: Array<{ connection_string: string }> = []; let replicaConnections: Array<{ connection_string: string }> = [];
if (!connectionString && logsConfig) { if (!connectionString && logsConfig) {
@@ -26,13 +27,15 @@ function createLogsDb() {
} }
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it // If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) { const replicaConnectionStrings = readEnvOrFile(
replicaConnections = "POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS"
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map( );
(conn) => ({ if (replicaConnectionStrings) {
connection_string: conn.trim() replicaConnections = replicaConnectionStrings
}) .split(",")
); .map((conn) => ({
connection_string: conn.trim()
}));
} }
// If no logs database is configured, fall back to main database // If no logs database is configured, fall back to main database
+2 -1
View File
@@ -6,6 +6,7 @@ import fs from "fs";
import { APP_PATH } from "@server/lib/consts"; import { APP_PATH } from "@server/lib/consts";
import { existsSync, mkdirSync } from "fs"; import { existsSync, mkdirSync } from "fs";
import logger from "@server/logger"; import logger from "@server/logger";
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
export const location = path.join(APP_PATH, "db", "db.sqlite"); export const location = path.join(APP_PATH, "db", "db.sqlite");
export const exists = checkFileExists(location); export const exists = checkFileExists(location);
@@ -19,7 +20,7 @@ function createDb() {
: undefined; : undefined;
const sqlite = new Database(location, { verbose }); const sqlite = new Database(location, { verbose });
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") { if (readEnvOrFile("ENABLE_SQLITE_WAL_MODE") == "true") {
// Enable WAL mode — allows concurrent readers + single writer, preventing // Enable WAL mode — allows concurrent readers + single writer, preventing
// contention across subsystems (verifySession, Traefik, audit, ping). // contention across subsystems (verifySession, Traefik, audit, ping).
// NOTE: journal_mode persists in the DB file once set; unsetting this // NOTE: journal_mode persists in the DB file once set; unsetting this
+6 -6
View File
@@ -1,6 +1,6 @@
export enum LicenseId { export enum LicenseId {
SMALL_LICENSE = "small_license", TIER1 = "tier1",
BIG_LICENSE = "big_license" TIER2 = "tier2"
} }
export type LicensePriceSet = { export type LicensePriceSet = {
@@ -9,15 +9,15 @@ export type LicensePriceSet = {
export const licensePriceSet: LicensePriceSet = { export const licensePriceSet: LicensePriceSet = {
// Free license matches the freeLimitSet // Free license matches the freeLimitSet
[LicenseId.SMALL_LICENSE]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT", [LicenseId.TIER1]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
[LicenseId.BIG_LICENSE]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS" [LicenseId.TIER2]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
}; };
export const licensePriceSetSandbox: LicensePriceSet = { export const licensePriceSetSandbox: LicensePriceSet = {
// Free license matches the freeLimitSet // Free license matches the freeLimitSet
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value // when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN", [LicenseId.TIER1]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl" [LicenseId.TIER2]: "price_1SxDy0DCpkOb237BWJxrxYkl"
}; };
export function getLicensePriceSet( export function getLicensePriceSet(
+36 -2
View File
@@ -1,3 +1,37 @@
export const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => { import fs from "fs";
return process.env[envVar] ?? valFromYaml;
// Resolves an environment variable, also honoring a `<envVar>_FILE` variant
// that points to a file whose (trimmed) contents should be used as the
// value. This is the common convention for consuming Docker/Swarm secrets
// (e.g. mounted at /run/secrets/...) without putting the raw value in the
// container's environment.
export const readEnvOrFile = (envVar: string): string | undefined => {
const fileEnvVar = `${envVar}_FILE`;
const filePath = process.env[fileEnvVar];
if (filePath) {
if (process.env[envVar]) {
throw new Error(
`Both ${envVar} and ${fileEnvVar} are set. Please set only one.`
);
}
try {
return fs.readFileSync(filePath, "utf8").trim();
} catch (error) {
throw new Error(
`Failed to read ${fileEnvVar} (${filePath}): ${
error instanceof Error ? error.message : error
}`
);
}
}
return process.env[envVar];
}; };
export const getEnvOrYaml =
(envVar: string) =>
(valFromYaml: string | undefined): string | undefined => {
return readEnvOrFile(envVar) ?? valFromYaml;
};
+15 -28
View File
@@ -3,7 +3,7 @@ import * as yaml from "js-yaml";
import { configFilePath1, configFilePath2 } from "./consts"; import { configFilePath1, configFilePath2 } from "./consts";
import { z } from "zod"; import { z } from "zod";
import stoi from "./stoi"; import stoi from "./stoi";
import { getEnvOrYaml } from "./getEnvOrYaml"; import { getEnvOrYaml, readEnvOrFile } from "./getEnvOrYaml";
const portSchema = z.number().positive().gt(0).lte(65535); const portSchema = z.number().positive().gt(0).lte(65535);
@@ -26,14 +26,12 @@ export const configSchema = z
.object({ .object({
anonymous_usage: z.boolean().optional().default(true) anonymous_usage: z.boolean().optional().default(true)
}) })
.optional()
.prefault({}), .prefault({}),
notifications: z notifications: z
.object({ .object({
product_updates: z.boolean().optional().default(true), product_updates: z.boolean().optional().default(true),
new_releases: z.boolean().optional().default(true) new_releases: z.boolean().optional().default(true)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional() .optional()
@@ -109,7 +107,6 @@ export const configSchema = z
id: z.string().optional().default("P-Access-Token-Id"), id: z.string().optional().default("P-Access-Token-Id"),
token: z.string().optional().default("P-Access-Token") token: z.string().optional().default("P-Access-Token")
}) })
.optional()
.prefault({}), .prefault({}),
remote_headers: z remote_headers: z
.object({ .object({
@@ -126,7 +123,6 @@ export const configSchema = z
name: z.string().optional().default("Remote-Name"), name: z.string().optional().default("Remote-Name"),
role: z.string().optional().default("Remote-Role") role: z.string().optional().default("Remote-Role")
}) })
.optional()
.prefault({}), .prefault({}),
resource_session_request_param: z resource_session_request_param: z
.string() .string()
@@ -164,14 +160,17 @@ export const configSchema = z
.boolean() .boolean()
.optional() .optional()
.default(false) .default(false)
.transform((val) => .transform((val) => {
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !== const envVal = readEnvOrFile(
undefined "ENABLE_AI_GATEWAY_CLIENT_IP_HEADER"
? process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER === );
"true" return envVal !== undefined ? envVal === "true" : val;
: val }),
), secret: z
secret: z.string().pipe(z.string().min(8)).optional(), .string()
.pipe(z.string().min(8))
.optional()
.transform(getEnvOrYaml("SERVER_SECRET")),
maxmind_db_path: z.string().optional(), maxmind_db_path: z.string().optional(),
maxmind_asn_path: z.string().optional() maxmind_asn_path: z.string().optional()
}) })
@@ -202,7 +201,8 @@ export const configSchema = z
dashboard_session_length_hours: 720, dashboard_session_length_hours: 720,
resource_session_length_hours: 720, resource_session_length_hours: 720,
trust_proxy: 1, trust_proxy: 1,
enable_ai_gateway_client_ip_header: false enable_ai_gateway_client_ip_header: false,
secret: undefined
}), }),
postgres: z postgres: z
.object({ .object({
@@ -238,7 +238,6 @@ export const configSchema = z
.default(5000), .default(5000),
jit_mode: z.boolean().default(true) jit_mode: z.boolean().default(true)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional(), .optional(),
@@ -278,7 +277,6 @@ export const configSchema = z
.optional() .optional()
.default(5000) .default(5000)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional(), .optional(),
@@ -325,10 +323,8 @@ export const configSchema = z
.optional() .optional()
.default(50) .default(50)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional()
.prefault({}), .prefault({}),
gerbil: z gerbil: z
.object({ .object({
@@ -357,7 +353,6 @@ export const configSchema = z
.optional() .optional()
.default(30) .default(30)
}) })
.optional()
.prefault({}), .prefault({}),
orgs: z orgs: z
.object({ .object({
@@ -391,7 +386,6 @@ export const configSchema = z
.optional() .optional()
.default(500) .default(500)
}) })
.optional()
.prefault({}), .prefault({}),
auth: z auth: z
.object({ .object({
@@ -408,10 +402,8 @@ export const configSchema = z
.optional() .optional()
.default(500) .default(500)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional()
.prefault({}), .prefault({}),
email: z email: z
.object({ .object({
@@ -489,10 +481,8 @@ export const configSchema = z
.optional() .optional()
.default(12) .default(12)
}) })
.optional()
.prefault({}) .prefault({})
}) })
.optional()
.prefault({}) .prefault({})
}) })
.refine( .refine(
@@ -513,10 +503,7 @@ export const configSchema = z
) )
.refine( .refine(
(data) => { (data) => {
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env // If hybrid is not defined, server secret must be defined
if (data.server?.secret === undefined) {
data.server.secret = process.env.SERVER_SECRET;
}
return ( return (
data.server?.secret !== undefined && data.server?.secret !== undefined &&
data.server.secret.length > 0 data.server.secret.length > 0
+53 -24
View File
@@ -14,6 +14,7 @@ import { getTraefikConfig } from "#dynamic/lib/traefik";
import { getValidCertificatesForDomains } from "@server/lib/certificates"; import { getValidCertificatesForDomains } from "@server/lib/certificates";
import { sendToExitNode } from "#dynamic/lib/exitNodes"; import { sendToExitNode } from "#dynamic/lib/exitNodes";
import { build } from "@server/build"; import { build } from "@server/build";
import license from "#dynamic/license/license";
export class TraefikConfigManager { export class TraefikConfigManager {
private intervalId: NodeJS.Timeout | null = null; private intervalId: NodeJS.Timeout | null = null;
@@ -357,7 +358,11 @@ export class TraefikConfigManager {
this.lastActiveDomains = new Set(domains); this.lastActiveDomains = new Set(domains);
} }
if (process.env.CERT_MODE === "pangolin" && build != "oss") { if (
process.env.CERT_MODE === "pangolin" &&
build != "oss" &&
(await license.hasTier(["personal", "tier2", "enterprise"]))
) {
// Scan current local certificate state // Scan current local certificate state
this.lastLocalCertificateState = this.lastLocalCertificateState =
await this.scanLocalCertificateState(); await this.scanLocalCertificateState();
@@ -717,10 +722,9 @@ export class TraefikConfigManager {
} }
if (shouldWrite) { if (shouldWrite) {
try { try {
fs.writeFileSync( this.atomicWriteFileSync(
traefikDynamicConfigPath, traefikDynamicConfigPath,
yaml.dump(traefikConfig, { noRefs: true }), yaml.dump(traefikConfig, { noRefs: true })
"utf8"
); );
logger.info("Traefik dynamic config updated"); logger.info("Traefik dynamic config updated");
} catch (err) { } catch (err) {
@@ -822,7 +826,7 @@ export class TraefikConfigManager {
// Only write the config if it has changed // Only write the config if it has changed
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true }); const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
if (newConfigYaml !== originalConfigYaml) { if (newConfigYaml !== originalConfigYaml) {
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8"); this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
logger.info("Dynamic cert config updated from local certificates"); logger.info("Dynamic cert config updated from local certificates");
} }
} }
@@ -900,26 +904,23 @@ export class TraefikConfigManager {
`Processing certificate for domain: ${cert.domain}` `Processing certificate for domain: ${cert.domain}`
); );
fs.writeFileSync(certPath, cert.certFile, "utf8"); // Write atomically (temp file + rename) so Traefik's
fs.writeFileSync(keyPath, cert.keyFile, "utf8"); // file watcher never observes a partially written
// cert/key and fails with "failed to find any PEM data".
// Set appropriate permissions (readable by owner only for key file) this.atomicWriteFileSync(certPath, cert.certFile, 0o644);
fs.chmodSync(certPath, 0o644); this.atomicWriteFileSync(keyPath, cert.keyFile, 0o600);
fs.chmodSync(keyPath, 0o600);
// Write/update .last_update file with current timestamp // Write/update .last_update file with current timestamp
fs.writeFileSync( this.atomicWriteFileSync(
lastUpdatePath, lastUpdatePath,
new Date().toISOString(), new Date().toISOString()
"utf8"
); );
// Check if this is a wildcard certificate and store it // Check if this is a wildcard certificate and store it
const wildcardPath = path.join(domainDir, ".wildcard"); const wildcardPath = path.join(domainDir, ".wildcard");
fs.writeFileSync( this.atomicWriteFileSync(
wildcardPath, wildcardPath,
cert.wildcard ? "true" : "false", cert.wildcard ? "true" : "false"
"utf8"
); );
logger.info( logger.info(
@@ -931,10 +932,9 @@ export class TraefikConfigManager {
// even if the cert content didn't change // even if the cert content didn't change
if (cert.expiresAt) { if (cert.expiresAt) {
const expiresAtPath = path.join(domainDir, ".expires_at"); const expiresAtPath = path.join(domainDir, ".expires_at");
fs.writeFileSync( this.atomicWriteFileSync(
expiresAtPath, expiresAtPath,
cert.expiresAt.toString(), cert.expiresAt.toString()
"utf8"
); );
} }
@@ -970,7 +970,7 @@ export class TraefikConfigManager {
// Only write the config if it has changed // Only write the config if it has changed
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true }); const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
if (newConfigYaml !== originalConfigYaml) { if (newConfigYaml !== originalConfigYaml) {
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8"); this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
logger.info("Dynamic cert config updated"); logger.info("Dynamic cert config updated");
} }
} }
@@ -1141,10 +1141,9 @@ export class TraefikConfigManager {
if (configChanged) { if (configChanged) {
try { try {
fs.writeFileSync( this.atomicWriteFileSync(
dynamicConfigPath, dynamicConfigPath,
yaml.dump(dynamicConfig, { noRefs: true }), yaml.dump(dynamicConfig, { noRefs: true })
"utf8"
); );
logger.info("Dynamic config updated after cleanup"); logger.info("Dynamic config updated after cleanup");
} catch (err) { } catch (err) {
@@ -1171,6 +1170,36 @@ export class TraefikConfigManager {
} }
} }
/**
* Write a file atomically by writing to a temp file in the same
* directory and renaming it into place. This avoids Traefik (which
* watches these files/directories) picking up a partially written
* file and failing to parse it (e.g. "failed to find any PEM data").
*/
private atomicWriteFileSync(
filePath: string,
data: string,
mode?: number
): void {
const dir = path.dirname(filePath);
const tmpPath = path.join(
dir,
`.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
);
try {
fs.writeFileSync(tmpPath, data, "utf8");
if (mode !== undefined) {
fs.chmodSync(tmpPath, mode);
}
fs.renameSync(tmpPath, filePath);
} catch (error) {
try {
fs.rmSync(tmpPath, { force: true });
} catch {}
throw error;
}
}
/** /**
* Check if file exists * Check if file exists
*/ */
+6 -2
View File
@@ -4,7 +4,7 @@ import { setHostMeta } from "@server/lib/hostMeta";
const keyTypes = ["host"] as const; const keyTypes = ["host"] as const;
export type LicenseKeyType = (typeof keyTypes)[number]; export type LicenseKeyType = (typeof keyTypes)[number];
const keyTiers = ["personal", "enterprise"] as const; const keyTiers = ["personal", "enterprise", "tier1", "tier2"] as const;
export type LicenseKeyTier = (typeof keyTiers)[number]; export type LicenseKeyTier = (typeof keyTiers)[number];
export type LicenseStatus = { export type LicenseStatus = {
@@ -33,7 +33,7 @@ export type LicenseKeyCache = {
export class License { export class License {
private serverSecret!: string; private serverSecret!: string;
constructor(private hostMeta: HostMeta) { } constructor(private hostMeta: HostMeta) {}
public async check(): Promise<LicenseStatus> { public async check(): Promise<LicenseStatus> {
return { return {
@@ -50,6 +50,10 @@ export class License {
public async isUnlocked() { public async isUnlocked() {
return false; return false;
} }
public async hasTier(tier: LicenseKeyTier[]) {
return false;
}
} }
await setHostMeta(); await setHostMeta();
+2 -2
View File
@@ -45,9 +45,9 @@ export class JobScheduler {
label: string label: string
): () => Promise<void> { ): () => Promise<void> {
return async () => { return async () => {
if (!(await license.isUnlocked())) { if (!(await license.hasTier(["personal", "tier2", "enterprise"]))) {
logger.debug( logger.debug(
`Skipping ${label} tick - license is not subscribed` `Skipping ${label} tick - requires a tier2 license`
); );
return; return;
} }
+11 -6
View File
@@ -63,10 +63,11 @@ export class AuthoritativeDNSServer {
private allDomains: Set<string> = new Set(); private allDomains: Set<string> = new Set();
private domainRefreshInterval: NodeJS.Timeout | null = null; private domainRefreshInterval: NodeJS.Timeout | null = null;
// Cached license/subscription status. license.isUnlocked() does a DB // Cached license/plan status - only a tier2 license unlocks the DNS
// round-trip on every call, so it can't be checked per-query on a UDP // server. license.hasPlan() does a DB round-trip on every call, so it
// server that may see very high query volume - instead it's polled on // can't be checked per-query on a UDP server that may see very high
// the same cadence as the domain set refresh and read from memory here. // query volume - instead it's polled on the same cadence as the domain
// set refresh and read from memory here.
private isLicensed: boolean = false; private isLicensed: boolean = false;
private licenseRefreshInterval: NodeJS.Timeout | null = null; private licenseRefreshInterval: NodeJS.Timeout | null = null;
@@ -128,7 +129,7 @@ export class AuthoritativeDNSServer {
} }
if (!this.isLicensed) { if (!this.isLicensed) {
logger.debug("Refusing DNS query - license is not subscribed"); logger.debug("Refusing DNS query - requires a tier2 license");
// REFUSED (rcode=5) indicates a policy refusal by this nameserver. // REFUSED (rcode=5) indicates a policy refusal by this nameserver.
this.sendResponse(packet, [], rinfo, false, 5, []); this.sendResponse(packet, [], rinfo, false, 5, []);
return; return;
@@ -1042,7 +1043,11 @@ export class AuthoritativeDNSServer {
private async refreshLicenseStatus(): Promise<void> { private async refreshLicenseStatus(): Promise<void> {
try { try {
this.isLicensed = await license.isUnlocked(); this.isLicensed = await license.hasTier([
"personal",
"tier2",
"enterprise"
]);
} catch (error) { } catch (error) {
logger.error("Failed to refresh license status:", error); logger.error("Failed to refresh license status:", error);
this.isLicensed = false; this.isLicensed = false;
-3
View File
@@ -48,7 +48,6 @@ export const privateConfigSchema = z
.optional() .optional()
.transform(getEnvOrYaml("FOSSORIAL_API_KEY")) .transform(getEnvOrYaml("FOSSORIAL_API_KEY"))
}) })
.optional()
.prefault({}), .prefault({}),
redis: z redis: z
.object({ .object({
@@ -166,7 +165,6 @@ export const privateConfigSchema = z
.optional() .optional()
.default("http://gerbil:3004") .default("http://gerbil:3004")
}) })
.optional()
.prefault({}), .prefault({}),
flags: z flags: z
.object({ .object({
@@ -187,7 +185,6 @@ export const privateConfigSchema = z
// (server/private/lib/config.ts). // (server/private/lib/config.ts).
disable_private_http_placeholder: z.boolean().optional() disable_private_http_placeholder: z.boolean().optional()
}) })
.optional()
.prefault({}), .prefault({}),
acme: z acme: z
.object({ .object({
+40 -51
View File
@@ -54,6 +54,7 @@ import {
getValidCertificatesForDomains getValidCertificatesForDomains
} from "@server/lib/certificates"; } from "@server/lib/certificates";
import { build } from "@server/build"; import { build } from "@server/build";
import license from "#private/license/license";
import regionalCache from "#private/lib/cache"; import regionalCache from "#private/lib/cache";
import { TargetWithSite } from "@server/lib/traefik/types"; import { TargetWithSite } from "@server/lib/traefik/types";
import { buildWildcardTls } from "@server/lib/traefik/certResolver"; import { buildWildcardTls } from "@server/lib/traefik/certResolver";
@@ -395,8 +396,15 @@ export async function getTraefikConfig(
) )
); );
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
// license (self-hosted) or a saas build - otherwise fall back to
// Traefik's own cert resolvers (buildWildcardTls) throughout.
const pangolinCertModeEnabled =
privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin" &&
(await license.hasTier(["personal", "tier2", "enterprise"]));
let validCerts: CertificateResult[] = []; let validCerts: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") { if (pangolinCertModeEnabled) {
// create a list of all domains to get certs for // create a list of all domains to get certs for
const domains = new Set<string>(); const domains = new Set<string>();
for (const resource of resourcesMap.values()) { for (const resource of resourcesMap.values()) {
@@ -522,10 +530,7 @@ export async function getTraefikConfig(
); );
let tls = {}; let tls = {};
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
tls = buildWildcardTls({ tls = buildWildcardTls({
fullDomain, fullDomain,
hasSubdomain: !!resource.subdomain, hasSubdomain: !!resource.subdomain,
@@ -546,6 +551,30 @@ export async function getTraefikConfig(
} }
} }
config_output.http.services![serviceName] = {
loadBalancer: {
servers: buildHttpLoadBalancerServers(targets),
...(resource.stickySession
? buildStickySessionCookie(resource.ssl)
: {})
}
};
if (resource.tlsServerName) {
if (!config_output.http.serversTransports) {
config_output.http.serversTransports = {};
}
config_output.http.serversTransports![transportName] = {
serverName: resource.tlsServerName,
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
// if defined in the static config and here. if not set, self-signed certs won't work
insecureSkipVerify: true
};
config_output.http.services![
serviceName
].loadBalancer.serversTransport = transportName;
}
if (resource.ssl) { if (resource.ssl) {
config_output.http.routers![routerName + "-redirect"] = { config_output.http.routers![routerName + "-redirect"] = {
entryPoints: [ entryPoints: [
@@ -710,31 +739,6 @@ export async function getTraefikConfig(
priority: priority, priority: priority,
...(resource.ssl ? { tls } : {}) ...(resource.ssl ? { tls } : {})
}; };
config_output.http.services![serviceName] = {
loadBalancer: {
servers: buildHttpLoadBalancerServers(targets),
...(resource.stickySession
? buildStickySessionCookie(resource.ssl)
: {})
}
};
// Add the serversTransport if TLS server name is provided
if (resource.tlsServerName) {
if (!config_output.http.serversTransports) {
config_output.http.serversTransports = {};
}
config_output.http.serversTransports![transportName] = {
serverName: resource.tlsServerName,
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
// if defined in the static config and here. if not set, self-signed certs won't work
insecureSkipVerify: true
};
config_output.http.services![
serviceName
].loadBalancer.serversTransport = transportName;
}
} else if (resource.mode == "tcp" || resource.mode == "udp") { } else if (resource.mode == "tcp" || resource.mode == "udp") {
// Non-HTTP (TCP/UDP) configuration // Non-HTTP (TCP/UDP) configuration
if (!resource.enableProxy) { if (!resource.enableProxy) {
@@ -791,10 +795,7 @@ export async function getTraefikConfig(
domainCertResolver, domainCertResolver,
preferWildcardCert preferWildcardCert
}) => { }) => {
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
return buildWildcardTls({ return buildWildcardTls({
fullDomain, fullDomain,
hasSubdomain, hasSubdomain,
@@ -835,10 +836,7 @@ export async function getTraefikConfig(
maintenancePageUiUrl, maintenancePageUiUrl,
redirectHttpsMiddlewareName, redirectHttpsMiddlewareName,
resolveTls: (fullDomain) => { resolveTls: (fullDomain) => {
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// siteResource aliases don't have a per-domain cert // siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global // resolver stored, so always fall back to the global
// defaults. // defaults.
@@ -929,10 +927,7 @@ export async function getTraefikConfig(
const rule = buildHostRule(fullDomain, ir.wildcard); const rule = buildHostRule(fullDomain, ir.wildcard);
let tls: any = {}; let tls: any = {};
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
tls = buildWildcardTls({ tls = buildWildcardTls({
fullDomain, fullDomain,
hasSubdomain: !!ir.subdomain, hasSubdomain: !!ir.subdomain,
@@ -1012,10 +1007,7 @@ export async function getTraefikConfig(
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
let tls: any = {}; let tls: any = {};
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// siteResource aliases don't have a per-domain cert // siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global // resolver stored, so always fall back to the global
// defaults. // defaults.
@@ -1089,7 +1081,7 @@ export async function getTraefikConfig(
.where(eq(exitNodes.exitNodeId, exitNodeId)); .where(eq(exitNodes.exitNodeId, exitNodeId));
let validCertsLoginPages: CertificateResult[] = []; let validCertsLoginPages: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") { if (pangolinCertModeEnabled) {
// create a list of all domains to get certs for // create a list of all domains to get certs for
const domains = new Set<string>(); const domains = new Set<string>();
for (const lp of exitNodeLoginPages) { for (const lp of exitNodeLoginPages) {
@@ -1134,10 +1126,7 @@ export async function getTraefikConfig(
} }
const tls = {}; const tls = {};
if ( if (!pangolinCertModeEnabled) {
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// TODO: we need to add the wildcard logic here too // TODO: we need to add the wildcard logic here too
} else { } else {
// find a cert that matches the full domain, if not continue // find a cert that matches the full domain, if not continue
+19 -2
View File
@@ -26,6 +26,7 @@ import {
LicenseStatus LicenseStatus
} from "@server/license/license"; } from "@server/license/license";
import { setHostMeta } from "@server/lib/hostMeta"; import { setHostMeta } from "@server/lib/hostMeta";
import { build } from "@server/build";
type ActivateLicenseKeyAPIResponse = { type ActivateLicenseKeyAPIResponse = {
data: { data: {
@@ -119,6 +120,9 @@ LQIDAQAB
} }
public async isUnlocked(): Promise<boolean> { public async isUnlocked(): Promise<boolean> {
if (build == "saas") {
return true;
}
const status = await this.check(); const status = await this.check();
if (status.isHostLicensed) { if (status.isHostLicensed) {
if (status.isLicenseValid) { if (status.isLicenseValid) {
@@ -128,6 +132,20 @@ LQIDAQAB
return false; return false;
} }
public async hasTier(tier: LicenseKeyTier[]): Promise<boolean> {
if (build == "saas") {
return true;
}
const status = await this.check();
if (status.isHostLicensed && status.isLicenseValid) {
return (
status.tier !== undefined &&
tier.includes(status.tier as LicenseKeyTier)
);
}
return false;
}
public async check(): Promise<LicenseStatus> { public async check(): Promise<LicenseStatus> {
// If a check is already in progress, return the last known status // If a check is already in progress, return the last known status
if (this.checkInProgress) { if (this.checkInProgress) {
@@ -135,8 +153,7 @@ LQIDAQAB
"License check already in progress, returning last known status" "License check already in progress, returning last known status"
); );
const lastStatus = this.statusCache.get(this.statusKey) as const lastStatus = this.statusCache.get(this.statusKey) as
| LicenseStatus LicenseStatus | undefined;
| undefined;
if (lastStatus) { if (lastStatus) {
return lastStatus; return lastStatus;
} }
@@ -54,7 +54,6 @@ export const queryAccessAuditLogsQuery = z.object({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -47,7 +47,6 @@ export const queryActionAuditLogsQuery = z.object({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -55,7 +55,6 @@ export const queryConnectionAuditLogsQuery = z.object({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -222,15 +222,16 @@ export async function handleSubscriptionCreated(
let numUsers: number; let numUsers: number;
let numSites: number; let numSites: number;
let tier = "enterprise";
if (subscriptionPriceId === priceSet[LicenseId.SMALL_LICENSE]) { if (subscriptionPriceId === priceSet[LicenseId.TIER1]) {
numUsers = 25; numUsers = 25;
numSites = 25; numSites = 25;
} else if ( tier = "tier1";
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE] } else if (subscriptionPriceId === priceSet[LicenseId.TIER2]) {
) {
numUsers = 50; numUsers = 50;
numSites = 100; numSites = 100;
tier = "tier2";
} else { } else {
logger.error( logger.error(
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}` `Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
@@ -256,7 +257,8 @@ export async function handleSubscriptionCreated(
licenseId: parseInt(licenseId), licenseId: parseInt(licenseId),
paidFor: true, paidFor: true,
users: numUsers, users: numUsers,
sites: numSites sites: numSites,
tier: tier
}) })
} }
); );
@@ -64,14 +64,11 @@ export async function generateNewEnterpriseLicense(
const licenseData = req.body; const licenseData = req.body;
if ( if (licenseData.tier != "tier2" && licenseData.tier != "tier1") {
licenseData.tier != "big_license" &&
licenseData.tier != "small_license"
) {
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
"Invalid tier specified. Must be either 'big_license' or 'small_license'." "Invalid tier specified. Must be either 'tier2' or 'tier1'."
) )
); );
} }
@@ -118,9 +115,7 @@ export async function generateNewEnterpriseLicense(
} }
const tier = const tier =
licenseData.tier === "big_license" licenseData.tier === "tier2" ? LicenseId.TIER2 : LicenseId.TIER1;
? LicenseId.BIG_LICENSE
: LicenseId.SMALL_LICENSE;
const tierPrice = getLicensePriceSet()[tier]; const tierPrice = getLicensePriceSet()[tier];
const session = await stripe!.checkout.sessions.create({ const session = await stripe!.checkout.sessions.create({
@@ -47,7 +47,6 @@ export const queryAiSessionLogsQuery = z.strictObject({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -20,7 +20,6 @@ const queryAccessAuditLogsQuery = z.object({
error: "timeStart must be a valid ISO date string" error: "timeStart must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => getSevenDaysAgo().toISOString()) .prefault(() => getSevenDaysAgo().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -34,7 +33,6 @@ const queryAccessAuditLogsQuery = z.object({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
@@ -41,7 +41,6 @@ export const queryAccessAuditLogsQuery = z.strictObject({
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(() => new Date().toISOString()) .prefault(() => new Date().toISOString())
.openapi({ .openapi({
type: "string", type: "string",
+4 -4
View File
@@ -17,7 +17,7 @@ import {
import type { import type {
GenerateRegistrationOptionsOpts, GenerateRegistrationOptionsOpts,
GenerateAuthenticationOptionsOpts, GenerateAuthenticationOptionsOpts,
AuthenticatorTransportFuture AuthenticatorTransport
} from "@simplewebauthn/server"; } from "@simplewebauthn/server";
import { isoBase64URL } from "@simplewebauthn/server/helpers"; import { isoBase64URL } from "@simplewebauthn/server/helpers";
import config from "@server/lib/config"; import config from "@server/lib/config";
@@ -221,7 +221,7 @@ export async function startRegistration(
const excludeCredentials = existingSecurityKeys.map((key) => ({ const excludeCredentials = existingSecurityKeys.map((key) => ({
id: key.credentialId, id: key.credentialId,
transports: key.transports transports: key.transports
? (JSON.parse(key.transports) as AuthenticatorTransportFuture[]) ? (JSON.parse(key.transports) as AuthenticatorTransport[])
: undefined : undefined
})); }));
@@ -571,7 +571,7 @@ export async function startAuthentication(
transports: key.transports transports: key.transports
? (JSON.parse( ? (JSON.parse(
key.transports key.transports
) as AuthenticatorTransportFuture[]) ) as AuthenticatorTransport[])
: undefined : undefined
})); }));
} }
@@ -702,7 +702,7 @@ export async function verifyAuthentication(
transports: securityKey.transports transports: securityKey.transports
? (JSON.parse( ? (JSON.parse(
securityKey.transports securityKey.transports
) as AuthenticatorTransportFuture[]) ) as AuthenticatorTransport[])
: undefined : undefined
}, },
requireUserVerification: false requireUserVerification: false
@@ -43,7 +43,7 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
const { message, client: c } = context; const { message, client: c } = context;
const newt = c as Newt; const newt = c as Newt;
logger.info("Handling healthcheck status message"); logger.debug("Handling healthcheck status message");
if (!newt) { if (!newt) {
logger.warn("Newt not found"); logger.warn("Newt not found");
+14 -13
View File
@@ -252,9 +252,10 @@ export default function GenerateLicenseKeyForm({
try { try {
// Check if this is a business/enterprise license request // Check if this is a business/enterprise license request
if (payload.useCaseType === "business") { if (payload.useCaseType === "business") {
const response = await api.put< const response = await api.put<AxiosResponse<string>>(
AxiosResponse<string> `/org/${orgId}/license/enterprise`,
>(`/org/${orgId}/license/enterprise`, { ...payload, tier: "big_license" } ); { ...payload, tier: "tier2" }
);
console.log("Checkout session response:", response.data); console.log("Checkout session response:", response.data);
const checkoutUrl = response.data.data; const checkoutUrl = response.data.data;
@@ -1087,16 +1088,16 @@ export default function GenerateLicenseKeyForm({
)} )}
{!generatedKey && useCaseType === "business" && ( {!generatedKey && useCaseType === "business" && (
<Button <Button
type="submit" type="submit"
form="generate-license-business-form" form="generate-license-business-form"
disabled={loading} disabled={loading}
loading={loading} loading={loading}
> >
{t( {t(
"generateLicenseKeyForm.buttons.generateLicenseKey" "generateLicenseKeyForm.buttons.generateLicenseKey"
)} )}
</Button> </Button>
)} )}
</CredenzaFooter> </CredenzaFooter>
</CredenzaContent> </CredenzaContent>
+10 -3
View File
@@ -201,9 +201,16 @@ export default function GenerateLicenseKeysTable({
}, },
cell: ({ row }) => { cell: ({ row }) => {
const tier = row.original.tier; const tier = row.original.tier;
return tier === "enterprise" switch (tier) {
? t("licenseTierEnterprise") case "enterprise":
: t("licenseTierPersonal"); return t("licenseTierEnterprise");
case "tier1":
return t("licenseTierTier1");
case "tier2":
return t("licenseTierTier2");
default:
return t("licenseTierPersonal");
}
} }
}, },
{ {
+10 -3
View File
@@ -100,9 +100,16 @@ export function LicenseKeysDataTable({
}, },
cell: ({ row }) => { cell: ({ row }) => {
const tier = row.original.tier; const tier = row.original.tier;
return tier === "enterprise" switch (tier) {
? t("licenseTierEnterprise") case "enterprise":
: t("licenseTierPersonal"); return t("licenseTierEnterprise");
case "tier1":
return t("licenseTierTier1");
case "tier2":
return t("licenseTierTier2");
default:
return t("licenseTierPersonal");
}
} }
}, },
{ {
+2 -2
View File
@@ -40,8 +40,8 @@ import { InfoIcon } from "lucide-react";
import { useUserContext } from "@app/hooks/useUserContext"; import { useUserContext } from "@app/hooks/useUserContext";
const TIER_TO_LICENSE_ID = { const TIER_TO_LICENSE_ID = {
starter: "small_license", starter: "tier1",
scale: "big_license" scale: "tier2"
} as const; } as const;
type FormProps = { type FormProps = {
+1 -1
View File
@@ -87,7 +87,7 @@ function Calendar({
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5", : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label defaultClassNames.caption_label
), ),
table: "w-full border-collapse", month_grid: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays), weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn( weekday: cn(
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal", "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",