Compare commits

..

188 Commits

Author SHA1 Message Date
dependabot[bot] 6ad1af8656 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-14 21:55:53 +00:00
Owen Schwartz a84db679b2 Merge pull request #3741 from fosrl/dev
1.23.0-rc.0
2026-09-14 17:53:01 -04:00
Owen Schwartz fb726cc6b2 Merge pull request #3743 from fosrl/l10n_dev
New Crowdin updates
2026-09-14 17:51:52 -04:00
Owen Schwartz 5d0ceebe0d New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-14 17:51:05 -04:00
Owen Schwartz c964821195 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-14 17:51:03 -04:00
Owen Schwartz 5c44c6da73 New translations en-us.json (Turkish)
[ci skip]
2026-09-14 17:51:01 -04:00
Owen Schwartz 6e52758d6e New translations en-us.json (Russian)
[ci skip]
2026-09-14 17:50:59 -04:00
Owen Schwartz 0af194550f New translations en-us.json (Portuguese)
[ci skip]
2026-09-14 17:50:57 -04:00
Owen Schwartz 0d12402662 New translations en-us.json (Polish)
[ci skip]
2026-09-14 17:50:54 -04:00
Owen Schwartz 3fb2e34256 New translations en-us.json (Dutch)
[ci skip]
2026-09-14 17:50:52 -04:00
Owen Schwartz ec2b1fbd37 New translations en-us.json (Korean)
[ci skip]
2026-09-14 17:50:50 -04:00
Owen Schwartz 806ae93a12 New translations en-us.json (Italian)
[ci skip]
2026-09-14 17:50:48 -04:00
Owen Schwartz 5d99e0a2f6 New translations en-us.json (German)
[ci skip]
2026-09-14 17:50:45 -04:00
Owen Schwartz a75f335791 New translations en-us.json (Danish)
[ci skip]
2026-09-14 17:50:43 -04:00
Owen Schwartz 153d29c47c New translations en-us.json (Czech)
[ci skip]
2026-09-14 17:50:41 -04:00
Owen Schwartz ee8749c292 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-14 17:50:39 -04:00
Owen Schwartz 4f905ddae5 New translations en-us.json (Spanish)
[ci skip]
2026-09-14 17:50:37 -04:00
Owen Schwartz 74e686ff3f New translations en-us.json (French)
[ci skip]
2026-09-14 17:50:35 -04:00
Owen a4ade43380 Fix compilation issue with prop 2026-09-14 17:47:44 -04:00
miloschwartz 68821514a0 replace node cache in dns server 2026-09-14 17:41:41 -04:00
Owen Schwartz a2652cf692 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-14 17:41:14 -04:00
Owen Schwartz 957144ac3a New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-14 17:41:11 -04:00
Owen Schwartz 20c23cdfc5 New translations en-us.json (Turkish)
[ci skip]
2026-09-14 17:41:09 -04:00
Owen Schwartz c03e1c5781 New translations en-us.json (Russian)
[ci skip]
2026-09-14 17:41:06 -04:00
Owen Schwartz e69d3eded5 New translations en-us.json (Portuguese)
[ci skip]
2026-09-14 17:41:04 -04:00
Owen Schwartz 34084cc3a6 New translations en-us.json (Polish)
[ci skip]
2026-09-14 17:41:02 -04:00
Owen Schwartz 91776ad2e4 New translations en-us.json (Dutch)
[ci skip]
2026-09-14 17:41:00 -04:00
Owen Schwartz 77eabf41e7 New translations en-us.json (Korean)
[ci skip]
2026-09-14 17:40:58 -04:00
Owen Schwartz d68895e64a New translations en-us.json (Italian)
[ci skip]
2026-09-14 17:40:56 -04:00
Owen Schwartz 1c2d81d77c New translations en-us.json (German)
[ci skip]
2026-09-14 17:40:53 -04:00
Owen Schwartz 998778eb72 New translations en-us.json (Danish)
[ci skip]
2026-09-14 17:40:51 -04:00
Owen Schwartz 7be206947a New translations en-us.json (Czech)
[ci skip]
2026-09-14 17:40:49 -04:00
Owen Schwartz 125d33c071 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-14 17:40:47 -04:00
Owen Schwartz 410d26f2df New translations en-us.json (Spanish)
[ci skip]
2026-09-14 17:40:45 -04:00
Owen Schwartz 6aa1dafa10 New translations en-us.json (French)
[ci skip]
2026-09-14 17:40:43 -04:00
miloschwartz 8f1344a665 bump version and add 1.23 migrations 2026-09-14 17:18:31 -04:00
Owen d23028d38e Update lock 2026-09-14 17:06:14 -04:00
Owen Schwartz 7506a2614b Update Crowdin configuration file
[ci skip]
2026-09-14 17:00:52 -04:00
Owen b3e5de4a4d Fix cache separation for versions 2026-09-14 16:48:12 -04:00
Owen f9f38fb3f0 Handle the agent and agent version 2026-09-14 16:48:12 -04:00
Owen 888ccce397 Rename newt site to site 2026-09-14 16:48:12 -04:00
Owen 49e0a8bd47 Update the install and run commands to use the cli 2026-09-14 16:48:11 -04:00
Owen f59aed8482 Replace newt and olm with pangolin-cli 2026-09-14 16:48:11 -04:00
Owen b6c048c805 Store the cli version for display and handle auto update 2026-09-14 16:48:11 -04:00
Owen 018ee17304 Include erid new node 2026-09-14 16:48:11 -04:00
Owen 5cad796023 Dont restrict 2026-09-14 16:48:11 -04:00
Owen 5a445817e0 Show the remote nodes on enterprise pangolin dns 2026-09-14 16:48:11 -04:00
Owen fd2c397bfc Update comment to be more clear 2026-09-14 16:48:11 -04:00
Owen 6c794e0b0c Just point to the docs 2026-09-14 16:48:11 -04:00
Owen 7acb98e245 Working on ha config and docs 2026-09-14 16:48:11 -04:00
Owen d59ab30c22 Add reference documentation 2026-09-14 16:48:11 -04:00
Owen e4a8e9ac8c Add region from config file 2026-09-14 16:48:10 -04:00
Owen a874a0b745 Add explicit process exit to prevent hanging in migrations script 2026-09-14 16:48:10 -04:00
Owen 9ba2cfdef2 Make the email optional 2026-09-14 16:48:10 -04:00
Owen bba8d4e7a2 Add license checks to JobScheduler and AuthoritativeDNSServer 2026-09-14 16:48:10 -04:00
Owen 350f8c012a Pull in static domains to the traefik config again 2026-09-14 16:48:10 -04:00
Owen 8a97ed5200 Move domain information to all in private 2026-09-14 16:48:10 -04:00
Owen e66f7fe71b Implement exit node check-in tracking and adjust logging for connection errors 2026-09-14 16:48:10 -04:00
Owen a37a59f39a Add default dns port and allow sites to be empty 2026-09-14 16:48:10 -04:00
Owen d0bf553f6f Quiet up logs 2026-09-14 16:48:10 -04:00
Owen 87e005d32f Seperate dns from certificates 2026-09-14 16:48:10 -04:00
Owen ad343a7453 Wire up to start 2026-09-14 16:48:10 -04:00
Owen 7e4d38548f Add certificate generation 2026-09-14 16:48:09 -04:00
Owen 3421441635 Add cert_mode to know when to gen or pull certs 2026-09-14 16:48:09 -04:00
Owen 073bfb32e9 Adjust lic headers 2026-09-14 16:48:09 -04:00
Owen 2c5b1e5f0f Add dns server 2026-09-14 16:48:09 -04:00
miloschwartz 294830db08 show ssh commands 2026-09-14 16:48:09 -04:00
miloschwartz fb8d531435 add sites to resource launcher panel 2026-09-14 16:48:09 -04:00
miloschwartz 11d947f4ef add better page loading indicator 2026-09-14 16:48:09 -04:00
miloschwartz aad18e6501 more cosmetic improvememnts 2026-09-14 16:48:09 -04:00
miloschwartz dac8b5a132 move improvements to user management ui 2026-09-14 16:48:09 -04:00
miloschwartz 162dade852 improvements to create user 2026-09-14 16:48:09 -04:00
miloschwartz dfe7f60244 add server side filter for server admin 2026-09-14 16:48:09 -04:00
Fred KISSIE 07bea71cb9 ♻️ refactor 2026-09-14 16:48:08 -04:00
Fred KISSIE 557c398d0b 💬 update text 2026-09-14 16:48:08 -04:00
Fred KISSIE 0a385d1e44 🚧 toggle server admin 2026-09-14 16:48:08 -04:00
Fred KISSIE 521f78c2f3 🚧 server admin 2026-09-14 16:48:08 -04:00
miloschwartz 0ea4a5fb3d visual adjustments to logo empty state and profile dropdown 2026-09-14 16:48:08 -04:00
miloschwartz e2609f1a0d cosmetic adjustments 2026-09-14 16:48:08 -04:00
Fred KISSIE 8b20a88838 delete org 2026-09-14 16:48:08 -04:00
Fred KISSIE 7aae51ca9d 🚧 wip 2026-09-14 16:48:08 -04:00
Fred KISSIE 1d6d885f30 ♻️ Only show the username instead of the name+username 2026-09-14 16:48:08 -04:00
Fred KISSIE fef1476f67 List of orgs, with all columns 2026-09-14 16:48:08 -04:00
Fred KISSIE e371f26d73 List orgs in server 2026-09-14 16:48:08 -04:00
Fred KISSIE 9c8b93d6cc 🚧 WIP 2026-09-14 16:48:08 -04:00
miloschwartz aed325f273 switch to lru in memory cache and dont cache failed sessions 2026-09-14 16:48:07 -04:00
miloschwartz c7645e5c5b update screenshots 2026-09-14 16:48:07 -04:00
Owen c0eed078c8 Fix tsconfig to use react-jsx 2026-09-14 16:48:07 -04:00
Owen 0f69012c4d Fix circular import 2026-09-14 16:48:07 -04:00
Owen ba2eb87f20 Widen subnet 2026-09-14 16:48:07 -04:00
Owen c0ea32863c Quiet up error logs 2026-09-14 16:48:07 -04:00
Owen 4d71fcbac8 Quiet log message 2026-09-14 16:48:07 -04:00
Owen 1d3dcec4c8 Use endpoint instead of reachableAt for remote nodes 2026-09-14 16:48:07 -04:00
Owen Schwartz 17375348b0 Merge pull request #3702 from fosrl/dev
1.22.2
2026-09-04 17:18:42 -04:00
Owen ea9017ac06 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-09-04 17:15:33 -04:00
Owen 88770ff97b Refactor form submissions to use startTransition for improved performance 2026-09-04 17:15:20 -04:00
miloschwartz 8e75425887 update screenshots 2026-09-04 15:46:56 -04:00
Owen 44b0186044 Fix yaml import 2026-09-04 15:32:59 -04:00
Owen 063f6b5ca9 Basic DNS config send empty for olm 2026-09-04 12:31:04 -04:00
Owen Schwartz e7fdbf9e85 Merge pull request #3699 from fosrl/dev
1.22.1-s.1
2026-09-04 10:38:39 -04:00
Owen 778a840ed7 Fix react bug not making it possible to complete security form 2026-09-04 10:08:57 -04:00
Owen 9d19195089 Configurable tab title and disable flag for keys 2026-09-04 09:20:20 -04:00
Owen 54bbe82504 Dont log invalid message type
Fixes #3695
2026-09-04 09:06:58 -04:00
Milo Schwartz cddb5ecc3d Merge pull request #3691 from fosrl/dev
update readme
2026-09-03 16:19:47 -04:00
miloschwartz de57df2520 update readme and screenshots 2026-09-03 16:17:07 -04:00
Owen 9e392a967d Add AI disclosure 2026-09-03 15:41:12 -04:00
Owen Schwartz 52f7364c35 Merge pull request #3690 from fosrl/dev
1.22.1
2026-09-03 12:05:18 -04:00
Owen 1bc5fbbf0f Always pull all of the users for the blueprints 2026-09-03 12:02:41 -04:00
Owen be0dd65557 Update imports 2026-09-03 11:35:28 -04:00
Owen 11be7b8ee4 Fix export 2026-09-03 11:24:36 -04:00
Owen 9373cc0408 Revert tanstak components 2026-09-03 11:20:13 -04:00
Owen e50763c340 Pin ts 2026-09-03 11:20:13 -04:00
dependabot[bot] 8826240548 Bump the npm-dependencies group across 1 directory with 74 updates
Bumps the npm-dependencies group with 74 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@asteasolutions/zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) | `8.5.0` | `9.1.0` |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1056.0` | `3.1121.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.4.0` | `5.9.1` |
| [@node-rs/argon2](https://github.com/napi-rs/node-rs) | `2.0.2` | `2.2.0` |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/avatar) | `1.1.11` | `1.2.6` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.3` | `1.3.11` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/collapsible) | `1.1.12` | `1.1.20` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.16` | `2.1.24` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.8` | `2.1.15` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.8` | `1.1.16` |
| [@radix-ui/react-radio-group](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radio-group) | `1.3.8` | `1.4.7` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.10` | `1.2.18` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.2.6` | `2.3.7` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.8` | `1.1.15` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.4` | `1.3.3` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.2.6` | `1.3.7` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.13` | `1.1.21` |
| [@radix-ui/react-toast](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/toast) | `1.2.15` | `1.2.23` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.8` | `1.2.16` |
| [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `2.0.8` | `2.1.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.1` | `13.3.3` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.100.14` | `5.102.8` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.2.4` |
| [axios](https://github.com/axios/axios) | `1.18.0` | `1.20.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.5.2` | `8.7.0` |
| [gpt-tokenizer](https://github.com/niieani/gpt-tokenizer) | `3.4.0` | `4.0.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.2.0` | `8.3.0` |
| [input-otp](https://github.com/guilhermerodz/input-otp/tree/HEAD/packages/input-otp) | `1.4.2` | `1.5.0` |
| [ioredis](https://github.com/redis/ioredis) | `5.11.0` | `6.0.0` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.3.1` | `5.4.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.17.0` | `1.38.0` |
| [maxmind](https://github.com/runk/node-maxmind) | `5.0.6` | `5.0.7` |
| [next](https://github.com/vercel/next.js) | `16.3.1` | `16.3.3` |
| [next-intl](https://github.com/amannn/next-intl) | `4.13.0` | `4.14.1` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.1` | `9.1.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.21.0` | `8.23.0` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.20.0` | `8.23.1` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.35.6` | `5.51.4` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.8` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.18` |
| [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.6` | `19.2.8` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.3` | `19.2.5` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.76.1` | `7.87.0` |
| [react-icons](https://github.com/react-icons/react-icons) | `5.6.0` | `5.7.0` |
| [recharts](https://github.com/recharts/recharts) | `3.8.1` | `3.10.1` |
| [semver](https://github.com/npm/node-semver) | `7.8.1` | `7.8.5` |
| [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) | `7.7.1` | `7.8.0` |
| [stripe](https://github.com/stripe/stripe-node) | `22.2.0` | `22.6.0` |
| [uuid](https://github.com/uuidjs/uuid) | `14.0.0` | `14.0.2` |
| [ws](https://github.com/websockets/ws) | `8.21.0` | `8.21.3` |
| [yargs](https://github.com/yargs/yargs) | `18.0.0` | `18.1.0` |
| [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.69.1` | `2.23.0` |
| [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui) | `6.9.2` | `6.9.3` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.0` | `4.3.3` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.100.14` | `5.102.8` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `26.4.0` |
| [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer) | `8.0.0` | `8.0.1` |
| [@types/sshpk](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sshpk) | `1.17.4` | `1.17.5` |
| [esbuild](https://github.com/evanw/esbuild) | `0.28.0` | `0.28.2` |
| [esbuild-node-externals](https://github.com/pradel/esbuild-node-externals) | `1.22.0` | `2.0.0` |
| [eslint](https://github.com/eslint/eslint) | `10.4.0` | `10.9.1` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.2.6` | `16.3.3` |
| [postcss](https://github.com/postcss/postcss) | `8.5.23` | `8.5.26` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.9.6` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.5.0` | `6.9.3` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.3` |
| [tsc-alias](https://github.com/justkey007/tsc-alias) | `1.8.17` | `1.9.2` |
| [tsx](https://github.com/privatenumber/tsx) | `4.22.3` | `4.23.13` |
| [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.60.0` | `8.68.0` |



Updates `@asteasolutions/zod-to-openapi` from 8.5.0 to 9.1.0
- [Release notes](https://github.com/asteasolutions/zod-to-openapi/releases)
- [Commits](https://github.com/asteasolutions/zod-to-openapi/compare/v8.5.0...v9.1.0)

Updates `@aws-sdk/client-s3` from 3.1056.0 to 3.1121.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.1121.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.4.0 to 5.9.1
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.4.0...v5.9.1)

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

Updates `@radix-ui/react-avatar` from 1.1.11 to 1.2.6
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/avatar)

Updates `@radix-ui/react-checkbox` from 1.3.3 to 1.3.11
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-collapsible` from 1.1.12 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/collapsible/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/collapsible)

Updates `@radix-ui/react-dialog` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.16 to 2.1.24
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-label` from 2.1.8 to 2.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)

Updates `@radix-ui/react-popover` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-progress` from 1.1.8 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)

Updates `@radix-ui/react-radio-group` from 1.3.8 to 1.4.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radio-group/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radio-group)

Updates `@radix-ui/react-scroll-area` from 1.2.10 to 1.2.18
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-select` from 2.2.6 to 2.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-separator` from 1.1.8 to 1.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)

Updates `@radix-ui/react-slot` from 1.2.4 to 1.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)

Updates `@radix-ui/react-switch` from 1.2.6 to 1.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

Updates `@radix-ui/react-tabs` from 1.1.13 to 1.1.21
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-toast` from 1.2.15 to 1.2.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/toast/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/toast)

Updates `@radix-ui/react-tooltip` from 1.2.8 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

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

Updates `@simplewebauthn/server` from 13.3.1 to 13.3.3
- [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/v13.3.3/packages/server)

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

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 `axios` from 1.18.0 to 1.20.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.18.0...v1.20.0)

Updates `express-rate-limit` from 8.5.2 to 8.7.0
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.5.2...v8.7.0)

Updates `gpt-tokenizer` from 3.4.0 to 4.0.0
- [Release notes](https://github.com/niieani/gpt-tokenizer/releases)
- [Commits](https://github.com/niieani/gpt-tokenizer/compare/3.4.0...4.0.0)

Updates `helmet` from 8.2.0 to 8.3.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.2.0...v8.3.0)

Updates `input-otp` from 1.4.2 to 1.5.0
- [Release notes](https://github.com/guilhermerodz/input-otp/releases)
- [Changelog](https://github.com/guilhermerodz/input-otp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guilhermerodz/input-otp/commits/v1.5.0/packages/input-otp)

Updates `ioredis` from 5.11.0 to 6.0.0
- [Release notes](https://github.com/redis/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/redis/ioredis/compare/v5.11.0...v6.0.0)

Updates `js-yaml` from 4.3.1 to 5.4.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...5.4.1)

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

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

Updates `next` from 16.3.1 to 16.3.3
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.1...v16.3.3)

Updates `next-intl` from 4.13.0 to 4.14.1
- [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.13.0...v4.14.1)

Updates `nodemailer` from 9.0.1 to 9.1.0
- [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.0.1...v9.1.0)

Updates `pg` from 8.21.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)

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

Updates `posthog-node` from 5.35.6 to 5.51.4
- [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.51.4/packages/node)

Updates `react` from 19.2.6 to 19.2.8
- [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.2.8/packages/react)

Updates `@types/react` from 19.2.15 to 19.2.18
- [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.6 to 19.2.8
- [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.2.8/packages/react-dom)

Updates `@types/react-dom` from 19.2.3 to 19.2.5
- [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.76.1 to 7.87.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.76.1...v7.87.0)

Updates `react-icons` from 5.6.0 to 5.7.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.6.0...v5.7.0)

Updates `recharts` from 3.8.1 to 3.10.1
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.10.1)

Updates `semver` from 7.8.1 to 7.8.5
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.8.1...v7.8.5)

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

Updates `stripe` from 22.2.0 to 22.6.0
- [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.2.0...v22.6.0)

Updates `uuid` from 14.0.0 to 14.0.2
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.2)

Updates `ws` from 8.21.0 to 8.21.3
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.0...8.21.3)

Updates `yargs` from 18.0.0 to 18.1.0
- [Release notes](https://github.com/yargs/yargs/releases)
- [Changelog](https://github.com/yargs/yargs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs/compare/v18.0.0...v18.1.0)

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

Updates `@dotenvx/dotenvx` from 1.69.1 to 2.23.0
- [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/v1.69.1...v2.23.0)

Updates `@react-email/ui` from 6.9.2 to 6.9.3
- [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.3/packages/ui)

Updates `@tailwindcss/postcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-postcss)

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

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

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

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

Updates `@types/react` from 19.2.15 to 19.2.18
- [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.3 to 19.2.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

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

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

Updates `esbuild` from 0.28.0 to 0.28.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.2)

Updates `esbuild-node-externals` from 1.22.0 to 2.0.0
- [Release notes](https://github.com/pradel/esbuild-node-externals/releases)
- [Commits](https://github.com/pradel/esbuild-node-externals/compare/esbuild-node-externals-v1.22.0...esbuild-node-externals-v2.0.0)

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

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

Updates `postcss` from 8.5.23 to 8.5.26
- [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.23...8.5.26)

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

Updates `react-email` from 6.5.0 to 6.9.3
- [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.3/packages/react-email)

Updates `tailwindcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss)

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

Updates `tsx` from 4.22.3 to 4.23.13
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.3...v4.23.13)

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.60.0 to 8.68.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.68.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@asteasolutions/zod-to-openapi"
  dependency-version: 9.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1121.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 2.22.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@node-rs/argon2"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-avatar"
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-collapsible"
  dependency-version: 1.1.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-progress"
  dependency-version: 1.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-radio-group"
  dependency-version: 1.4.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.3.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-toast"
  dependency-version: 1.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@react-email/render"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@react-email/ui"
  dependency-version: 6.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.102.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.102.8
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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.4.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/sshpk"
  dependency-version: 1.17.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: axios
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: esbuild
  dependency-version: 0.28.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: esbuild-node-externals
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-config-next
  dependency-version: 16.3.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: express-rate-limit
  dependency-version: 8.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: gpt-tokenizer
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: helmet
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: input-otp
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: ioredis
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: js-yaml
  dependency-version: 5.4.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: maxmind
  dependency-version: 5.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next
  dependency-version: 16.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 9.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: pg
  dependency-version: 8.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: postcss
  dependency-version: 8.5.26
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.51.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: prettier
  dependency-version: 3.9.6
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-email
  dependency-version: 6.9.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.86.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-icons
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: recharts
  dependency-version: 3.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: semver
  dependency-version: 7.8.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: stripe
  dependency-version: 22.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tsc-alias
  dependency-version: 1.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tsx
  dependency-version: 4.23.12
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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.68.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: uuid
  dependency-version: 14.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: ws
  dependency-version: 8.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: yargs
  dependency-version: 18.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: zod
  dependency-version: 4.5.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-03 11:20:13 -04:00
dependabot[bot] 6d83614482 Bump browserslist from 4.28.2 to 4.28.8
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.2 to 4.28.8.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.8)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.28.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 11:20:13 -04:00
dependabot[bot] c319b7a65f Bump qs from 6.15.2 to 6.16.0
Bumps [qs](https://github.com/ljharb/qs) from 6.15.2 to 6.16.0.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.15.2...v6.16.0)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 11:20:12 -04:00
dependabot[bot] aa7fe7ee0e Bump fast-uri from 3.1.5 to 3.1.7
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to 3.1.7.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.7)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 11:20:12 -04:00
Owen Schwartz 5fd71df2ae Merge pull request #3672 from fosrl/dependabot/npm_and_yarn/npm-dependencies-6e680b3dcf
Bump the npm-dependencies group across 1 directory with 74 updates
2026-09-03 11:19:47 -04:00
Owen c7e1462e46 Revert tanstak components 2026-09-03 11:16:12 -04:00
Owen cfb73c9e21 Pin ts 2026-09-03 11:11:05 -04:00
dependabot[bot] 72a9040c2e Bump the npm-dependencies group across 1 directory with 74 updates
Bumps the npm-dependencies group with 74 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@asteasolutions/zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) | `8.5.0` | `9.1.0` |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1056.0` | `3.1121.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.4.0` | `5.9.1` |
| [@node-rs/argon2](https://github.com/napi-rs/node-rs) | `2.0.2` | `2.2.0` |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/avatar) | `1.1.11` | `1.2.6` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.3` | `1.3.11` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/collapsible) | `1.1.12` | `1.1.20` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.16` | `2.1.24` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.8` | `2.1.15` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.8` | `1.1.16` |
| [@radix-ui/react-radio-group](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radio-group) | `1.3.8` | `1.4.7` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.10` | `1.2.18` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.2.6` | `2.3.7` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.8` | `1.1.15` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.4` | `1.3.3` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.2.6` | `1.3.7` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.13` | `1.1.21` |
| [@radix-ui/react-toast](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/toast) | `1.2.15` | `1.2.23` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.8` | `1.2.16` |
| [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `2.0.8` | `2.1.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.1` | `13.3.3` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.100.14` | `5.102.8` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.2.4` |
| [axios](https://github.com/axios/axios) | `1.18.0` | `1.20.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.5.2` | `8.7.0` |
| [gpt-tokenizer](https://github.com/niieani/gpt-tokenizer) | `3.4.0` | `4.0.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.2.0` | `8.3.0` |
| [input-otp](https://github.com/guilhermerodz/input-otp/tree/HEAD/packages/input-otp) | `1.4.2` | `1.5.0` |
| [ioredis](https://github.com/redis/ioredis) | `5.11.0` | `6.0.0` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.3.1` | `5.4.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.17.0` | `1.38.0` |
| [maxmind](https://github.com/runk/node-maxmind) | `5.0.6` | `5.0.7` |
| [next](https://github.com/vercel/next.js) | `16.3.1` | `16.3.3` |
| [next-intl](https://github.com/amannn/next-intl) | `4.13.0` | `4.14.1` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.1` | `9.1.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.21.0` | `8.23.0` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.20.0` | `8.23.1` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.35.6` | `5.51.4` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.8` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.18` |
| [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.6` | `19.2.8` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.3` | `19.2.5` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.76.1` | `7.87.0` |
| [react-icons](https://github.com/react-icons/react-icons) | `5.6.0` | `5.7.0` |
| [recharts](https://github.com/recharts/recharts) | `3.8.1` | `3.10.1` |
| [semver](https://github.com/npm/node-semver) | `7.8.1` | `7.8.5` |
| [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) | `7.7.1` | `7.8.0` |
| [stripe](https://github.com/stripe/stripe-node) | `22.2.0` | `22.6.0` |
| [uuid](https://github.com/uuidjs/uuid) | `14.0.0` | `14.0.2` |
| [ws](https://github.com/websockets/ws) | `8.21.0` | `8.21.3` |
| [yargs](https://github.com/yargs/yargs) | `18.0.0` | `18.1.0` |
| [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.69.1` | `2.23.0` |
| [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui) | `6.9.2` | `6.9.3` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.0` | `4.3.3` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.100.14` | `5.102.8` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `26.4.0` |
| [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer) | `8.0.0` | `8.0.1` |
| [@types/sshpk](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sshpk) | `1.17.4` | `1.17.5` |
| [esbuild](https://github.com/evanw/esbuild) | `0.28.0` | `0.28.2` |
| [esbuild-node-externals](https://github.com/pradel/esbuild-node-externals) | `1.22.0` | `2.0.0` |
| [eslint](https://github.com/eslint/eslint) | `10.4.0` | `10.9.1` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.2.6` | `16.3.3` |
| [postcss](https://github.com/postcss/postcss) | `8.5.23` | `8.5.26` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.9.6` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.5.0` | `6.9.3` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.3` |
| [tsc-alias](https://github.com/justkey007/tsc-alias) | `1.8.17` | `1.9.2` |
| [tsx](https://github.com/privatenumber/tsx) | `4.22.3` | `4.23.13` |
| [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.60.0` | `8.68.0` |



Updates `@asteasolutions/zod-to-openapi` from 8.5.0 to 9.1.0
- [Release notes](https://github.com/asteasolutions/zod-to-openapi/releases)
- [Commits](https://github.com/asteasolutions/zod-to-openapi/compare/v8.5.0...v9.1.0)

Updates `@aws-sdk/client-s3` from 3.1056.0 to 3.1121.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.1121.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.4.0 to 5.9.1
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.4.0...v5.9.1)

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

Updates `@radix-ui/react-avatar` from 1.1.11 to 1.2.6
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/avatar)

Updates `@radix-ui/react-checkbox` from 1.3.3 to 1.3.11
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-collapsible` from 1.1.12 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/collapsible/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/collapsible)

Updates `@radix-ui/react-dialog` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.16 to 2.1.24
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-label` from 2.1.8 to 2.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)

Updates `@radix-ui/react-popover` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-progress` from 1.1.8 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)

Updates `@radix-ui/react-radio-group` from 1.3.8 to 1.4.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radio-group/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radio-group)

Updates `@radix-ui/react-scroll-area` from 1.2.10 to 1.2.18
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-select` from 2.2.6 to 2.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-separator` from 1.1.8 to 1.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)

Updates `@radix-ui/react-slot` from 1.2.4 to 1.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)

Updates `@radix-ui/react-switch` from 1.2.6 to 1.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

Updates `@radix-ui/react-tabs` from 1.1.13 to 1.1.21
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-toast` from 1.2.15 to 1.2.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/toast/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/toast)

Updates `@radix-ui/react-tooltip` from 1.2.8 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

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

Updates `@simplewebauthn/server` from 13.3.1 to 13.3.3
- [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/v13.3.3/packages/server)

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

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 `axios` from 1.18.0 to 1.20.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.18.0...v1.20.0)

Updates `express-rate-limit` from 8.5.2 to 8.7.0
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.5.2...v8.7.0)

Updates `gpt-tokenizer` from 3.4.0 to 4.0.0
- [Release notes](https://github.com/niieani/gpt-tokenizer/releases)
- [Commits](https://github.com/niieani/gpt-tokenizer/compare/3.4.0...4.0.0)

Updates `helmet` from 8.2.0 to 8.3.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.2.0...v8.3.0)

Updates `input-otp` from 1.4.2 to 1.5.0
- [Release notes](https://github.com/guilhermerodz/input-otp/releases)
- [Changelog](https://github.com/guilhermerodz/input-otp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guilhermerodz/input-otp/commits/v1.5.0/packages/input-otp)

Updates `ioredis` from 5.11.0 to 6.0.0
- [Release notes](https://github.com/redis/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/redis/ioredis/compare/v5.11.0...v6.0.0)

Updates `js-yaml` from 4.3.1 to 5.4.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...5.4.1)

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

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

Updates `next` from 16.3.1 to 16.3.3
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.1...v16.3.3)

Updates `next-intl` from 4.13.0 to 4.14.1
- [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.13.0...v4.14.1)

Updates `nodemailer` from 9.0.1 to 9.1.0
- [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.0.1...v9.1.0)

Updates `pg` from 8.21.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)

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

Updates `posthog-node` from 5.35.6 to 5.51.4
- [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.51.4/packages/node)

Updates `react` from 19.2.6 to 19.2.8
- [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.2.8/packages/react)

Updates `@types/react` from 19.2.15 to 19.2.18
- [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.6 to 19.2.8
- [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.2.8/packages/react-dom)

Updates `@types/react-dom` from 19.2.3 to 19.2.5
- [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.76.1 to 7.87.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.76.1...v7.87.0)

Updates `react-icons` from 5.6.0 to 5.7.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.6.0...v5.7.0)

Updates `recharts` from 3.8.1 to 3.10.1
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.10.1)

Updates `semver` from 7.8.1 to 7.8.5
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.8.1...v7.8.5)

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

Updates `stripe` from 22.2.0 to 22.6.0
- [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.2.0...v22.6.0)

Updates `uuid` from 14.0.0 to 14.0.2
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.2)

Updates `ws` from 8.21.0 to 8.21.3
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.0...8.21.3)

Updates `yargs` from 18.0.0 to 18.1.0
- [Release notes](https://github.com/yargs/yargs/releases)
- [Changelog](https://github.com/yargs/yargs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs/compare/v18.0.0...v18.1.0)

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

Updates `@dotenvx/dotenvx` from 1.69.1 to 2.23.0
- [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/v1.69.1...v2.23.0)

Updates `@react-email/ui` from 6.9.2 to 6.9.3
- [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.3/packages/ui)

Updates `@tailwindcss/postcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-postcss)

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

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

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

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

Updates `@types/react` from 19.2.15 to 19.2.18
- [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.3 to 19.2.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

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

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

Updates `esbuild` from 0.28.0 to 0.28.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.2)

Updates `esbuild-node-externals` from 1.22.0 to 2.0.0
- [Release notes](https://github.com/pradel/esbuild-node-externals/releases)
- [Commits](https://github.com/pradel/esbuild-node-externals/compare/esbuild-node-externals-v1.22.0...esbuild-node-externals-v2.0.0)

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

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

Updates `postcss` from 8.5.23 to 8.5.26
- [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.23...8.5.26)

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

Updates `react-email` from 6.5.0 to 6.9.3
- [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.3/packages/react-email)

Updates `tailwindcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss)

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

Updates `tsx` from 4.22.3 to 4.23.13
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.3...v4.23.13)

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.60.0 to 8.68.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.68.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@asteasolutions/zod-to-openapi"
  dependency-version: 9.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1121.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 2.22.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@node-rs/argon2"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-avatar"
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-collapsible"
  dependency-version: 1.1.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-progress"
  dependency-version: 1.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-radio-group"
  dependency-version: 1.4.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.3.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-toast"
  dependency-version: 1.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@react-email/render"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@react-email/ui"
  dependency-version: 6.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.102.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.102.8
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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.4.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/sshpk"
  dependency-version: 1.17.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: axios
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: esbuild
  dependency-version: 0.28.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: esbuild-node-externals
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-config-next
  dependency-version: 16.3.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: express-rate-limit
  dependency-version: 8.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: gpt-tokenizer
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: helmet
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: input-otp
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: ioredis
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: js-yaml
  dependency-version: 5.4.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: maxmind
  dependency-version: 5.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next
  dependency-version: 16.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 9.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: pg
  dependency-version: 8.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: postcss
  dependency-version: 8.5.26
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.51.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: prettier
  dependency-version: 3.9.6
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-email
  dependency-version: 6.9.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.86.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-icons
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: recharts
  dependency-version: 3.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: semver
  dependency-version: 7.8.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: stripe
  dependency-version: 22.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tsc-alias
  dependency-version: 1.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tsx
  dependency-version: 4.23.12
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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.68.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: uuid
  dependency-version: 14.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: ws
  dependency-version: 8.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: yargs
  dependency-version: 18.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: zod
  dependency-version: 4.5.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-03 14:58:30 +00:00
Owen Schwartz ce53b8c73d Merge pull request #3687 from fosrl/dependabot/npm_and_yarn/browserslist-4.28.8
Bump browserslist from 4.28.2 to 4.28.8
2026-09-03 10:52:28 -04:00
Owen Schwartz 6ff2b3e2fe Merge pull request #3688 from fosrl/crowdin_dev
New Crowdin updates
2026-09-03 10:52:08 -04:00
Owen 4dada38cb0 Please eslint 2026-09-03 10:51:46 -04:00
Owen Schwartz 732ea034f8 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-09-03 10:48:02 -04:00
Owen Schwartz 581137c486 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-09-03 10:48:00 -04:00
Owen Schwartz d75c6da3be New translations en-us.json (Turkish)
[ci skip]
2026-09-03 10:47:57 -04:00
Owen Schwartz 11b51202b8 New translations en-us.json (Russian)
[ci skip]
2026-09-03 10:47:55 -04:00
Owen Schwartz c35de18b9b New translations en-us.json (Portuguese)
[ci skip]
2026-09-03 10:47:53 -04:00
Owen Schwartz 353273a9f2 New translations en-us.json (Polish)
[ci skip]
2026-09-03 10:47:50 -04:00
Owen Schwartz 7c20a292e1 New translations en-us.json (Dutch)
[ci skip]
2026-09-03 10:47:48 -04:00
Owen Schwartz 6c13d6e343 New translations en-us.json (Korean)
[ci skip]
2026-09-03 10:47:46 -04:00
Owen Schwartz e641a8ce1b New translations en-us.json (Italian)
[ci skip]
2026-09-03 10:47:44 -04:00
Owen Schwartz a3419f60d6 New translations en-us.json (German)
[ci skip]
2026-09-03 10:47:41 -04:00
Owen Schwartz 2db3051a1a New translations en-us.json (Danish)
[ci skip]
2026-09-03 10:47:39 -04:00
Owen Schwartz 3c49b6f3d6 New translations en-us.json (Czech)
[ci skip]
2026-09-03 10:47:37 -04:00
Owen Schwartz 9ad4e36fa9 New translations en-us.json (Bulgarian)
[ci skip]
2026-09-03 10:47:34 -04:00
Owen Schwartz c489ed5724 New translations en-us.json (Spanish)
[ci skip]
2026-09-03 10:47:32 -04:00
Owen Schwartz fca3a1e5fa New translations en-us.json (French)
[ci skip]
2026-09-03 10:47:30 -04:00
dependabot[bot] b29348d004 Bump browserslist from 4.28.2 to 4.28.8
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.2 to 4.28.8.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.8)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.28.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 14:47:29 +00:00
Owen Schwartz 337d9a33a3 Merge pull request #3684 from fosrl/dependabot/npm_and_yarn/qs-6.16.0
Bump qs from 6.15.2 to 6.16.0
2026-09-03 10:46:26 -04:00
Owen Schwartz c61e96b1f0 Merge pull request #3682 from fosrl/dependabot/npm_and_yarn/fast-uri-3.1.7
Bump fast-uri from 3.1.5 to 3.1.7
2026-09-03 10:46:16 -04:00
dependabot[bot] 35bc692892 Bump qs from 6.15.2 to 6.16.0
Bumps [qs](https://github.com/ljharb/qs) from 6.15.2 to 6.16.0.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.15.2...v6.16.0)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 06:50:13 +00:00
dependabot[bot] 714e7e0ba4 Bump fast-uri from 3.1.5 to 3.1.7
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to 3.1.7.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.7)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 01:36:44 +00:00
Owen 49d5b0ec34 Increase maxRetriesPerRequest for Redis connections to improve resilience 2026-09-02 11:57:50 -04:00
Owen e0937a3afa Add validation for health check hostname
Fixes #3677
2026-09-02 10:42:59 -04:00
Owen 8d7e73afa8 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-09-02 10:32:24 -04:00
Owen a5ce56ea89 Move the session logs to private where it should be 2026-09-01 17:56:36 -04:00
miloschwartz 2f013335f9 make user lookup deterministic in blueprints by sorting 2026-09-01 15:36:39 -04:00
miloschwartz b4f6ae74d7 show warning when changing idp identifier mapping 2026-09-01 11:38:53 -04:00
Owen f1711ee0b0 Make it more clear that its a prefer 2026-09-01 10:09:16 -04:00
Owen 780d767a65 Merge branch 'main' into dev 2026-08-31 17:07:56 -04:00
Owen Schwartz d6d923e972 Merge pull request #3628 from aithal007/perf/add-fk-indexes
perf: add secondary FK indexes to core OSS schemas
2026-08-31 17:05:04 -04:00
Parikshith 41139f2fd0 perf: add secondary FK indexes to core OSS schemas
Add 13 secondary indexes across 10 tables in the SQLite schema and
5 missing indexes to the PostgreSQL schema.

All list endpoints (listSites, listResources, listClients, listRoles,
listTargets, listUserOrgs) filter and join on these FK columns. In the
SQLite schema, no secondary indexes existed at all on these foreign keys,
forcing full sequential scans on every paginated request and session lookup.

SQLite changes (13 new indexes):
  - sites.orgId
  - resources.orgId
  - targets.resourceId, targets.siteId
  - newt.siteId
  - clients.orgId, clients.userId
  - labels.orgId
  - olms.userId
  - session.userId
  - userOrgs.userId, userOrgs.orgId
  - roles.orgId

PG changes (5 new indexes, rest already present):
  - labels.orgId
  - session.userId
  - userOrgs.userId, userOrgs.orgId
  - roles.orgId
  - olms.userId
2026-08-31 17:04:01 -04:00
Owen Schwartz ebcdeab414 Merge pull request #3597 from shleeable/patch-11
Fix: License.check can fail silently
2026-08-31 17:02:53 -04:00
Owen 2cc7d03ace Update to fall back but still use cache 2026-08-31 17:02:03 -04:00
Owen Schwartz c36cf698c1 Merge pull request #3627 from fosrl/feat/ip-filtering
feat: Add IP column filtering to tables
2026-08-31 16:59:34 -04:00
Shlee 34671c6b13 Update license.ts 2026-08-31 16:57:36 -04:00
Owen Schwartz 8dfc95347f Merge pull request #3655 from moverq1337/fix/access-token-delete-root-key
Fix access token deletion for root API keys
2026-08-31 16:54:18 -04:00
Owen Schwartz 22db0319c2 Merge pull request #3654 from iMord0/crowdsec-v1.7.1
Crowdsec v1.7.1
2026-08-31 16:50:59 -04:00
miloschwartz 39722d30af resolve security-key login only for a unique internal user 2026-08-31 14:39:36 -04:00
miloschwartz 8aef14cf9f harden initial server-admin setup against duplicate users and races 2026-08-31 12:36:42 -04:00
Owen Schwartz 0dece5fef1 Merge pull request #3665 from fosrl/dev
Fix domain namespaces and country is not in blueprints
2026-08-31 11:03:06 -04:00
Owen e7f38c089f Merge branch 'main' into dev 2026-08-31 11:02:01 -04:00
miloschwartz dd0a5a359a check for namespace domain before blocking org check 2026-08-31 10:59:50 -04:00
moverq1337 1650ece0c3 Fix root API key access in verifyApiKeyAccessTokenAccess 2026-08-28 22:45:31 +03:00
iMord0 7f94d99455 Update CrowdSec plugin version
Updated CrowdSec plugin version from v1.4.4 to v1.7.1
2026-08-28 20:51:51 +02:00
iMord0 1f9e99219d Updated CrowdSec plugin version
Updated CrowdSec plugin version from v1.4.4 to v1.7.8
2026-08-28 20:42:26 +02:00
Owen Schwartz bc56a2bed0 Merge pull request #3647 from argueta-xyz/country-is-not-rule-via-blueprint
Allow COUNTRY_IS_NOT rules to be created via Blueprints
2026-08-28 10:52:24 -04:00
Owen Schwartz 49dcc590ce Merge pull request #3651 from fosrl/dev
Update readme
2026-08-28 10:19:58 -04:00
Owen da3e3ff33f Fix typo 2026-08-28 10:19:35 -04:00
Owen Schwartz 69d539f107 Merge pull request #3650 from fosrl/dev
Update readme, tel, and fix EE feature flag
2026-08-28 09:55:40 -04:00
Owen 872e0f9ae1 Merge branch 'main' into dev 2026-08-28 09:54:46 -04:00
Owen 5b3713a72f Update readme 2026-08-28 09:54:07 -04:00
Owen Schwartz f02be1fdbf Merge pull request #3649 from fosrl/dependabot/npm_and_yarn/multi-2e40a8c091
Bump ws and socket.io-adapter
2026-08-28 09:21:25 -04:00
dependabot[bot] 0bf04cf0cd Bump ws and socket.io-adapter
Bumps [ws](https://github.com/websockets/ws) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.18.3 to 8.21.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.21.0)

Updates `socket.io-adapter` from 2.5.6 to 2.5.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.8)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.21.0
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-28 13:12:35 +00:00
Alejandro Argueta 7cda28d685 Update RuleSchema to allow COUNTRY_IS_NOT rules to be created via blueprints as well. 2026-08-27 14:18:48 -07:00
Owen 60bc74c4df Add AI provider and usage metrics to telemetry data collection 2026-08-27 16:54:04 -04:00
Owen 48ab6c501f Fix #3646 2026-08-27 16:37:53 -04:00
Fred KISSIE d00b9478a2 Merge branch 'dev' into feat/ip-filtering 2026-08-24 22:06:55 +02:00
Fred KISSIE 4ddf36ebcc 💄 some last UI fixes 2026-08-24 20:39:30 +02:00
Fred KISSIE 28b32fe6f7 🏷️ fix types 2026-08-21 23:26:18 +02:00
Fred KISSIE adfb6003d9 Merge branch 'dev' into feat/ip-filtering 2026-08-21 23:24:09 +02:00
Fred KISSIE 6a5ecab013 Implement IP filtering for admin access logs 2026-08-21 22:49:45 +02:00
Fred KISSIE 2c197fab9f IP filtering on request log table finished 2026-08-21 21:04:40 +02:00
Fred KISSIE 65e4fe91b9 🚧 wip: add ip is column filter 2026-08-20 23:59:31 +02:00
Fred KISSIE 52c078a489 💄 ui 2026-08-18 23:28:31 +02:00
Fred KISSIE 195f67c6eb 💄 QoL for location column 2026-08-18 21:08:16 +02:00
Fred KISSIE 668a04bcd2 🚧 wip: IP column filtering 2026-08-14 21:52:16 +02:00
244 changed files with 12093 additions and 6550 deletions
+8
View File
@@ -34,6 +34,14 @@ body:
validations:
required: true
- type: textarea
attributes:
label: AI Disclosure
description: |
If you used AI to help write this issue, please disclose it here. This is important for transparency and helps maintain the integrity of the issue tracking process.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
-1
View File
@@ -46,7 +46,6 @@ public/branding
server/db/index.ts
server/build.ts
postgres/
dynamic/
*.mmdb
scratch/
tsconfig.json
+29 -5
View File
@@ -37,11 +37,22 @@
<p align="center">
<strong>
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
Get started with Pangolin Cloud at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
</strong>
</p>
Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure connectivity to infrastructure anywhere. It combines reverse-proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to private resources with NAT traversal, all with granular access control.
Pangolin is an open-source SASE platform, built on WireGuard®, with a simple mission: connect and protect your users, wherever they are. It brings networking and security together as one system including a zero-trust VPN, zero-trust reverse proxy, privileged access management, and an identity-aware AI gateway, all sharing one identity and policy model. It's the same idea behind platforms like Cloudflare One, Zscaler, and Prisma but open, self-hostable, and built to stay light and easy to deploy.
### Networking and security that's unified, open, and simple
Legacy SASE platforms got the idea right: connectivity and security belong together. But they delivered it as a heavyweight, closed, cloud-locked stack assembled from years of patchwork. Pangolin exists to do that unification differently, in the open, self-hostable, and simple enough that administrators actually enjoy running it.
* **Open source, not a black box**: the code is open and auditable, so you can see exactly how your traffic is handled and how access decisions get made, instead of trusting a closed cloud control plane.
* **Networking and security as one platform**: sites, reverse proxy, client access, RBAC, and the AI gateway share one identity and policy model, so protecting users and connecting them are executed together.
* **Lightweight by design**: the whole platform is built to stay small and fast: easy to self-host on a small server, with a lightweight, user-space connector that goes in your private networks.
* **Enjoyable to use**: a clean, modern interface and a setup flow that gets out of your way, so managing access feels simple instead of like fighting a legacy admin console.
* **Zero trust from day one**: access is granted per resource, not per network, with identity provider integration, role-based access control, and full audit logging.
* **Run it your way**: self-host the Community Edition for free, step up to the Enterprise Edition for advanced features, or use Pangolin Cloud if you'd rather not manage infrastructure at all.
## Installation
@@ -53,9 +64,9 @@ Pangolin is an open-source, identity-based remote access platform built on WireG
## Deployment Options
- **Pangolin Cloud** - Fully managed service - no infrastructure required.
- **Self-Host: Community Edition** - Free, open source, and licensed under AGPL-3.
- **Self-Host: Enterprise Edition** - Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
- **Pangolin Cloud** - Fully managed service with no infrastructure required.
- **Self-Host: Community Edition** - Free, open-source, and licensed under AGPL-3.
- **Self-Host: Enterprise Edition** - Open-core, and licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
## Key Features
@@ -99,6 +110,19 @@ Access private resources like SSH servers, databases, RDP, and entire network ra
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
### Identity-aware AI gateway
Put an identity-aware proxy in front of public cloud (OpenAI, Anthropic, Gemini, etc.) and self-hosted model servers (Ollama, vLLM, Mistral, etc.) so coding agents and AI clients call a single Pangolin URL. Publish it as a public resource with personal API keys, or keep it private on a client tunnel where the connected client is the credential for keyless access. Budgets, session history, and usage analytics sit in front of every call.
* Access self-hosted models (vLLM, Ollama, etc) alongside cloud models (OpenAI, Anthropic, etc) in one place
* Keyless access by authenticating users with the Pangolin desktop client
* Or, provide users with personal API keys
* Control costs and token usage by setting budgets
* Audit with detailed session history and analytics
* Integrate AI clients and coding agents (Claude Code, Codex, OpenCode, etc)
<img src="public/screenshots/expanded-session-logs.png" alt="AI Session Logs" width="100%" />
### Give users and roles access to resources
Use Pangolin's built-in users or bring your own identity provider and set up role-based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
+1 -1
View File
@@ -23,7 +23,7 @@ export const clearExitNodes: CommandModule<
// Delete all exit nodes
const deletedCount = await db
.delete(exitNodes)
.where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)) .returning();; // delete all
.where(eq(exitNodes.exitNodeId, exitNodes.exitNodeId)).returning();; // delete all
console.log(`Deleted ${deletedCount.length} exit node(s) from the database`);
+1 -1
View File
@@ -5,7 +5,7 @@ import { encrypt } from "@server/lib/crypto";
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
import { generateCA } from "@server/lib/sshCA";
import fs from "fs";
import yaml from "js-yaml";
import * as yaml from "js-yaml";
type GenerateOrgCaKeysArgs = {
orgId: string;
+1 -1
View File
@@ -4,7 +4,7 @@ import { encrypt, decrypt } from "@server/lib/crypto";
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
import { eq } from "drizzle-orm";
import fs from "fs";
import yaml from "js-yaml";
import * as yaml from "js-yaml";
type RotateServerSecretArgs = {
"old-secret": string;
+3
View File
@@ -0,0 +1,3 @@
## 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).
@@ -0,0 +1,23 @@
services:
postgres:
image: postgres:17
container_name: postgres
environment:
POSTGRES_DB: postgres # Default database name
POSTGRES_USER: postgres # Default user
POSTGRES_PASSWORD: password # Default password (change for production!)
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: always
redis:
image: redis:latest
container_name: redis
ports:
- "6379:6379"
restart: always
volumes:
postgres_data:
@@ -0,0 +1,38 @@
# To see all available options, please visit the docs:
# https://docs.pangolin.net/
gerbil:
start_port: 51820
base_endpoint: "<THIS_NODE_EXTERNAL_IP>"
exit_node_name: "node1"
app:
dashboard_url: "https://pangolin.example.com"
log_level: "info"
postgres:
connection_string: postgresql://<POSTGRES_USERNAME>:<POSTGRES_PASSWORD>@<POSTGRES_INTERNAL_HOST>:5432/postgres
traefik:
site_types: ["newt"] # Wireguard and local sites are not support in clustering
file_mode: true # Pangolin will generate and save yaml files in a shared volume
server:
secret: "<SECRET>"
cors:
origins: ["https://pangolin.example.com"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
maxmind_db_path: "./config/GeoLite2-Country.mmdb" # Make sure to download and place into the config dir
maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: false
enable_acme_cert_sync: false
disable_local_sites: true
disable_basic_wireguard_sites: true
disable_config_managed_domains: true
@@ -0,0 +1,67 @@
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
routers:
# Next.js router (handles everything except API and WebSocket paths)
next-router:
rule: "!PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- dashboard
middlewares:
- badger
# API router (handles /api/v1 paths)
api-router:
rule: "PathPrefix(`/api/v1`)"
service: api-service
entryPoints:
- dashboard
middlewares:
- badger
# WebSocket router
ws-router:
rule: "PathPrefix(`/`)"
service: api-service
entryPoints:
- dashboard
middlewares:
- badger
services:
next-service:
loadBalancer:
servers:
- url: "http://pangolin:3002" # Next.js server
api-service:
loadBalancer:
servers:
- url: "http://pangolin:3000" # API/WebSocket server
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2
udp:
routers:
dns-router:
entryPoints:
- dns
service: dns-service
services:
dns-service:
loadBalancer:
servers:
- address: "pangolin:53"
@@ -0,0 +1,18 @@
app:
region: "region1"
identity_provider_mode: "org"
redis:
host: "<REDIS_INTERNAL_HOST>"
port: 6379
flags:
enable_redis: true
use_pangolin_dns: true
acme:
cert_mode: "pangolin"
contact_email: "<CONTACT_EMAIL>"
enable_acme_client: true
dns:
enabled: true
nameserver_name: "ns.example.com"
cname_extension: "cname.example.com"
site_extension: "site.example.com" # Optional
@@ -0,0 +1,45 @@
providers:
file:
directory: "/var/dynamic"
watch: true
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.7.0"
log:
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
proxyProtocol: # We trust gerbil upstream
trustedIPs:
- 0.0.0.0/0
- ::1/128
transport:
respondingTimeouts:
readTimeout: "30m"
http:
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
dashboard:
address: ":3000"
dns:
address: ":53/udp"
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
@@ -0,0 +1,62 @@
name: pangolin
services:
pangolin:
image: docker.io/fosrl/pangolin:ee-latest
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
- ./config/certificates:/var/certificates
- ./config/dynamic:/var/dynamic
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
gerbil:
image: docker.io/fosrl/gerbil:latest
container_name: gerbil
restart: unless-stopped
depends_on:
pangolin:
condition: service_healthy
command:
- --reachableAt=http://<NODE1_INTERNAL_IP>:3004
- --generateAndSaveKeyTo=/var/config/key
- --remoteConfig=http://pangolin:3001/api/v1/
- --trusted-upstreams=<NODE1_EXTERNAL_IP>,<NODE2_EXTERNAL_IP>
volumes:
- ./config/:/var/config
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- 51820:51820/udp # wireguard
- 21820:21820/udp # relay
- 53:53/udp # DNS
- 443:8443 # resources
- 80:80 # web
- 3004:3004 # gerbil api
- 3000:3000 # Pangolin UI
traefik:
image: docker.io/traefik:v3.7.11
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil # Ports appear on the gerbil service
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/traefik/logs:/var/log/traefik
- ./config/certificates:/var/certificates:ro
- ./config/dynamic:/var/dynamic:ro
networks:
default:
driver: bridge
name: pangolin
@@ -0,0 +1,38 @@
# To see all available options, please visit the docs:
# https://docs.pangolin.net/
gerbil:
start_port: 51820
base_endpoint: "<THIS_NODE_EXTERNAL_IP>"
exit_node_name: "node2"
app:
dashboard_url: "https://pangolin.example.com"
log_level: "info"
postgres:
connection_string: postgresql://<POSTGRES_USERNAME>:<POSTGRES_PASSWORD>@<POSTGRES_INTERNAL_HOST>:5432/postgres
traefik:
site_types: ["newt"] # Wireguard and local sites are not support in clustering
file_mode: true # Pangolin will generate and save yaml files in a shared volume
server:
secret: "<SECRET>"
cors:
origins: ["https://pangolin.example.com"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
maxmind_db_path: "./config/GeoLite2-Country.mmdb" # Make sure to download and place into the config dir
maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: false
enable_acme_cert_sync: false
disable_local_sites: true
disable_basic_wireguard_sites: true
disable_config_managed_domains: true
@@ -0,0 +1,67 @@
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
routers:
# Next.js router (handles everything except API and WebSocket paths)
next-router:
rule: "!PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- dashboard
middlewares:
- badger
# API router (handles /api/v1 paths)
api-router:
rule: "PathPrefix(`/api/v1`)"
service: api-service
entryPoints:
- dashboard
middlewares:
- badger
# WebSocket router
ws-router:
rule: "PathPrefix(`/`)"
service: api-service
entryPoints:
- dashboard
middlewares:
- badger
services:
next-service:
loadBalancer:
servers:
- url: "http://pangolin:3002" # Next.js server
api-service:
loadBalancer:
servers:
- url: "http://pangolin:3000" # API/WebSocket server
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2
udp:
routers:
dns-router:
entryPoints:
- dns
service: dns-service
services:
dns-service:
loadBalancer:
servers:
- address: "pangolin:53"
@@ -0,0 +1,16 @@
app:
region: "region1"
identity_provider_mode: "org"
redis:
host: "<REDIS_INTERNAL_HOST>"
port: 6379
flags:
enable_redis: true
use_pangolin_dns: true
acme:
cert_mode: "pangolin"
dns:
enabled: true
nameserver_name: "ns.example.com"
cname_extension: "cname.example.com"
site_extension: "site.example.com" # Optional
@@ -0,0 +1,45 @@
providers:
file:
directory: "/var/dynamic"
watch: true
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.7.0"
log:
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
proxyProtocol: # We trust gerbil upstream
trustedIPs:
- 0.0.0.0/0
- ::1/128
transport:
respondingTimeouts:
readTimeout: "30m"
http:
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
dashboard:
address: ":3000"
dns:
address: ":53/udp"
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
@@ -0,0 +1,62 @@
name: pangolin
services:
pangolin:
image: docker.io/fosrl/pangolin:ee-latest
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
- ./config/certificates:/var/certificates
- ./config/dynamic:/var/dynamic
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
gerbil:
image: docker.io/fosrl/gerbil:latest
container_name: gerbil
restart: unless-stopped
depends_on:
pangolin:
condition: service_healthy
command:
- --reachableAt=http://<NODE1_INTERNAL_IP>:3004
- --generateAndSaveKeyTo=/var/config/key
- --remoteConfig=http://pangolin:3001/api/v1/
- --trusted-upstreams=<NODE1_EXTERNAL_IP>,<NODE2_EXTERNAL_IP>
volumes:
- ./config/:/var/config
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- 51820:51820/udp # wireguard
- 21820:21820/udp # relay
- 53:53/udp # DNS
- 443:8443 # resources
- 80:80 # web
- 3004:3004 # gerbil api
- 3000:3000 # Pangolin UI
traefik:
image: docker.io/traefik:v3.7.11
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil # Ports appear on the gerbil service
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/traefik/logs:/var/log/traefik
- ./config/certificates:/var/certificates:ro
- ./config/dynamic:/var/dynamic:ro
networks:
default:
driver: bridge
name: pangolin
+1 -1
View File
@@ -1,3 +1,3 @@
files:
- source: /messages/en-US.json
translation: /messages/%locale%.json
translation: /messages/%locale%.json
+2
View File
@@ -0,0 +1,2 @@
tls:
certificates: []
+1
View File
@@ -0,0 +1 @@
{}
+1 -1
View File
@@ -16,7 +16,7 @@ experimental:
version: "{{.BadgerVersion}}"
crowdsec: # CrowdSec plugin configuration added
moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
version: "v1.4.4"
version: "v1.7.1"
log:
level: "INFO"
+44 -4
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Цели на този сайт",
"siteSetting": "Настройки на {siteName}",
"siteNewtTunnel": "Нов Сайт (Препоръчително)",
"pangolinSite": "Сайт Панголиин",
"siteNewtTunnelDescription": "Най-лесният начин да създадете точка за достъп до всяка мрежа. Няма нужда от допълнителни настройки.",
"siteWg": "Основен WireGuard",
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required. ONLY WORKS ON SELF HOSTED NODES",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Изтрийте API ключа",
"apiKeysManage": "Управление на API ключове",
"apiKeysDescription": "API ключове се използват за удостоверяване с интеграционния API",
"orgsManage": "Управление на организации",
"orgsDescription": "Преглед и управление на всички организации в тази инстанция",
"provisioningKeysTitle": "Ключ за осигуряване",
"provisioningKeysManage": "Управление на ключове за осигуряване",
"provisioningKeysDescription": "Ключовете за осигуряване се използват за удостоверяване на автоматичното осигуряване на сайта за вашата организация.",
@@ -716,6 +719,7 @@
"nameOptional": "Име (по избор)",
"accessControls": "Контрол на достъпа",
"userDescription2": "Управление на настройките на този потребител",
"userGeneralSettingsDescription": "Управление на ролите и настройките на този потребител в организацията",
"accessRoleErrorAdd": "Неуспешно добавяне на потребител към роля",
"accessRoleErrorAddDescription": "Възникна грешка при добавяне на потребителя към ролята.",
"userSaved": "Потребителят е запазен",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Когато правилата са изключени, целият трафик преминава към удостоверяване.",
"policyAccessRulesFallthroughOn": "Когато няма съвпадение, трафикът преминава към удостоверяване.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/администратор/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Запазете правилата",
"resourceErrorCreate": "Грешка при създаване на ресурс",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Научете повече за JMESPath",
"idpJmespathLabel": "Идентификатор на пътя",
"idpJmespathLabelDescription": "Пътят към идентификатора на потребителя в ID токена",
"idpIdentifierChangeTitle": "Предупреждение за промяна на пътя на идентификатора",
"idpIdentifierChangeDescription": "Ще промените пътя на идентификатора. Това ще повлияе на начина, по който съществуващите потребители са разпределени. Потребители, които преди са влизали чрез този доставчик на идентичности, може вече да не бъдат разпознавани като същите потребители.",
"idpIdentifierChangeConfirmMessage": "Потвърждавам",
"idpIdentifierChangeWarningText": "Това ще повлияе на начина, по който съществуващите потребители са разпределени",
"idpJmespathEmailPathOptional": "Път за имейл (по избор)",
"idpJmespathEmailPathOptionalDescription": "Пътят до имейла на потребителя в ID токена",
"idpJmespathNamePathOptional": "Път (по избор) на име",
@@ -1420,6 +1428,24 @@
"logoutError": "Грешка при излизане",
"signingAs": "Влезли сте като",
"serverAdmin": "Администратор на сървъра",
"promoteServerAdmin": "Повишаване до администратор на сървъра",
"promoteServerAdminTitle": "Повишаване до Admin на сървъра",
"promoteServerAdminQuestion": "Сигурни ли сте, че искате да повишите {selectedUser} до администратор на сървъра?",
"promoteServerAdminMessage": "Администраторите на сървъра имат най-високите привилегии и могат да управляват сървъра.",
"promoteServerAdminWarning": "Това може да бъде отменено по всяко време посредством понижаване на потребителя.",
"promoteServerAdminConfirm": "Повишаване до Admin на сървъра",
"promoteServerAdminSuccess": "Потребителят е повишен",
"promoteServerAdminSuccessDescription": "{selectedUser} вече е администратор на сървъра.",
"promoteServerAdminError": "Неуспешно повишаване на потребител",
"demoteServerAdmin": "Понижаване от администратор на сървъра",
"demoteServerAdminTitle": "Понижаване от Admin на сървъра",
"demoteServerAdminQuestion": "Сигурни ли сте, че искате да понижите {selectedUser} от администратор на сървъра?",
"demoteServerAdminMessage": "{selectedUser} ще загуби всички привилегии на администратор на сървъра.",
"demoteServerAdminWarning": "Това може да бъде отменено по всяко време посредством повишаване на потребителя.",
"demoteServerAdminConfirm": "Понижаване от администратор на сървъра",
"demoteServerAdminSuccess": "Потребителят е понижен",
"demoteServerAdminSuccessDescription": "{selectedUser} вече не е администратор на сървъра.",
"demoteServerAdminError": "Неуспешно понижаване на потребител",
"managedSelfhosted": "Управлявано Самостоятелно-хоствано",
"otpEnable": "Включване на двуфакторен",
"otpDisable": "Изключване на двуфакторен",
@@ -1573,6 +1599,8 @@
"search": "Търси…",
"searchPlaceholder": "Търсене...",
"emptySearchOptions": "Няма намерени опции",
"ipFilterSearchPlaceholder": "Въведете IP адрес…",
"ipFilterEmptyMessage": "Въведете IP адрес, за да филтрирате по него",
"create": "Създаване",
"orgs": "Организации",
"loginError": "Възникна неочаквана грешка. Моля, опитайте отново.",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Бюджет",
"resourceBudgetSettingsDescription": "Конфигурирайте как AI порталът ограничава употребата, основавайки се на разходи или лимити на токени",
"sidebarApiKeys": "API ключове",
"sidebarOrgs": "Организации",
"sidebarProvisioning": "Осигуряване",
"sidebarSettings": "Настройки",
"sidebarAllUsers": "Всички потребители",
@@ -2596,6 +2625,7 @@
"createDomainType": "Тип:",
"createDomainName": "Име:",
"createDomainValue": "Стойност:",
"multiSelectFilterCount": "{count} избрани",
"createDomainCnameRecords": "CNAME записи",
"createDomainARecords": "A записи",
"createDomainRecordNumber": "Запис {number}",
@@ -2710,6 +2740,7 @@
"healthScheme": "Метод",
"healthSelectScheme": "Избор на метод",
"healthCheckPortInvalid": "Портът трябва да бъде между 1 и 65535",
"healthCheckHostnameInvalid": "Името на хоста не трябва да съдържа празни символи",
"healthCheckPath": "Път",
"healthHostname": "IP / Хост",
"healthPort": "Порт",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Добавете CIDR диапазон (напр. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Неуспешно зареждане на подмрежи",
"remoteExitNodeNetworkingLabelsTitle": "Етикети за Предпочитания",
"remoteExitNodeNetworkingLabelsDescription": "Сайтове с тези етикети ще бъдат принудени да се свържат чрез този отдалечен край.",
"remoteExitNodeNetworkingLabelsDescription": "Сайтовете с тези етикети ще предпочетат да се свържат чрез този отдалечен изходен възел.",
"remoteExitNodeNetworkingLabelsButtonText": "Изберете етикети...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Търсене на етикети...",
"remoteExitNodeNetworkingLabelsLoadError": "Неуспешно зареждане на етикети",
@@ -3160,7 +3191,7 @@
"idpAzureConfiguration": "Конфигурация на Azure Entra ID",
"idpAzureConfigurationDescription": "Конфигурирайте OAuth2 идентификационни данни на Azure Entra ID",
"idpTenantId": "Идентификационен номер на наемателя",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "идентификационен номер на наемателя",
"idpAzureTenantIdDescription": "Идентификационен номер на наемателя на Azure (намира се в прегледа на Azure Active Directory)",
"idpAzureClientIdDescription": "Идентификационен код на клиента за регистриране на приложение в Azure",
"idpAzureClientSecretDescription": "Секретен код на клиента за регистриране на приложение в Azure",
@@ -3470,6 +3501,7 @@
"priority": "Приоритет",
"priorityDescription": "По-високите приоритетни маршрути се оценяват първи. Приоритет = 100 означава автоматично подреждане (системата решава). Използвайте друго число, за да наложите ръчен приоритет.",
"instanceName": "Име на инстанция",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Конфигурация на съвпадение по пътека",
"pathMatchModalDescription": "Настройте как трябва да бъдат съвпадани входящите заявки въз основа на техния път.",
"pathMatchType": "Вид на съвпадението",
@@ -3759,6 +3791,7 @@
"noData": "Няма Данни",
"machineClients": "Машинни клиенти",
"install": "Инсталирай",
"downloadInstaller": "Изтегляне на инсталатора",
"run": "Изпълни",
"envFile": "Файл за среда",
"serviceFile": "Файл за услуга",
@@ -3966,6 +3999,7 @@
"disconnected": "Прекъснат",
"approvalsEmptyStateTitle": "Одобрения на устройство не са активирани",
"approvalsEmptyStateDescription": "Активирайте одобрения на устройства за роли, така че да изискват администраторско одобрение, преди потребителите да могат да свързват нови устройства.",
"approvalsEmptyStateHowToTitle": "Как да го активирате",
"approvalsEmptyStateStep1Title": "Отидете на роли",
"approvalsEmptyStateStep1Description": "Навигирайте до настройките на ролите на вашата организация, за да конфигурирате одобренията на устройства.",
"approvalsEmptyStateStep2Title": "Активирайте одобрения на устройства",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Вижте като Админ",
"resourceLauncherResourceDetailsDescription": "Информация и статус на връзката за този ресурс.",
"resourceLauncherResourceDetails": "Детайли за ресурса",
"resourceLauncherSitesDescription": "Ресурсът е достъпен чрез следните сайтове.",
"resourceLauncherViewSiteAsAdmin": "Вижте сайта като Админ",
"resourceLauncherFilterBySite": "Филтриране по сайт",
"resourceLauncherSshCommand": "SSH Команда",
"resourceLauncherSshCommandDescription": "Използвайте Pangolin CLI, за да отворите SSH сесия до този ресурс.",
"resourceLauncherAuthMethodsDescription": "Методи на автентикация, активирани за този ресурс.",
"resourceLauncherPrivateClientRequired": "Свържете се с клиент на вашето устройство, за да получите частен достъп до този ресурс.",
"resourceLauncherPrivateClientRequiredTitle": "Изисква се връзка с клиента",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Режим на unicode клавиатура",
"sessionToolbarShow": "Показване на лентата с инструменти",
"sessionToolbarHide": "Скриване на лентата с инструменти",
"actionUpdateSiteApprovals": "Обновяване на одобренията на сайта"
"actionUpdateSiteApprovals": "Обновяване на одобренията на сайта",
"check": "Проверка"
}
+90 -50
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Cíle na tomto webu",
"siteSetting": "Nastavení {siteName}",
"siteNewtTunnel": "Novinka (doporučeno)",
"pangolinSite": "Stránka Pangolin",
"siteNewtTunnelDescription": "Nejjednodušší způsob, jak vytvořit vstupní bod do jakékoli sítě. Žádné další nastavení.",
"siteWg": "Základní WireGuard",
"siteWgDescription": "Použijte jakéhokoli klienta WireGuard abyste sestavili tunel. Vyžaduje se ruční nastavení NAT.",
@@ -323,7 +324,7 @@
"resourceConfig": "Konfigurační snippety",
"resourceConfigDescription": "Zkopírujte a vložte tyto konfigurační úryvky pro nastavení TCP/UDP zdroje.",
"resourceAddEntrypoints": "Traefik: Přidat vstupní body",
"resourceExposePorts": "Gerbil: Expose Ports in Docker Compose",
"resourceExposePorts": "Gerbil: Popis port v Docker Compose",
"resourceLearnRaw": "Naučte se konfigurovat zdroje TCP/UDP",
"resourceBack": "Zpět na zdroje",
"resourceGoTo": "Přejít na dokument",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Odstranit klíč API",
"apiKeysManage": "Správa API klíčů",
"apiKeysDescription": "API klíče se používají k ověření s integračním API",
"orgsManage": "Spravovat organizace",
"orgsDescription": "Zobrazit a spravovat všechny organizace v této instanci",
"provisioningKeysTitle": "Zajišťovací klíč",
"provisioningKeysManage": "Spravovat zajišťovací klíče",
"provisioningKeysDescription": "Zajišťovací klíče slouží k ověření automatického poskytování služeb vaší organizaci.",
@@ -532,7 +535,7 @@
"userMessageRemove": "Uživatel bude odstraněn ze všech organizací a bude zcela odstraněn ze serveru.",
"userQuestionRemove": "Jste si jisti, že chcete trvale odstranit uživatele ze serveru?",
"licenseKey": "Licenční klíč",
"valid": "Valid",
"valid": "Platný",
"numberOfSites": "Počet lokalit",
"licenseKeySearch": "Hledat licenční klíče...",
"licenseKeyAdd": "Přidat licenční klíč",
@@ -609,7 +612,7 @@
"inviteSent": "Nová pozvánka byla odeslána na {email}.",
"inviteSentEmail": "Poslat uživateli oznámení e-mailem",
"inviteGenerate": "Nová pozvánka byla vygenerována pro {email}.",
"inviteDuplicateError": "Duplicate Invite",
"inviteDuplicateError": "Duplicitní pozvání",
"inviteDuplicateErrorDescription": "Pozvánka pro tohoto uživatele již existuje.",
"inviteRateLimitError": "Limit sazby překročen",
"inviteRateLimitErrorDescription": "Překročil jsi limit 3 regenerací za hodinu. Opakujte akci později.",
@@ -652,7 +655,7 @@
"ownerMustRetainAdminRole": "Vlastník organizace musí zachovat alespoň jednu roli správce.",
"usernameRequired": "Uživatelské jméno je povinné",
"idpSelectPlease": "Vyberte poskytovatele identity",
"idpGenericOidc": "Generic OAuth2/OIDC provider.",
"idpGenericOidc": "Obecný poskytovatel OAuth2/OIDC.",
"accessRoleErrorFetch": "Nepodařilo se načíst role",
"accessRoleErrorFetchDescription": "Při načítání rolí došlo k chybě",
"idpErrorFetch": "Nepodařilo se načíst poskytovatele identity",
@@ -716,6 +719,7 @@
"nameOptional": "Jméno (nepovinné)",
"accessControls": "Kontrola přístupu",
"userDescription2": "Spravovat nastavení tohoto uživatele",
"userGeneralSettingsDescription": "Spravujte role a nastavení tohoto uživatele v organizaci",
"accessRoleErrorAdd": "Přidání uživatele do role se nezdařilo",
"accessRoleErrorAddDescription": "Došlo k chybě při přidávání uživatele do role.",
"userSaved": "Uživatel uložen",
@@ -736,15 +740,15 @@
"proxyErrorTls": "Neplatné jméno TLS serveru. Použijte formát doménového jména nebo uložte prázdné pro odstranění názvu TLS serveru.",
"proxyEnableSSL": "Povolit TLS",
"proxyEnableSSLDescription": "Povolit šifrování SSL/TLS pro zabezpečená připojení HTTPS k cílům.",
"target": "Target",
"target": "Cíl",
"configureTarget": "Konfigurace cílů",
"targetErrorFetch": "Nepodařilo se načíst cíle",
"targetErrorFetchDescription": "Při načítání cílů došlo k chybě",
"siteErrorFetch": "Nepodařilo se načíst zdroj",
"siteErrorFetchDescription": "Při načítání zdroje došlo k chybě",
"targetErrorDuplicate": "Duplicate target",
"targetErrorDuplicate": "Duplicitní cíl",
"targetErrorDuplicateDescription": "Cíl s těmito nastaveními již existuje",
"targetWireGuardErrorInvalidIp": "Invalid target IP",
"targetWireGuardErrorInvalidIp": "Neplatná IP adresa cíle",
"targetWireGuardErrorInvalidIpDescription": "Cílová IP adresa musí být v podsíti webu",
"targetsUpdated": "Cíle byly aktualizovány",
"targetsUpdatedDescription": "Cíle a nastavení byly úspěšně aktualizovány",
@@ -772,11 +776,11 @@
"targetStickySessions": "Povolit Rychlé relace",
"targetStickySessionsDescription": "Zachovat spojení na stejném cíli pro celou relaci.",
"methodSelect": "Vyberte metodu",
"targetSubmit": "Add Target",
"targetSubmit": "Přidat cíl",
"targetNoOne": "Tento zdroj nemá žádné cíle. Přidejte cíl pro konfiguraci kam poslat žádosti na backend.",
"targetNoOneDescription": "Přidáním více než jednoho cíle se umožní vyvážení zatížení.",
"targetsSubmit": "Uložit Nastavení",
"addTarget": "Add Target",
"addTarget": "Přidat cíl",
"proxyMultiSiteRoundRobinNodeHelp": "Round robin routing nebude fungovat mezi lokalitami, které nejsou připojeny ke stejnému uzlu, ale failover bude fungovat.",
"targetErrorInvalidIp": "Neplatná IP adresa",
"targetErrorInvalidIpDescription": "Zadejte prosím platnou IP adresu nebo název hostitele",
@@ -892,7 +896,7 @@
"policyAuthPincodeTitle": "PIN Kód",
"policyAuthPincodeDescription": "Krátký číselný kód vyžadován pro přístup ke zdroji",
"policyAuthPincodeSummary": "Nastaven 6místný PIN",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailTitle": "Seznam povolených emailů",
"policyAuthEmailDescription": "Povolit vybraným emailovým adresám s jednorázovými hesly",
"policyAuthEmailSummary": "Povoleno {count} adres(y)",
"policyAuthEmailOtpCallout": "Povolení seznamu povolených e-mailů odešle jednorázové heslo na e-mail návštěvníka při přihlášení.",
@@ -948,7 +952,7 @@
"unknownCommand": "Neznámý příkaz",
"newtErrorFetchReleases": "Nepodařilo se načíst informace o vydání: {err}",
"newtErrorFetchLatest": "Chyba při načítání nejnovější verze: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Koncový bod",
"newtId": "ID",
"newtSecretKey": "Tajný klíč",
"newtVersion": "Verze",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Další informace o cestě JMESPath",
"idpJmespathLabel": "Cesta identifikátoru",
"idpJmespathLabelDescription": "Cesta k identifikátoru uživatele v tokenu ID",
"idpIdentifierChangeTitle": "Varování o změně cesty identifikátoru",
"idpIdentifierChangeDescription": "Chystáte se změnit cestu identifikátoru. Tímto se změní způsob mapování stávajících uživatelů. Uživatelé, kteří se dříve přihlásili přes tohoto poskytovatele identity, již nemusí být rozpoznáni jako stejní uživatelé.",
"idpIdentifierChangeConfirmMessage": "Potvrzuji",
"idpIdentifierChangeWarningText": "Toto ovlivní, jak budou mapováni stávající uživatelé",
"idpJmespathEmailPathOptional": "Cesta e-mailu (volitelné)",
"idpJmespathEmailPathOptionalDescription": "Cesta k e-mailu uživatele v ID tokenu",
"idpJmespathNamePathOptional": "Cesta k názvu (volitelné)",
@@ -1222,7 +1230,7 @@
"orgIdpRedirectUrls": "Přesměrovat URL",
"redirectUrlAbout": "O přesměrování URL",
"redirectUrlAboutDescription": "Toto je URL, na kterou budou uživatelé po ověření přesměrováni. Tuto URL je třeba nastavit v nastavení poskytovatele identity.",
"pangolinAuth": "Auth - Pangolin",
"pangolinAuth": "Autentizace - Pangolin",
"verificationCodeLengthRequirements": "Váš ověřovací kód musí mít 8 znaků.",
"errorOccurred": "Došlo k chybě",
"emailErrorVerify": "Nepodařilo se ověřit e-mail:",
@@ -1265,7 +1273,7 @@
"passwordRequirementUppercaseText": "Velké písmeno (A-Z)",
"passwordRequirementLowercaseText": "Malé písmeno (a-z)",
"passwordRequirementNumberText": "Číslo (0-9)",
"passwordRequirementSpecialText": "Special character (!@#$%...)",
"passwordRequirementSpecialText": "Speciální znak (!@#$%...)",
"passwordsDoNotMatch": "Hesla se neshodují",
"otpEmailRequirementsLength": "OTP musí mít alespoň 1 znak",
"otpEmailSent": "OTP odesláno",
@@ -1302,12 +1310,12 @@
"passwordReset": "Obnovit heslo",
"passwordResetDescription": "Postupujte podle kroků pro obnovení hesla",
"passwordResetSent": "Na tuto e-mailovou adresu zašleme kód pro obnovení hesla.",
"passwordResetCode": "Reset Code",
"passwordResetCode": "Kód obnovení",
"passwordResetCodeDescription": "Zkontrolujte svůj e-mail pro kód pro obnovení.",
"generatePasswordResetCode": "Vygenerovat kód pro obnovení hesla",
"passwordResetCodeGenerated": "Kód pro obnovení hesla byl vytvořen",
"passwordResetCodeGeneratedDescription": "Sdílejte tento kód s uživatelem. Mohou jej použít k obnovení hesla.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "URL obnovení",
"passwordNew": "Nové heslo",
"passwordNewConfirm": "Potvrdit nové heslo",
"changePassword": "Změnit heslo",
@@ -1364,7 +1372,7 @@
"inviteErrorExpired": "Pozvánka možná vypršela",
"inviteErrorRevoked": "Pozvánka mohla být zrušena",
"inviteErrorTypo": "Na pozvánce může být typol",
"pangolinSetup": "Setup - Pangolin",
"pangolinSetup": "Nastavení - Pangolin",
"orgNameRequired": "Je vyžadován název organizace",
"orgIdRequired": "Je vyžadováno ID organizace",
"orgIdMaxLength": "ID organizace musí mít nejvýše 32 znaků",
@@ -1392,7 +1400,7 @@
"tagWarnDuplicate": "Duplicitní značka {tagText} nebyla přidána",
"supportKeyInvalid": "Neplatný klíč",
"supportKeyInvalidDescription": "Váš supporter klíč je neplatný.",
"supportKeyValid": "Valid Key",
"supportKeyValid": "Platný klíč",
"supportKeyValidDescription": "Váš klíč podporovatele byl ověřen. Děkujeme za vaši podporu!",
"supportKeyErrorValidationDescription": "Nepodařilo se ověřit klíč podporovatele.",
"supportKey": "Podpořte vývoj a přijměte Pangolin!",
@@ -1414,12 +1422,30 @@
"supportKeyHideSevenDays": "Skrýt na 7 dní",
"supportKeyEnter": "Zadejte klíč podpory",
"supportKeyEnterDescription": "Seznamte se s vlastním mazlíčkem Pangolin!",
"githubUsername": "GitHub Username",
"githubUsername": "Uživatelské jméno GitHub",
"supportKeyInput": "Klíč pro podporu",
"supportKeyBuy": "Koupit klíč pro podporu",
"logoutError": "Chyba při odhlášení",
"signingAs": "Přihlášen jako",
"serverAdmin": "Správce serveru",
"promoteServerAdmin": "Podpora na úroveň správce serveru",
"promoteServerAdminTitle": "Podpora na úroveň Správce serveru",
"promoteServerAdminQuestion": "Jste si jisti, že chcete podpořit {selectedUser} na úroveň správce serveru?",
"promoteServerAdminMessage": "Správci serveru mají nejvyšší oprávnění a mohou spravovat server.",
"promoteServerAdminWarning": "To lze kdykoli vrátit zpět snížením úrovně uživatele.",
"promoteServerAdminConfirm": "Podpora na úroveň Správce serveru",
"promoteServerAdminSuccess": "Uživatel podpořen",
"promoteServerAdminSuccessDescription": "{selectedUser} je nyní správce serveru.",
"promoteServerAdminError": "Nepodařilo se podpořit uživatele",
"demoteServerAdmin": "Snížit úroveň správce serveru",
"demoteServerAdminTitle": "Snížit úroveň Správce serveru",
"demoteServerAdminQuestion": "Jste si jisti, že chcete snížit {selectedUser} z úrovně správce serveru?",
"demoteServerAdminMessage": "{selectedUser} ztratí všechna oprávnění správce serveru.",
"demoteServerAdminWarning": "To lze kdykoli vrátit zpět podporou uživatele.",
"demoteServerAdminConfirm": "Snížit úroveň ze správce serveru",
"demoteServerAdminSuccess": "Uživatel snížen",
"demoteServerAdminSuccessDescription": "{selectedUser} již není správce serveru.",
"demoteServerAdminError": "Nepodařilo se snížit úroveň uživatele",
"managedSelfhosted": "Spravované vlastní hostování",
"otpEnable": "Povolit dvoufaktorové",
"otpDisable": "Zakázat dvoufaktorové",
@@ -1505,11 +1531,11 @@
"actionSetResourcePolicyHeaderAuth": "Nastavit autentizaci hlavičky zásady zdroje",
"actionSetResourcePolicyWhitelist": "Nastavit seznam povolených e-mailů v zásadě zdroje",
"actionSetResourcePolicyRules": "Nastavit pravidla zásady zdroje",
"actionCreateTarget": "Create Target",
"actionCreateTarget": "Vytvořit cíl",
"actionDeleteTarget": "Odstranit cíl",
"actionGetTarget": "Získat cíl",
"actionListTargets": "Seznam cílů",
"actionUpdateTarget": "Update Target",
"actionUpdateTarget": "Aktualizovat cíl",
"actionCreateRole": "Vytvořit roli",
"actionDeleteRole": "Odstranit roli",
"actionGetRole": "Získat roli",
@@ -1573,6 +1599,8 @@
"search": "Vyhledávání…",
"searchPlaceholder": "Hledat...",
"emptySearchOptions": "Nebyly nalezeny žádné možnosti",
"ipFilterSearchPlaceholder": "Zadejte IP adresu…",
"ipFilterEmptyMessage": "Zadejte IP adresu pro filtrování",
"create": "Vytvořit",
"orgs": "Organizace",
"loginError": "Došlo k neočekávané chybě. Zkuste to prosím znovu.",
@@ -1584,7 +1612,7 @@
"idpContinue": "Nebo pokračovat s",
"idpLastUsed": "Naposled použito",
"otpAuthBack": "Zpět na heslo",
"navbar": "Navigation Menu",
"navbar": "Navigační menu",
"navbarDescription": "Hlavní navigační menu aplikace",
"navbarDocsLink": "Dokumentace",
"commandPaletteTitle": "Paleta příkazů",
@@ -1840,7 +1868,7 @@
"aiProviderTypeSearch": "Hledat poskytovatele...",
"aiProviderTypeNotFound": "Typ poskytovatele nenalezen",
"aiProviderTypeOpenai": "OpenAI",
"aiProviderTypeAnthropic": "Anthropic",
"aiProviderTypeAnthropic": "Antropic",
"aiProviderTypeGoogleGemini": "Google Gemini",
"aiProviderTypeVertexAi": "Vertex AI",
"aiProviderTypeBedrock": "Amazon Bedrock",
@@ -1868,11 +1896,11 @@
"aiProviderAuthType": "Typ ověření",
"aiProviderAuthTypeSearch": "Vyhledat typy ověření...",
"aiProviderAuthTypeNotFound": "Nebyl nalezen žádný typ ověření",
"aiProviderAuthTypeBearer": "Bearer",
"aiProviderAuthTypeBearer": "Přepravitel",
"aiProviderAuthTypeBearerDescription": "Oprávnění: Bearer klíč. Používá OpenAI a většina poskytovatelů",
"aiProviderAuthTypeXApiKey": "x-api-key",
"aiProviderAuthTypeXApiKey": "x-api-klíč",
"aiProviderAuthTypeXApiKeyDescription": "x-api-key záhlaví. Používá Anthropic",
"aiProviderAuthTypeXGoogApiKey": "x-goog-api-key",
"aiProviderAuthTypeXGoogApiKey": "x-goog-api-klíč",
"aiProviderAuthTypeXGoogApiKeyDescription": "x-goog-api-key záhlaví. Používá Google Gemini",
"aiProviderAuthTypeHec": "Splunk HEC",
"aiProviderAuthTypeHecDescription": "Oprávnění: Splunk klíč. Používá Splunk HTTP Event Collector",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Rozpočet",
"resourceBudgetSettingsDescription": "Zjistěte, jak tato AI brána omezuje užívání na základě výdajů nebo limitů tokenů",
"sidebarApiKeys": "API klíče",
"sidebarOrgs": "Organizace",
"sidebarProvisioning": "Zajištění",
"sidebarSettings": "Nastavení",
"sidebarAllUsers": "Všichni uživatelé",
@@ -2099,7 +2128,7 @@
"sidebarMachineClients": "Stroje a přístroje",
"sidebarDomains": "Domény",
"sidebarGeneral": "Spravovat",
"sidebarLogAndAnalytics": "Log & Analytics",
"sidebarLogAndAnalytics": "Protokoly a analytika",
"sidebarBluePrints": "Plány",
"sidebarAlerting": "Upozornění",
"sidebarHealthChecks": "Kontroly stavu",
@@ -2351,7 +2380,7 @@
"containerLabels": "Popisky",
"containerLabelsCount": "{count, plural, one {# štítek} other {# štítků}}",
"containerLabelsTitle": "Popisky kontejneru",
"containerLabelEmpty": "<empty>",
"containerLabelEmpty": "<prázdné>",
"containerPorts": "Přístavy",
"containerPortsMore": "+{count} další",
"containerActions": "Akce",
@@ -2596,6 +2625,7 @@
"createDomainType": "Typ:",
"createDomainName": "Jméno:",
"createDomainValue": "Hodnota:",
"multiSelectFilterCount": "{count} vybráno",
"createDomainCnameRecords": "Záznamy CNAME",
"createDomainARecords": "Záznamy",
"createDomainRecordNumber": "Nahrát {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Stáhněte si Olm běžící ve vašem systému",
"clientOlmCredentials": "Pověření",
"clientOlmCredentialsDescription": "Tímto způsobem bude klient autentizovat se serverem",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Koncový bod",
"olmId": "ID",
"olmSecretKey": "Tajný klíč",
"clientCredentialsSave": "Uložit pověření",
@@ -2693,7 +2723,7 @@
"resourceEnableProxy": "Povolit veřejné proxy",
"resourceEnableProxyDescription": "Povolit veřejné proxying pro tento zdroj. To umožňuje přístup ke zdrojům mimo síť prostřednictvím cloudu na otevřeném portu. Vyžaduje nastavení Traefik.",
"externalProxyEnabled": "Externí proxy povolen",
"addNewTarget": "Add New Target",
"addNewTarget": "Přidat nový cíl",
"targetsList": "Seznam cílů",
"advancedMode": "Pokročilý režim",
"advancedSettings": "Pokročilá nastavení",
@@ -2710,6 +2740,7 @@
"healthScheme": "Způsob",
"healthSelectScheme": "Vybrat metodu",
"healthCheckPortInvalid": "Port musí být mezi 1 a 65535",
"healthCheckHostnameInvalid": "Název hostitele nesmí obsahovat mezery",
"healthCheckPath": "Cesta",
"healthHostname": "IP / Hostitel",
"healthPort": "Přístav",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Nebyly nalezeny žádné zdroje proxy",
"resourcesTableNoInternalResourcesFound": "Nebyly nalezeny žádné privátní zdroje.",
"resourcesTableDestination": "Místo určení",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Přezdívka",
"resourcesTableAliasAddress": "Adresa aliasu",
"resourcesTableAliasAddressInfo": "Tato adresa je součástí subsítě veřejných služeb organizace. Používá se k řešení záznamů aliasů pomocí interního rozlišení DNS.",
"resourcesTableClients": "Klienti",
@@ -2857,7 +2888,7 @@
"editInternalResourceDialogName": "Jméno",
"editInternalResourceDialogProtocol": "Protokol",
"editInternalResourceDialogSitePort": "Port webu",
"editInternalResourceDialogTargetConfiguration": "Target Configuration",
"editInternalResourceDialogTargetConfiguration": "Konfigurace cíle",
"editInternalResourceDialogCancel": "Zrušit",
"editInternalResourceDialogSaveResource": "Uložit dokument",
"editInternalResourceDialogSuccess": "Úspěšně",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "IP adresa nebo název hostitele zdroje v síti webu.",
"editInternalResourceDialogDestinationIPDescription": "IP nebo název hostitele zdroje v síti webu.",
"editInternalResourceDialogDestinationCidrDescription": "Rozsah zdrojů CIDR v síti webu.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Přezdívka",
"editInternalResourceDialogAliasDescription": "Volitelný interní DNS alias pro tento dokument.",
"createInternalResourceDialogNoSitesAvailable": "Nejsou k dispozici žádné weby",
"createInternalResourceDialogNoSitesAvailableDescription": "K vytvoření privátních zdrojů potřebujete mít alespoň jednu lokalitu Newt s nastavenou podsítí.",
@@ -2913,7 +2944,7 @@
"createInternalResourceDialogUdp": "UDP",
"createInternalResourceDialogSitePort": "Port webu",
"createInternalResourceDialogSitePortDescription": "Použijte tento port pro přístup ke zdroji na webu při připojení s klientem.",
"createInternalResourceDialogTargetConfiguration": "Target Configuration",
"createInternalResourceDialogTargetConfiguration": "Konfigurace cíle",
"createInternalResourceDialogDestinationIPDescription": "IP nebo název hostitele zdroje v síti webu.",
"createInternalResourceDialogDestinationPortDescription": "Přístav na cílové IP adrese, kde je zdroj dostupný.",
"createInternalResourceDialogCancel": "Zrušit",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Místo určení",
"createInternalResourceDialogDestinationHostDescription": "IP adresa nebo název hostitele zdroje v síti webu.",
"createInternalResourceDialogDestinationCidrDescription": "Rozsah zdrojů CIDR v síti webu.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Přezdívka",
"createInternalResourceDialogAliasDescription": "Volitelný interní DNS alias pro tento dokument.",
"internalResourceAliasLocalWarning": "Aliasy končící na .local mohou způsobit problémy s vyřešením díky mDNS v některých sítích.",
"internalResourceDownstreamSchemeRequired": "HTTP metoda je vyžadována pro HTTP zdroje",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Přidejte rozsah CIDR (např. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Nepodařilo se načíst podsítě",
"remoteExitNodeNetworkingLabelsTitle": "Názvy preferencí",
"remoteExitNodeNetworkingLabelsDescription": "Weby s těmito názvy budou nucenyipojit se tímto vzdáleným výstupním uzlem.",
"remoteExitNodeNetworkingLabelsDescription": "Stránky s těmito štítky preferují spojeníes tento vzdálený výstupní uzel.",
"remoteExitNodeNetworkingLabelsButtonText": "Vyberte názvy...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Hledat názvy...",
"remoteExitNodeNetworkingLabelsLoadError": "Nepodařilo se načíst názvy",
@@ -3081,11 +3112,11 @@
"regionNorthernEurope": "Severní Evropa",
"regionSouthernEurope": "Jižní Evropa",
"regionWesternEurope": "Západní Evropa",
"regionOceania": "Oceania",
"regionOceania": "Oceánie",
"regionAustraliaAndNewZealand": "Austrálie a Nový Zéland",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"regionMelanesia": "Melanésie",
"regionMicronesia": "Mikronésie",
"regionPolynesia": "Polynésie",
"managedSelfHosted": {
"title": "Spravované vlastní hostování",
"description": "Spolehlivější a nízko udržovaný Pangolinův server s dalšími zvony a bičkami",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Odstranit",
"idpGoogleConfiguration": "Konfigurace Google",
"idpGoogleConfigurationDescription": "Konfigurace přihlašovacích údajů Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Vaše ID klienta Google OAuth2",
"idpGoogleClientSecretDescription": "Tajný klíč klienta Google OAuth2",
"idpAzureConfiguration": "Nastavení Azure Entra ID",
"idpAzureConfigurationDescription": "Konfigurace Azure Entra ID OAuth2",
"idpTenantId": "ID tenanta",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "id tenanta",
"idpAzureTenantIdDescription": "ID nájemce Azura (nalezeno v přehledu Azure Active Directory - Azure Active Directory - přehled )",
"idpAzureClientIdDescription": "ID klienta pro registraci aplikace Azure",
"idpAzureClientSecretDescription": "Tajný klíč registrace aplikace Azure",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "ID klienta pro registraci aplikace Azure",
"idpAzureClientSecretDescription2": "Tajný klíč registrace aplikace Azure",
"idpGoogleDescription": "Poskytovatel Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Poskytovatel Microsoft Azure OAuth2/OIDC",
"subnet": "Podsíť",
"utilitySubnet": "Nástrojová podsíť",
"subnetDescription": "Podsíť pro konfiguraci sítě této organizace.",
@@ -3470,6 +3501,7 @@
"priority": "Priorita",
"priorityDescription": "Vyšší priorita je vyhodnocena jako první. Priorita = 100 znamená automatické řazení (rozhodnutí systému). Pro vynucení manuální priority použijte jiné číslo.",
"instanceName": "Název instance",
"clearInstanceName": "Obnovit spojení se serverem",
"pathMatchModalTitle": "Nastavit porovnávání cest",
"pathMatchModalDescription": "Nastavte jak se bude ověřovat, ze cesty příchozích požadavků se shodují.",
"pathMatchType": "Typ shody",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Povoleno pomocí pravidla",
"allowedNoAuth": "Povoleno bez ověření",
"validAccessToken": "Platný přístupový token",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Platná záhlaví ověření",
"validPincode": "Platný pin",
"validPassword": "Platné heslo",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Platný email",
"validSSO": "Platné SSO",
"validVirtualAPIKey": "Platný virtuální API klíč",
"view": "Zobrazit",
"configManaged": "Správa konfigurace",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Zrušeno pravidlem",
"noSessions": "Žádné relace",
"temporaryRequestToken": "Dočasný požadavek token",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Žádné platné ověření",
"ip": "IP adresa",
"reason": "Důvod",
"requestLogs": "Záznamy HTTP požadavků",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Obnovení přihlašovacích údajů zneplatní předchozí a způsobí odpojení. Ujistěte se, že aktualizujete všechny konfigurace, které tyto přihlašovací údaje používají.",
"confirm": "Potvrdit",
"regenerateCredentialsConfirmation": "Jste si jisti, že chcete obnovit přihlašovací údaje?",
"endpoint": "Endpoint",
"endpoint": "Koncový bod",
"Id": "Id",
"SecretKey": "Tajný klíč",
"niceId": "Pěkné ID",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Došlo k chybě při aktualizaci identifikátoru Nice.",
"niceIdCannotBeEmpty": "Nice ID nemůže být prázdné",
"enterIdentifier": "Zadejte identifikátor",
"identifier": "Identifier",
"identifier": "Identifikátor",
"deviceLoginUseDifferentAccount": "Nejste vy? Použijte jiný účet.",
"deviceLoginDeviceRequestingAccessToAccount": "Zařízení žádá o přístup k tomuto účtu.",
"loginSelectAuthenticationMethod": "Chcete-li pokračovat, vyberte metodu ověřování.",
"noData": "Žádná data",
"machineClients": "Strojoví klienti",
"install": "Instalovat",
"downloadInstaller": "Stáhnout instalátor",
"run": "Spustit",
"envFile": "Konfigurační soubor prostředí",
"serviceFile": "Služební soubor",
@@ -3966,6 +3999,7 @@
"disconnected": "Odpojeno",
"approvalsEmptyStateTitle": "Schvalování zařízení není povoleno",
"approvalsEmptyStateDescription": "Povolte oprávnění oprávnění pro role správce před připojením nových zařízení.",
"approvalsEmptyStateHowToTitle": "Jak povolit",
"approvalsEmptyStateStep1Title": "Přejít na role",
"approvalsEmptyStateStep1Description": "Přejděte do nastavení rolí vaší organizace pro konfiguraci schválení zařízení.",
"approvalsEmptyStateStep2Title": "Povolit schválení zařízení",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Zobrazit jako administrátor",
"resourceLauncherResourceDetailsDescription": "Informace o připojení a stavu pro tento zdroj.",
"resourceLauncherResourceDetails": "Podrobnosti o zdroji",
"resourceLauncherSitesDescription": "Zdroj je přístupný prostřednictvím následujících stránek.",
"resourceLauncherViewSiteAsAdmin": "Zobrazit web jako administrátor",
"resourceLauncherFilterBySite": "Filtrovat podle stránky",
"resourceLauncherSshCommand": "SSH příkaz",
"resourceLauncherSshCommandDescription": "Použijte Pangolin CLI pro otevření SSH relace k tomuto zdroji.",
"resourceLauncherAuthMethodsDescription": "Povolené metody autentizace pro tento zdroj.",
"resourceLauncherPrivateClientRequired": "Připojte se s klientem na vašem zařízení, abyste měli k tomuto zdroji soukromý přístup.",
"resourceLauncherPrivateClientRequiredTitle": "K připojení klienta vyžadováno",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Režim Unicode klávesnice",
"sessionToolbarShow": "Zobrazit panel nástrojů",
"sessionToolbarHide": "Skrýt panel nástrojů",
"actionUpdateSiteApprovals": "Aktualizovat schválení webu"
"actionUpdateSiteApprovals": "Aktualizovat schválení webu",
"check": "Zkontrolovat"
}
+61 -21
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Mål på dette site",
"siteSetting": "{siteName} Indstillinger",
"siteNewtTunnel": "Newt-site (anbefalet)",
"pangolinSite": "Pangolin Site",
"siteNewtTunnelDescription": "Lekkeste måte at oprette et indgangspunkt til ethvert netværk. Ingen ekstra opsætning på.",
"siteWg": "Grunnleggende WireGuard",
"siteWgDescription": "Brug en hvilken som helst WireGuard-klient til at etablere en tunnel. Manuel NAT-opsætning er påkrævet.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Slet API-nøgle",
"apiKeysManage": "Administrer API-nøgler",
"apiKeysDescription": "API-nøgler bruges for at autentificere med integrasjons-API",
"orgsManage": "Administrer Organisationer",
"orgsDescription": "Vis og administrer alle organisationer i denne instans",
"provisioningKeysTitle": "Provisioneringsnøgle",
"provisioningKeysManage": "Administrer provisioneringsnøgler",
"provisioningKeysDescription": "Provisioneringsnøgler bruges til at godkende automatiseret site-provisionering for din organisation.",
@@ -716,6 +719,7 @@
"nameOptional": "Navn (valgfrit)",
"accessControls": "Adgangskontroller",
"userDescription2": "Administrer indstillingerne for denne bruger",
"userGeneralSettingsDescription": "Administrer denne brugers roller og indstillinger i organisationen",
"accessRoleErrorAdd": "Kunne ikke tilføje bruger i rolle",
"accessRoleErrorAddDescription": "Det opstod en fejl under tildeling af brugeren til rollen.",
"userSaved": "Bruger gemt",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Når regler er deaktiveret, går all trafik gennem til autentificering.",
"policyAccessRulesFallthroughOn": "Når ingen regler matcher, fortsetter trafikken til autentificering.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/administrator/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Gem Regler",
"resourceErrorCreate": "Fejl under oprettelse af ressource",
@@ -948,7 +952,7 @@
"unknownCommand": "Ukjent kommando",
"newtErrorFetchReleases": "Mislykkedes at hente utgivelsesinfo: {err}",
"newtErrorFetchLatest": "Fejl ved hentning af seneste utgivelse: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Slutpunkt",
"newtId": "ID",
"newtSecretKey": "Sikkerhedsnøgle",
"newtVersion": "Version",
@@ -1176,11 +1180,15 @@
"idpJmespathAboutDescriptionLink": "Læs mere om JMESPath",
"idpJmespathLabel": "Identifikatorsti",
"idpJmespathLabelDescription": "Stien til brugeridentifikatoren i ID-tokenet",
"idpIdentifierChangeTitle": "Advarsel om ændring af identifikatorsti",
"idpIdentifierChangeDescription": "Du er ved at ændre identifikatorstien. Dette vil påvirke, hvordan eksisterende brugere bliver kortlagt. Brugere, der tidligere har logget ind gennem denne identitetsudbyder, genkendes muligvis ikke længere som de samme brugere.",
"idpIdentifierChangeConfirmMessage": "Jeg bekræfter",
"idpIdentifierChangeWarningText": "Dette vil påvirke, hvordan eksisterende brugere bliver kortlagt",
"idpJmespathEmailPathOptional": "E-mailsti (Valgfrit)",
"idpJmespathEmailPathOptionalDescription": "Stien til brugerens e-mailadresse i ID-tokenet",
"idpJmespathNamePathOptional": "Navn Sti (Valgfrit)",
"idpJmespathNamePathOptionalDescription": "Stien til brugerens navn i ID-tokenet",
"idpOidcConfigureScopes": "Scopes",
"idpOidcConfigureScopes": "Scope",
"idpOidcConfigureScopesDescription": "Mellemrumsepareret liste over OAuth2-scopes at be om",
"idpSubmit": "Opret identitetsudbyder",
"orgPolicies": "Organisationspolitikker",
@@ -1258,7 +1266,7 @@
"passwordRequirementsMet": "✓ Adgangskoden opfylder alle krav",
"passwordStrength": "Adgangskodestyrke",
"passwordStrengthWeak": "Svag",
"passwordStrengthMedium": "Medium",
"passwordStrengthMedium": "Middel",
"passwordStrengthStrong": "Stærk",
"passwordRequirements": "Krav:",
"passwordRequirementLengthText": "8+ tegn",
@@ -1420,6 +1428,24 @@
"logoutError": "Fejl ved logout",
"signingAs": "Logget ind som",
"serverAdmin": "Serveradministrator",
"promoteServerAdmin": "Fremme til serveradministrator",
"promoteServerAdminTitle": "Fremme til Serveradministrator",
"promoteServerAdminQuestion": "Er du sikker på at du vil forfremme {selectedUser} til serveradministrator?",
"promoteServerAdminMessage": "Serveradministratorer har de højeste privilegier og kan administrere serveren.",
"promoteServerAdminWarning": "Dette kan fortrydes når som helst ved at degradere brugeren.",
"promoteServerAdminConfirm": "Fremme til Serveradministrator",
"promoteServerAdminSuccess": "Bruger Forfremmet",
"promoteServerAdminSuccessDescription": "{selectedUser} er nu serveradministrator.",
"promoteServerAdminError": "Kunne ikke forfremme bruger",
"demoteServerAdmin": "Degradere fra serveradministrator",
"demoteServerAdminTitle": "Degradere fra Serveradministrator",
"demoteServerAdminQuestion": "Er du sikker på at du vil degradere {selectedUser} fra serveradministrator?",
"demoteServerAdminMessage": "{selectedUser} vil miste alle serveradministrator privilegier.",
"demoteServerAdminWarning": "Dette kan fortrydes når som helst ved at forfremme brugeren.",
"demoteServerAdminConfirm": "Degradere fra serveradministrator",
"demoteServerAdminSuccess": "Bruger degraderet",
"demoteServerAdminSuccessDescription": "{selectedUser} er ikke længere serveradministrator.",
"demoteServerAdminError": "Kunne ikke degradere bruger",
"managedSelfhosted": "Administreret selvhostet",
"otpEnable": "Aktivér tofaktor",
"otpDisable": "Deaktivér tofaktor",
@@ -1573,6 +1599,8 @@
"search": "Søg…",
"searchPlaceholder": "Søg...",
"emptySearchOptions": "Ingen valg fundet",
"ipFilterSearchPlaceholder": "Indtast en IP-adresse…",
"ipFilterEmptyMessage": "Indtast en IP-adresse for at filtrere efter",
"create": "Opret",
"orgs": "Organisationer",
"loginError": "Der opstod en uventet fejl. Prøv venligst igen.",
@@ -1971,7 +1999,7 @@
"aiProviderModelsCatalogEmpty": "Ingen matchende katalogmodeller.",
"aiProviderModelsCatalogHeading": "Kendte Modeller",
"aiProviderModelsAllLabel": "Alle modeller",
"aiProviderModelsAllPatternHint": "Wildcard: *",
"aiProviderModelsAllPatternHint": "Jokertegn: *",
"aiProviderModelsAddAllAllow": "Tillad alle modeller",
"aiProviderModelsAddAllBlock": "Blokér alle modeller",
"aiProviderModelsAddAllDescription": "Bruger * wildcard så hver modelnøgle matcher.",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Budget",
"resourceBudgetSettingsDescription": "Konfigurer, hvordan denne AI-gateway begrænser brug baseret på udgifts- eller token-grænser",
"sidebarApiKeys": "API-nøgler",
"sidebarOrgs": "Organisationer",
"sidebarProvisioning": "Provisionering",
"sidebarSettings": "Indstillinger",
"sidebarAllUsers": "Alle brugere",
@@ -2143,7 +2172,7 @@
"commandBilling": "Fakturering",
"commandEnterpriseLicenses": "Licenser",
"commandSettings": "Indstillinger",
"commandLauncher": "Launcher",
"commandLauncher": "Startprogram",
"commandResourceLauncher": "Ressource-launcher",
"commandSearchResults": "Søgeresultater",
"alertingTitle": "Varsling",
@@ -2596,6 +2625,7 @@
"createDomainType": "Type:",
"createDomainName": "Navn:",
"createDomainValue": "Værdi:",
"multiSelectFilterCount": "{count} valgt",
"createDomainCnameRecords": "CNAME-poster",
"createDomainARecords": "A-poster",
"createDomainRecordNumber": "Post {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Få Olm til at køre på dit system",
"clientOlmCredentials": "Legitimationsoplysninger",
"clientOlmCredentialsDescription": "Dette er hvordan klienten vil godkende med serveren",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Slutpunkt",
"olmId": "ID",
"olmSecretKey": "Sikkerhedsnøgle",
"clientCredentialsSave": "Gem brugeroplysninger",
@@ -2710,6 +2740,7 @@
"healthScheme": "Metode",
"healthSelectScheme": "Vælg metode",
"healthCheckPortInvalid": "Porten skal være mellem 1 og 65535",
"healthCheckHostnameInvalid": "Værtsnavnet må ikke indeholde mellemrum",
"healthCheckPath": "Sti",
"healthHostname": "IP / Vært",
"healthPort": "Port",
@@ -2722,7 +2753,7 @@
"requireDeviceApproval": "Kræv enhedsgodkendelse",
"requireDeviceApprovalDescription": "Brugere med denne rolle skal have nye enheder godkendt af en administrator, før de kan oprette forbindelse og få adgang til ressourcer.",
"sshSettings": "SSH",
"inferenceSettings": "AI Gateway",
"inferenceSettings": "AI-gateway",
"sshAccess": "SSH-adgang",
"rdpSettings": "RDP",
"vncSettings": "VNC",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Ingen proxy-ressourcer fundet.",
"resourcesTableNoInternalResourcesFound": "Ingen private ressourcer fundet.",
"resourcesTableDestination": "Destination",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Navn",
"resourcesTableAliasAddress": "Alias adresse",
"resourcesTableAliasAddressInfo": "Denne adressen er en del af organisationens subnet. Den bruges til at løse aliasposter ved hjælp af intern DNS-opløsning.",
"resourcesTableClients": "Klienter",
@@ -2878,7 +2909,7 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeInference": "AI Gateway",
"editInternalResourceDialogModeInference": "AI-gateway",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Skema",
"editInternalResourceDialogEnableSsl": "Aktivér TLS",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller værtsnavnet til ressourcen på sitets netværk.",
"editInternalResourceDialogDestinationIPDescription": "IP eller hostnavn til ressourcen på sitets netværk.",
"editInternalResourceDialogDestinationCidrDescription": "CIDR-området til ressourcen på sitets netværk.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Navn",
"editInternalResourceDialogAliasDescription": "Et valgfrit internt DNS-alias for denne ressource.",
"createInternalResourceDialogNoSitesAvailable": "Ingen tilgængelige steder",
"createInternalResourceDialogNoSitesAvailableDescription": "Du skal have mindst ét Newt-site med et konfigureret subnet for at oprette private ressourcer.",
@@ -2899,7 +2930,7 @@
"privateResourceAllowIcmpPing": "Tillad ICMP-ping",
"privateResourceNetworkAccess": "Netværksadgang",
"privateResourceNetworkAccessDescription": "Styr TCP/UDP-portadgang og om ICMP-ping er tilladt for denne ressource.",
"hostSettings": "Host",
"hostSettings": "Vært",
"cidrSettings": "CIDR",
"createInternalResourceDialogResourceProperties": "Ressourceegenskaber",
"createInternalResourceDialogName": "Navn",
@@ -2938,7 +2969,7 @@
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"createInternalResourceDialogModeInference": "AI Gateway",
"createInternalResourceDialogModeInference": "AI-gateway",
"scheme": "Skema",
"createInternalResourceDialogScheme": "Skema",
"createInternalResourceDialogEnableSsl": "Aktivér TLS",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Destination",
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller værtsnavnet til ressourcen på sitets netværk.",
"createInternalResourceDialogDestinationCidrDescription": "CIDR-området til ressourcen på sitets netværk.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Navn",
"createInternalResourceDialogAliasDescription": "Et valgfrit internt DNS-alias for denne ressource.",
"internalResourceAliasLocalWarning": "Aliasser, der ender på .local, kan forårsage opløsningsproblemer på grund af mDNS på nogle netværk.",
"internalResourceDownstreamSchemeRequired": "Skema er påkrævet for HTTP-ressourcer",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Tilføj et CIDR-område (f.eks. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Kunne ikke indlæse subnets",
"remoteExitNodeNetworkingLabelsTitle": "Præference Etiketter",
"remoteExitNodeNetworkingLabelsDescription": "Sites med disse etiketter vil blive tvunget til at oprette forbindelse gennem denne fjerne exit-node.",
"remoteExitNodeNetworkingLabelsDescription": "Sider med disse etiketter vil foretrække at forbinde gennem denne fjernudgarnknude.",
"remoteExitNodeNetworkingLabelsButtonText": "Vælg etiketter...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Søg efter etiketter...",
"remoteExitNodeNetworkingLabelsLoadError": "Kunne ikke indlæse etiketter",
@@ -3189,7 +3220,7 @@
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
"authPageBrandingDeleteConfirm": "Bekræft sletning af merkevarebygging",
"brandingLogoURL": "Logo URL",
"brandingLogoURL": "Logo-URL",
"brandingLogoURLOrPath": "Logoen URL eller sti",
"brandingLogoPathDescription": "Indtast en URL eller en lokal sti.",
"brandingLogoURLDescription": "Indtast en offentligt tilgængelig webadresse til din logobillede.",
@@ -3470,6 +3501,7 @@
"priority": "Prioritet",
"priorityDescription": "Ruter med højere prioritet evalueres først. Prioritet = 100 betyder automatisk rækkefølge (systembeslutninger). Brug et andet tal for at gennemtvinge manuel prioritet.",
"instanceName": "Forekomst navn",
"clearInstanceName": "Nulstil serverforbindelse",
"pathMatchModalTitle": "Konfigurere matching af sti",
"pathMatchModalDescription": "Opsæt hvordan indgående forespørgsler skal matches baseret på deres sti.",
"pathMatchType": "Matchtype",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Regenerering af legitimationsoplysninger vil ugyldiggøre de forrige og forårsage en frakobling. Sørg for at opdatere alle konfigurationer, der bruger disse legitimationsoplysninger.",
"confirm": "Bekræft",
"regenerateCredentialsConfirmation": "Er du sikker på at du vil regenerere legetimationerne?",
"endpoint": "Endpoint",
"endpoint": "Slutpunkt",
"Id": "Id",
"SecretKey": "Hemmelig nøgle",
"niceId": "God ID",
@@ -3759,6 +3791,7 @@
"noData": "Ingen data",
"machineClients": "Maskinklienter",
"install": "Installer",
"downloadInstaller": "Download Installer",
"run": "Kjør",
"envFile": "Miljøfil",
"serviceFile": "Tjenestefil",
@@ -3966,6 +3999,7 @@
"disconnected": "Offline",
"approvalsEmptyStateTitle": "Enhedsgodkendelser er ikke aktiveret",
"approvalsEmptyStateDescription": "Aktivere godkendelser af enheder for at roller skal godkendes af admin før brugere kan opret forbindelse til nye enheder.",
"approvalsEmptyStateHowToTitle": "Sådan aktiveres",
"approvalsEmptyStateStep1Title": "Gå til roller",
"approvalsEmptyStateStep1Description": "Naviger til organisationens roller indstillinger for at konfigurere enhedsgodkendelser.",
"approvalsEmptyStateStep2Title": "Aktivér enhedsgodkendelser",
@@ -4206,7 +4240,7 @@
"memberPortalResourceDisabled": "Ressource deaktiveret",
"memberPortalShowingResources": "Viser {start}-{end} af {total} ressourcer",
"resourceLauncherTitle": "Ressource Starter",
"resourceSidebarLauncherTitle": "Launcher",
"resourceSidebarLauncherTitle": "Startprogram",
"resourceLauncherDescription": "Se alle tilgængelige ressourcer og start dem fra ét centralt knudepunkt",
"resourceLauncherSearchPlaceholder": "Søg i dine ressourcer...",
"resourceLauncherDefaultView": "Standard",
@@ -4222,7 +4256,7 @@
"resourceLauncherSaveForEveryone": "Gem for Alle",
"resourceLauncherSaveForEveryoneDescription": "Del denne visning med alle organisationsmedlemmer. Når den er ikke markeret, er visningen kun synlig for dig.",
"resourceLauncherMakePersonal": "Gør Personlig",
"resourceLauncherFilter": "Filter",
"resourceLauncherFilter": "Filtre",
"resourceLauncherFilterWithCount": "Filter, {count} anvendt",
"resourceLauncherSort": "Sortér",
"resourceLauncherSortAscending": "Sortér stigende",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Vis som Admin",
"resourceLauncherResourceDetailsDescription": "Forbindelsesoplysninger og status for denne ressource.",
"resourceLauncherResourceDetails": "Ressourcedetaljer",
"resourceLauncherSitesDescription": "Ressourcen er tilgængelig via følgende sites.",
"resourceLauncherViewSiteAsAdmin": "Vis side som Admin",
"resourceLauncherFilterBySite": "Filtrer efter site",
"resourceLauncherSshCommand": "SSH-kommando",
"resourceLauncherSshCommandDescription": "Brug Pangolin CLI til at åbne en SSH-session til denne ressource.",
"resourceLauncherAuthMethodsDescription": "Autentificeringsmetoder aktiveret for denne ressource.",
"resourceLauncherPrivateClientRequired": "Forbind med en klient på din enhed for at få privat adgang til denne ressource.",
"resourceLauncherPrivateClientRequiredTitle": "Klientforbindelse påkrævet",
@@ -4254,7 +4293,7 @@
"resourceLauncherTcp": "TCP",
"resourceLauncherUdp": "UDP",
"resourceLauncherUnlabeled": "Uden Etiket",
"resourceLauncherAiGateway": "AI Gateway",
"resourceLauncherAiGateway": "AI-gateway",
"resourceLauncherNoSite": "Ingen Site",
"resourceLauncherAvailableModels": "Tilgængelige Modeller",
"resourceLauncherAvailableModelsDescription": "Modeller, du kan bruge med denne AI-gateway.",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode tastaturtilstand",
"sessionToolbarShow": "Vis værktøjslinje",
"sessionToolbarHide": "Skjul værktøjslinje",
"actionUpdateSiteApprovals": "Opdater godkendelser på site"
"actionUpdateSiteApprovals": "Opdater godkendelser på site",
"check": "Tjek"
}
+53 -13
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Ziele an diesem Standort",
"siteSetting": "{siteName} Einstellungen",
"siteNewtTunnel": "Newt Standort (empfohlen)",
"pangolinSite": "Pangolin Seite",
"siteNewtTunnelDescription": "Einfachster Weg, einen Einstiegspunkt in jedes Netzwerk zu erstellen. Keine zusätzliche Einrichtung.",
"siteWg": "Einfacher WireGuard Tunnel",
"siteWgDescription": "Verwende jeden WireGuard-Client, um einen Tunnel einzurichten. Manuelles NAT-Setup erforderlich.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "API-Schlüssel löschen",
"apiKeysManage": "API-Schlüssel verwalten",
"apiKeysDescription": "API-Schlüssel werden zur Authentifizierung mit der Integrations-API verwendet",
"orgsManage": "Organisationen verwalten",
"orgsDescription": "Alle Organisationen in dieser Instanz anzeigen und verwalten",
"provisioningKeysTitle": "Bereitstellungsschlüssel",
"provisioningKeysManage": "Bereitstellungsschlüssel verwalten",
"provisioningKeysDescription": "Bereitstellungsschlüssel werden verwendet, um die automatisierte Bereitstellung von Standorten für Ihr Unternehmen zu authentifizieren.",
@@ -716,6 +719,7 @@
"nameOptional": "Name (optional)",
"accessControls": "Zugriffskontrolle",
"userDescription2": "Verwalten Sie die Einstellungen dieses Benutzers",
"userGeneralSettingsDescription": "Die Rollen und Einstellungen dieses Benutzers in der Organisation verwalten",
"accessRoleErrorAdd": "Fehler beim Hinzufügen des Benutzers zur Rolle",
"accessRoleErrorAddDescription": "Beim Hinzufügen des Benutzers zur Rolle ist ein Fehler aufgetreten.",
"userSaved": "Benutzer gespeichert",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Mehr über JMESPath erfahren",
"idpJmespathLabel": "Identifikationspfad",
"idpJmespathLabelDescription": "Der JMESPath zum Benutzeridentifikator im ID-Token",
"idpIdentifierChangeTitle": "Warnung zur Änderung des Identifikatorpfads",
"idpIdentifierChangeDescription": "Sie stehen kurz davor, den Identifikatorpfad zu ändern. Dies wird beeinflussen, wie bestehende Benutzer zugeordnet werden. Benutzer, die sich zuvor über diesen Identitätsanbieter angemeldet haben, werden möglicherweise nicht mehr als dieselben Benutzer erkannt.",
"idpIdentifierChangeConfirmMessage": "Ich bestätige",
"idpIdentifierChangeWarningText": "Dies wird beeinflussen, wie bestehende Benutzer zugeordnet werden",
"idpJmespathEmailPathOptional": "E-Mail-Pfad (Optional)",
"idpJmespathEmailPathOptionalDescription": "Der JMESPath zur E-Mail-Adresse des Benutzers im ID-Token",
"idpJmespathNamePathOptional": "Namenspfad (Optional)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Passwort zurücksetzen Code generieren",
"passwordResetCodeGenerated": "Passwort zurücksetzen Code generiert",
"passwordResetCodeGeneratedDescription": "Teilen Sie diesen Code mit dem Benutzer. Sie können ihn verwenden, um ihr Passwort zurückzusetzen.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "URL zurücksetzen",
"passwordNew": "Neues Passwort",
"passwordNewConfirm": "Neues Passwort bestätigen",
"changePassword": "Passwort ändern",
@@ -1420,6 +1428,24 @@
"logoutError": "Fehler beim Abmelden",
"signingAs": "Angemeldet als",
"serverAdmin": "Server-Administrator",
"promoteServerAdmin": "Zum Server-Admin befördern",
"promoteServerAdminTitle": "Zum Server-Admin befördern",
"promoteServerAdminQuestion": "Sind Sie sicher, dass Sie {selectedUser} zum Server-Admin befördern möchten?",
"promoteServerAdminMessage": "Server-Admins haben die höchsten Privilegien und können den Server verwalten.",
"promoteServerAdminWarning": "Das kann jederzeit rückgängig gemacht werden, indem der Benutzer degradiert wird.",
"promoteServerAdminConfirm": "Zum Server-Admin befördern",
"promoteServerAdminSuccess": "Benutzer befördert",
"promoteServerAdminSuccessDescription": "{selectedUser} ist jetzt ein Server-Admin.",
"promoteServerAdminError": "Fehler beim Befördern des Benutzers",
"demoteServerAdmin": "Vom Server-Admin degradieren",
"demoteServerAdminTitle": "Vom Server-Admin degradieren",
"demoteServerAdminQuestion": "Sind Sie sicher, dass Sie {selectedUser} als Server-Admin degradieren möchten?",
"demoteServerAdminMessage": "{selectedUser} wird alle Server-Admin-Rechte verlieren.",
"demoteServerAdminWarning": "Das kann jederzeit rückgängig gemacht werden, indem der Benutzer befördert wird.",
"demoteServerAdminConfirm": "Vom Server-Admin degradieren",
"demoteServerAdminSuccess": "Benutzer degradiert",
"demoteServerAdminSuccessDescription": "{selectedUser} ist kein Server-Admin mehr.",
"demoteServerAdminError": "Fehler beim Degradieren des Benutzers",
"managedSelfhosted": "Verwaltetes Selbsthosted",
"otpEnable": "Zwei-Faktor aktivieren",
"otpDisable": "Zwei-Faktor deaktivieren",
@@ -1573,6 +1599,8 @@
"search": "Suche…",
"searchPlaceholder": "Suche...",
"emptySearchOptions": "Keine Optionen gefunden",
"ipFilterSearchPlaceholder": "Geben Sie eine IP-Adresse ein…",
"ipFilterEmptyMessage": "Geben Sie eine IP-Adresse zur Filterung ein",
"create": "Erstellen",
"orgs": "Organisationen",
"loginError": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
@@ -1676,7 +1704,7 @@
"commandAiProviders": "KI-Anbieter",
"sidebarVirtualApiKeys": "Virtuelle API-Schlüssel",
"sidebarMyApiKeys": "Ihre API-Schlüssel",
"sidebarAccount": "Launcher",
"sidebarAccount": "Starter",
"commandVirtualApiKeys": "Virtuelle API-Schlüssel",
"virtualApiKeysTitle": "Virtuelle API-Schlüssel verwalten",
"virtualApiKeysDescription": "Erstellen und verwalten Sie manuelle API-Schlüssel für den Zugang zum KI-Gateway des öffentlichen KI-Gateways",
@@ -1879,7 +1907,7 @@
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI Gateway",
"aiProviderAuthTypeCfAigAuthorizationDescription": "cf-aig-authorization: Bearer-Schlüssel. Wird vom Cloudflare AI Gateway verwendet",
"aiProviderAuthTypeNone": "Keine Auth",
"aiProviderAuthTypePassthrough": "Passthrough",
"aiProviderAuthTypePassthrough": "Durchreichen",
"aiProviderAuthTypeDescription": "Wie die Upstream-API Anfragen authentifiziert",
"aiProviderAuthTypePassthroughDescription": "Leiten Sie die API-Schlüssel-Header des Anrufers an den Upstream weiter",
"aiProviderAuthTypeNoneDescription": "Keine Authentifizierungs-Header an den Upstream senden",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Budget",
"resourceBudgetSettingsDescription": "Konfigurieren Sie, wie dieses KI-Gateway die Nutzung basierend auf Ausgabe- oder Token-Limits einschränkt",
"sidebarApiKeys": "API-Schlüssel",
"sidebarOrgs": "Organisationen",
"sidebarProvisioning": "Bereitstellung",
"sidebarSettings": "Einstellungen",
"sidebarAllUsers": "Alle Benutzer",
@@ -2104,7 +2133,7 @@
"sidebarAlerting": "Benachrichtigung",
"sidebarHealthChecks": "Gesundheits-Checks",
"sidebarOrganization": "Organisation",
"sidebarManagement": "Management",
"sidebarManagement": "Verwaltung",
"sidebarBillingAndLicenses": "Abrechnung & Lizenzen",
"sidebarLogsAnalytics": "Analytik",
"commandSites": "Seiten",
@@ -2596,6 +2625,7 @@
"createDomainType": "Typ:",
"createDomainName": "Name:",
"createDomainValue": "Wert:",
"multiSelectFilterCount": "{count} ausgewählt",
"createDomainCnameRecords": "CNAME-Einträge",
"createDomainARecords": "A-Aufzeichnungen",
"createDomainRecordNumber": "Eintrag {number}",
@@ -2710,6 +2740,7 @@
"healthScheme": "Methode",
"healthSelectScheme": "Methode auswählen",
"healthCheckPortInvalid": "Der Port muss zwischen 1 und 65535 liegen",
"healthCheckHostnameInvalid": "Der Hostname darf keinen Leerraum enthalten",
"healthCheckPath": "Pfad",
"healthHostname": "IP / Host",
"healthPort": "Port",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Fügen Sie einen CIDR-Bereich hinzu (z.B. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Fehler beim Laden der Subnetze",
"remoteExitNodeNetworkingLabelsTitle": "Präferenzetiketten",
"remoteExitNodeNetworkingLabelsDescription": "Standorte mit diesen Etiketten werden gezwungen, über diesen Remote Exit Node zu verbinden.",
"remoteExitNodeNetworkingLabelsDescription": "Standorte mit diesen Labels bevorzugen die Verbindung über diesen Remote-Exit-Knoten.",
"remoteExitNodeNetworkingLabelsButtonText": "Etiketten auswählen...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Etiketten suchen...",
"remoteExitNodeNetworkingLabelsLoadError": "Fehler beim Laden der Etiketten",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Entfernen",
"idpGoogleConfiguration": "Google-Konfiguration",
"idpGoogleConfigurationDescription": "Google OAuth2 Zugangsdaten konfigurieren",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Google OAuth2 Client-ID",
"idpGoogleClientSecretDescription": "Google OAuth2 Client Geheimnis",
"idpAzureConfiguration": "Azure Entra ID Konfiguration",
"idpAzureConfigurationDescription": "Azure Entra ID OAuth2 Zugangsdaten konfigurieren",
"idpTenantId": "Mandanten-ID",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "mandanten-ID",
"idpAzureTenantIdDescription": "Azure Tenant ID (gefunden in Azure Active Directory Übersicht)",
"idpAzureClientIdDescription": "Azure App Registration Client ID",
"idpAzureClientSecretDescription": "Azure App Registration Client Geheimnis",
@@ -3470,6 +3501,7 @@
"priority": "Priorität",
"priorityDescription": "Die Routen mit höherer Priorität werden zuerst ausgewertet. Priorität = 100 bedeutet automatische Bestellung (Systementscheidung). Verwenden Sie eine andere Nummer, um manuelle Priorität zu erzwingen.",
"instanceName": "Instanzname",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Pfad anpassen konfigurieren",
"pathMatchModalDescription": "Legen Sie fest, wie eingehende Anfragen basierend auf ihrem Pfad übereinstimmen sollen.",
"pathMatchType": "Übereinstimmungstyp",
@@ -3759,6 +3791,7 @@
"noData": "Keine Daten",
"machineClients": "Maschinen-Clients",
"install": "Installieren",
"downloadInstaller": "Installer herunterladen",
"run": "Ausführen",
"envFile": "Umgebungsdatei",
"serviceFile": "Servicedatei",
@@ -3966,6 +3999,7 @@
"disconnected": "Verbindung getrennt",
"approvalsEmptyStateTitle": "Gerätezulassungen nicht aktiviert",
"approvalsEmptyStateDescription": "Aktiviere Gerätegenehmigungen für Rollen, um Administratorgenehmigungen zu benötigen, bevor Benutzer neue Geräte verbinden können.",
"approvalsEmptyStateHowToTitle": "Aktivieren",
"approvalsEmptyStateStep1Title": "Gehe zu Rollen",
"approvalsEmptyStateStep1Description": "Navigieren Sie zu den Rolleneinstellungen Ihrer Organisation, um die Gerätefreigaben zu konfigurieren.",
"approvalsEmptyStateStep2Title": "Gerätegenehmigungen aktivieren",
@@ -3974,12 +4008,12 @@
"approvalsEmptyStateButtonText": "Rollen verwalten",
"domainErrorTitle": "Wir haben Probleme mit der Überprüfung deiner Domain",
"idpAdminAutoProvisionPoliciesTabHint": "Konfigurieren Sie Rollenzuordnungs- und Organisationsrichtlinien auf der Registerkarte <policiesTabLink>Auto-Bereitstellungseinstellungen</policiesTabLink>.",
"streamingTitle": "Event Streaming",
"streamingTitle": "Ereignis-Streaming",
"streamingDescription": "Streamen Sie Events aus Ihrem Unternehmen in Echtzeit zu externen Zielen.",
"streamingUnnamedDestination": "Unbenanntes Ziel",
"streamingNoUrlConfigured": "Keine URL konfiguriert",
"streamingAddDestination": "Ziel hinzufügen",
"streamingHttpWebhookTitle": "HTTP Webhook",
"streamingHttpWebhookTitle": "HTTP-Webhook",
"streamingHttpWebhookDescription": "Sende Ereignisse an jeden HTTP-Endpunkt mit flexibler Authentifizierung und Vorlage.",
"streamingS3Title": "Amazon S3",
"streamingS3Description": "Streame Ereignisse in eine S3-kompatible Objekt-Speicher-Eimer. Kommt bald.",
@@ -4071,7 +4105,7 @@
"httpDestBodyTemplateHint": "Verwenden Sie Template-Variablen, um Ereignisfelder in Ihrer Payload zu referenzieren.",
"httpDestPayloadFormatTitle": "Payload-Format",
"httpDestPayloadFormatDescription": "Wie Ereignisse in jedes Anfragegremium serialisiert werden.",
"httpDestFormatJsonArrayTitle": "JSON Array",
"httpDestFormatJsonArrayTitle": "JSON-Array",
"httpDestFormatJsonArrayDescription": "Eine Anfrage pro Stapel ist ein JSON-Array. Kompatibel mit den meisten generischen Webhooks und Datadog.",
"httpDestFormatNdjsonTitle": "NDJSON",
"httpDestFormatNdjsonDescription": "Eine Anfrage pro Batch, der Körper ist newline-getrenntes JSON - ein Objekt pro Zeile, kein äußeres Array. Benötigt von Splunk HEC, Elastic / OpenSearch, und Grafana Loki.",
@@ -4206,7 +4240,7 @@
"memberPortalResourceDisabled": "Ressource deaktiviert",
"memberPortalShowingResources": "Zeige {start}-{end} von {total} Ressourcen",
"resourceLauncherTitle": "Ressourcenstarter",
"resourceSidebarLauncherTitle": "Launcher",
"resourceSidebarLauncherTitle": "Starter",
"resourceLauncherDescription": "Alle verfügbaren Ressourcen anzeigen und von einem zentralen Hub aus starten",
"resourceLauncherSearchPlaceholder": "Suche deine Ressourcen...",
"resourceLauncherDefaultView": "Standard",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Ansicht als Administrator anzeigen",
"resourceLauncherResourceDetailsDescription": "Verbindungsinformationen und Status für diese Ressource.",
"resourceLauncherResourceDetails": "Ressourcendetails",
"resourceLauncherSitesDescription": "Die Ressource ist über die folgenden Standorte zugänglich.",
"resourceLauncherViewSiteAsAdmin": "Seite als Administrator anzeigen",
"resourceLauncherFilterBySite": "Nach Standort filtern",
"resourceLauncherSshCommand": "SSH-Befehl",
"resourceLauncherSshCommandDescription": "Verwenden Sie die Pangolin-CLI, um eine SSH-Sitzung zu dieser Ressource zu öffnen.",
"resourceLauncherAuthMethodsDescription": "Aktivierte Authentifizierungsmethoden für diese Ressource.",
"resourceLauncherPrivateClientRequired": "Verbinde dich mit einem Client auf deinem Gerät, um privat auf diese Ressource zuzugreifen.",
"resourceLauncherPrivateClientRequiredTitle": "Client-Verbindung erforderlich",
@@ -4346,7 +4385,7 @@
"rdpConnectionFailed": "Verbindung fehlgeschlagen",
"rdpFit": "Anpassen",
"rdpFull": "Vollständig",
"rdpReal": "Real",
"rdpReal": "Echt",
"rdpMeta": "Meta",
"rdpUploadFiles": "Dateien hochladen",
"rdpFilesReadyToPaste": "Dateien bereit zum Einfügen",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode-Tastaturmodus",
"sessionToolbarShow": "Werkzeugleiste zeigen",
"sessionToolbarHide": "Werkzeugleiste ausblenden",
"actionUpdateSiteApprovals": "Standortgenehmigungen aktualisieren"
"actionUpdateSiteApprovals": "Standortgenehmigungen aktualisieren",
"check": "Prüfen"
}
+59 -19
View File
@@ -80,7 +80,7 @@
"siteManageSites": "Manage Sites",
"siteDescription": "Create and manage sites to enable connectivity to private networks",
"sitesBannerTitle": "Connect Any Network",
"sitesBannerDescription": "A site is a connection to a remote network that allows Pangolin to provide access to resources, whether public or private, to users anywhere. Install the site network connector (Newt) anywhere you can run a binary or container to establish the connection.",
"sitesBannerDescription": "A site is a connection to a remote network that allows Pangolin to provide access to resources, whether public or private, to users anywhere. Install the site network connector anywhere you can run a binary or container to establish the connection.",
"sitesBannerButtonText": "Install Site Connector",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
@@ -152,7 +152,8 @@
"siteResourcesHowToAccess": "How to access",
"siteResourcesTargetsOnSite": "Targets on this site",
"siteSetting": "{siteName} Settings",
"siteNewtTunnel": "Newt Site (Recommended)",
"siteNewtTunnel": "Pangolin Site (Recommended)",
"pangolinSite": "Pangolin Site",
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
"siteWg": "Basic WireGuard",
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.",
@@ -226,9 +227,9 @@
"never": "Never",
"shareErrorSelectResource": "Please select a resource",
"proxyResourceTitle": "Manage Public Resources",
"proxyResourceDescription": "Create and manage resources that are publicly accessible through a web browser",
"proxyResourceDescription": "Create and manage resources that are publicly accessible via a proxy",
"publicResourcesBannerTitle": "Web-based Public Access",
"publicResourcesBannerDescription": "Public resources are proxies accessible to anyone on the internet through a web browser and include identity and context-aware access policies. Unlike private resources, they do not require client-side software.",
"publicResourcesBannerDescription": "Public resources are proxies accessible to anyone on the internet, like a website or API, and include identity and context-aware access policies. Unlike private resources, they do not require any client-side software to access.",
"clientResourceTitle": "Manage Private Resources",
"clientResourceDescription": "Create and manage resources that are only accessible through a connected client",
"privateResourcesBannerTitle": "Zero-Trust Private Access",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Delete API Key",
"apiKeysManage": "Manage API Keys",
"apiKeysDescription": "API keys are used to authenticate with the integration API",
"orgsManage": "Manage Organizations",
"orgsDescription": "View and manage all organizations on this instance",
"provisioningKeysTitle": "Provisioning Key",
"provisioningKeysManage": "Manage Provisioning Keys",
"provisioningKeysDescription": "Provisioning keys are used to authenticate automated site provisioning for your organization.",
@@ -520,12 +523,12 @@
"pendingSitesBannerDescription": "Sites that connect using a provisioning key appear here for review.",
"pendingSitesBannerButtonText": "Learn More",
"apiKeysSettings": "{apiKeyName} Settings",
"userTitle": "Manage All Users",
"userDescription": "View and manage all users in the system",
"userTitle": "Manage Users",
"userDescription": "View and manage all users in this instance",
"userAbount": "About User Management",
"userAbountDescription": "This table displays all base user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their base user object. They will remain in the system. To completely remove a user from the system, you must delete their base user object using the delete action in this table.",
"userServer": "Server Users",
"userSearch": "Search server users...",
"userSearch": "Search users...",
"userErrorDelete": "Error deleting user",
"userDeleteConfirm": "Confirm Delete User",
"userDeleteServer": "Delete User from Server",
@@ -594,7 +597,7 @@
"licensePricingPage": "For the most up-to-date pricing and discounts, please visit the ",
"invite": "Invitations",
"inviteRegenerate": "Regenerate Invitation",
"inviteRegenerateDescription": "Revoke previous invitation and create a new one",
"inviteRegenerateDescription": "Create a new invite link for this user. The previous invitation will be revoked.",
"inviteRemove": "Remove Invitation",
"inviteRemoveError": "Failed to remove invitation",
"inviteRemoveErrorDescription": "An error occurred while removing the invitation.",
@@ -674,11 +677,11 @@
"accessUserCreateDescription": "Follow the steps below to create a new user",
"userSeeAll": "See All Users",
"userTypeTitle": "User Type",
"userTypeDescription": "Determine how you want to create the user",
"userTypeDescription": "Select the identity provider to use for this user",
"userSettings": "User Information",
"userSettingsDescription": "Enter the details for the new user",
"userSettingsDescription": "Enter the general details for the new user",
"inviteEmailSent": "Send invite email to user",
"inviteValid": "Invite Valid For (days)",
"inviteValid": "Invite Valid For",
"selectDuration": "Select duration",
"selectResource": "Select Resource",
"filterByResource": "Filter By Resource",
@@ -716,6 +719,7 @@
"nameOptional": "Name (Optional)",
"accessControls": "Access Controls",
"userDescription2": "Manage the settings on this user",
"userGeneralSettingsDescription": "Manage this user's roles and settings in the organization",
"accessRoleErrorAdd": "Failed to add user to role",
"accessRoleErrorAddDescription": "An error occurred while adding user to the role.",
"userSaved": "User saved",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Learn more about JMESPath",
"idpJmespathLabel": "Identifier Path",
"idpJmespathLabelDescription": "The path to the user identifier in the ID token",
"idpIdentifierChangeTitle": "Identifier Path Change Warning",
"idpIdentifierChangeDescription": "You are about to change the identifier path. This will affect how existing users are mapped. Users who previously signed in through this identity provider may no longer be recognized as the same users.",
"idpIdentifierChangeConfirmMessage": "I confirm",
"idpIdentifierChangeWarningText": "This will affect how existing users are mapped",
"idpJmespathEmailPathOptional": "Email Path (Optional)",
"idpJmespathEmailPathOptionalDescription": "The path to the user's email in the ID token",
"idpJmespathNamePathOptional": "Name Path (Optional)",
@@ -1210,7 +1218,7 @@
"orgPoliciesEdit": "Edit Organization Policy",
"org": "Organization",
"orgSelect": "Select organization",
"orgSearch": "Search org",
"orgSearch": "Search organizations...",
"orgNotFound": "No org found.",
"roleMappingPathOptional": "Role Mapping Path (Optional)",
"orgMappingPathOptional": "Organization Mapping Path (Optional)",
@@ -1349,7 +1357,7 @@
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "No labels found.",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Search labels",
"labelSearch": "Search labels...",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
@@ -1420,6 +1428,24 @@
"logoutError": "Error logging out",
"signingAs": "Signed in as",
"serverAdmin": "Server Admin",
"promoteServerAdmin": "Promote to Server admin",
"promoteServerAdminTitle": "Promote to Server Admin",
"promoteServerAdminQuestion": "Are you sure you want to promote {selectedUser} to server admin?",
"promoteServerAdminMessage": "Server admins have the highest privileges and can manage the server.",
"promoteServerAdminWarning": "This can be undone at any time by demoting the user.",
"promoteServerAdminConfirm": "Promote to Server Admin",
"promoteServerAdminSuccess": "User Promoted",
"promoteServerAdminSuccessDescription": "{selectedUser} is now a server admin.",
"promoteServerAdminError": "Failed to promote user",
"demoteServerAdmin": "Demote from Server admin",
"demoteServerAdminTitle": "Demote from Server Admin",
"demoteServerAdminQuestion": "Are you sure you want to demote {selectedUser} from server admin?",
"demoteServerAdminMessage": "{selectedUser} will lose all server admin privileges.",
"demoteServerAdminWarning": "This can be undone at any time by promoting the user.",
"demoteServerAdminConfirm": "Demote from server admin",
"demoteServerAdminSuccess": "User demoted",
"demoteServerAdminSuccessDescription": "{selectedUser} is no longer a server admin.",
"demoteServerAdminError": "Failed to demote user",
"managedSelfhosted": "Managed Self-Hosted",
"otpEnable": "Enable Two-factor",
"otpDisable": "Disable Two-factor",
@@ -1573,6 +1599,8 @@
"search": "Search…",
"searchPlaceholder": "Search...",
"emptySearchOptions": "No options found",
"ipFilterSearchPlaceholder": "Enter an IP address…",
"ipFilterEmptyMessage": "Enter an IP address to filter by",
"create": "Create",
"orgs": "Organizations",
"loginError": "An unexpected error occurred. Please try again.",
@@ -2089,9 +2117,10 @@
"resourceBudgetSettings": "Budget",
"resourceBudgetSettingsDescription": "Configure how this AI gateway restricts usage based on spending or token limits",
"sidebarApiKeys": "API Keys",
"sidebarOrgs": "Organizations",
"sidebarProvisioning": "Provisioning",
"sidebarSettings": "Settings",
"sidebarAllUsers": "All Users",
"sidebarAllUsers": "Users",
"sidebarIdentityProviders": "Identity Providers",
"sidebarLicense": "License",
"sidebarClients": "Clients",
@@ -2596,6 +2625,7 @@
"createDomainType": "Type:",
"createDomainName": "Name:",
"createDomainValue": "Value:",
"multiSelectFilterCount": "{count} selected",
"createDomainCnameRecords": "CNAME Records",
"createDomainARecords": "A Records",
"createDomainRecordNumber": "Record {number}",
@@ -2710,6 +2740,7 @@
"healthScheme": "Method",
"healthSelectScheme": "Select Method",
"healthCheckPortInvalid": "Port must be between 1 and 65535",
"healthCheckHostnameInvalid": "Hostname must not contain whitespace",
"healthCheckPath": "Path",
"healthHostname": "IP / Host",
"healthPort": "Port",
@@ -2890,7 +2921,7 @@
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.",
"createInternalResourceDialogNoSitesAvailable": "No Sites Available",
"createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one Newt site with a subnet configured to create private resources.",
"createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one site with a subnet configured to create private resources.",
"createInternalResourceDialogClose": "Close",
"createInternalResourceDialogCreateClientResource": "Create Private Resource",
"createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will prefer to connect through this remote exit node.",
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
@@ -3469,7 +3500,8 @@
},
"priority": "Priority",
"priorityDescription": "Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.",
"instanceName": "Instance Name",
"instanceName": "Server ID",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Configure Path Matching",
"pathMatchModalDescription": "Set up how incoming requests should be matched based on their path.",
"pathMatchType": "Match Type",
@@ -3759,6 +3791,7 @@
"noData": "No Data",
"machineClients": "Machine Clients",
"install": "Install",
"downloadInstaller": "Download Installer",
"run": "Run",
"envFile": "Environment File",
"serviceFile": "Service File",
@@ -3966,11 +3999,12 @@
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateHowToTitle": "How to Enable",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStatePreviewDescription": "When enabled, pending device requests will appear here for review.",
"approvalsEmptyStateButtonText": "Manage Roles",
"domainErrorTitle": "We are having trouble verifying your domain",
"idpAdminAutoProvisionPoliciesTabHint": "Configure role mapping and organization policies on the <policiesTabLink>Auto Provision Settings</policiesTabLink> tab.",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "View as Admin",
"resourceLauncherResourceDetailsDescription": "Connection information and status for this resource.",
"resourceLauncherResourceDetails": "Resource Details",
"resourceLauncherSitesDescription": "The resource is accessible via the following sites.",
"resourceLauncherViewSiteAsAdmin": "View Site as Admin",
"resourceLauncherFilterBySite": "Filter by Site",
"resourceLauncherSshCommand": "SSH Command",
"resourceLauncherSshCommandDescription": "Use the Pangolin CLI to open an SSH session to this resource.",
"resourceLauncherAuthMethodsDescription": "Authentication methods enabled for this resource.",
"resourceLauncherPrivateClientRequired": "Connect with a client on your device to access this resource privately.",
"resourceLauncherPrivateClientRequiredTitle": "Client Connection Required",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
"sessionToolbarShow": "Show toolbar",
"sessionToolbarHide": "Hide toolbar",
"actionUpdateSiteApprovals": "Update Site Approvals"
"actionUpdateSiteApprovals": "Update Site Approvals",
"check": "Check"
}
+61 -21
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Objetivos en este sitio",
"siteSetting": "Ajustes {siteName}",
"siteNewtTunnel": "Sitio nuevo (recomendado)",
"pangolinSite": "Sitio de Pangolin",
"siteNewtTunnelDescription": "La forma más fácil de crear un punto de entrada en cualquier red. Sin configuración extra.",
"siteWg": "Wirex Guardia Básica",
"siteWgDescription": "Utilice cualquier cliente Wirex Guard para establecer un túnel. Se requiere una configuración manual de NAT.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Borrar Clave API",
"apiKeysManage": "Administrar claves API",
"apiKeysDescription": "Las claves API se utilizan para autenticar con la API de integración",
"orgsManage": "Administrar Organizaciones",
"orgsDescription": "Ver y gestionar todas las organizaciones en esta instancia",
"provisioningKeysTitle": "Clave de aprovisionamiento",
"provisioningKeysManage": "Administrar Claves de Aprovisionamiento",
"provisioningKeysDescription": "Las claves de aprovisionamiento se utilizan para autenticar la provisión automatizada del sitio para su organización.",
@@ -716,6 +719,7 @@
"nameOptional": "Nombre (opcional)",
"accessControls": "Controles de acceso",
"userDescription2": "Administrar la configuración de este usuario",
"userGeneralSettingsDescription": "Gestionar los roles y configuraciones de este usuario en la organización",
"accessRoleErrorAdd": "No se pudo agregar el usuario al rol",
"accessRoleErrorAddDescription": "Ocurrió un error mientras se añadía el usuario al rol.",
"userSaved": "Usuario guardado",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Nombre de servidor TLS inválido. Utilice el formato de nombre de dominio o guarde en blanco para eliminar el nombre de servidor TLS.",
"proxyEnableSSL": "Activar TLS",
"proxyEnableSSLDescription": "Habilita el cifrado SSL/TLS para conexiones seguras HTTPS a los objetivos.",
"target": "Target",
"target": "Destino",
"configureTarget": "Configurar objetivos",
"targetErrorFetch": "Error al recuperar los objetivos",
"targetErrorFetchDescription": "Se ha producido un error al recuperar los objetivos",
@@ -948,7 +952,7 @@
"unknownCommand": "Comando desconocido",
"newtErrorFetchReleases": "No se pudo obtener la información del lanzamiento: {err}",
"newtErrorFetchLatest": "Error obteniendo la última versión: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Punto final",
"newtId": "ID",
"newtSecretKey": "Secreto",
"newtVersion": "Versión",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Más información sobre JMESPath",
"idpJmespathLabel": "Ruta del identificador",
"idpJmespathLabelDescription": "La ruta al identificador de usuario en el token de ID",
"idpIdentifierChangeTitle": "Advertencia de Cambio de Ruta de Identificador",
"idpIdentifierChangeDescription": "Está a punto de cambiar la ruta del identificador. Esto afectará cómo se asignan los usuarios existentes. Los usuarios que anteriormente iniciaron sesión a través de este proveedor de identidad pueden ya no ser reconocidos como los mismos usuarios.",
"idpIdentifierChangeConfirmMessage": "Confirmo",
"idpIdentifierChangeWarningText": "Esto afectará cómo se asignan los usuarios existentes",
"idpJmespathEmailPathOptional": "Ruta de correo (opcional)",
"idpJmespathEmailPathOptionalDescription": "La ruta al correo electrónico del usuario en el token de ID",
"idpJmespathNamePathOptional": "Ruta del nombre (opcional)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Generar código de restablecimiento de contraseña",
"passwordResetCodeGenerated": "Código de restablecimiento de contraseña generado",
"passwordResetCodeGeneratedDescription": "Comparte este código con el usuario. Pueden usarlo para restablecer su contraseña.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "URL de reinicio",
"passwordNew": "Nueva contraseña",
"passwordNewConfirm": "Confirmar nueva contraseña",
"changePassword": "Cambiar Contraseña",
@@ -1379,8 +1387,8 @@
"pangolinDashboard": "Tablero - Pangolin",
"noResults": "No se han encontrado resultados.",
"terabytes": "TB {count}",
"gigabytes": "{count} GB",
"megabytes": "{count} MB",
"gigabytes": "{count, plural, one {{count} GB} other {{count} GB}}",
"megabytes": "{count, plural, one {{count} MB} other {{count} MB}}",
"tagsEntered": "Etiquetas introducidas",
"tagsEnteredDescription": "Estas son las etiquetas que has introducido.",
"tagsWarnCannotBeLessThanZero": "maxTags y minTags no pueden ser menores que 0",
@@ -1420,6 +1428,24 @@
"logoutError": "Error al cerrar sesión",
"signingAs": "Conectado como",
"serverAdmin": "Admin Servidor",
"promoteServerAdmin": "Promover a Admin del Servidor",
"promoteServerAdminTitle": "Promover a Admin del Servidor",
"promoteServerAdminQuestion": "¿Estás seguro de que quieres promover a {selectedUser} a admin del servidor?",
"promoteServerAdminMessage": "Los administradores del servidor tienen los máximos privilegios y pueden gestionar el servidor.",
"promoteServerAdminWarning": "Esto se puede deshacer en cualquier momento degradando al usuario.",
"promoteServerAdminConfirm": "Promover a Admin del Servidor",
"promoteServerAdminSuccess": "Usuario Promovido",
"promoteServerAdminSuccessDescription": "{selectedUser} ahora es administrador del servidor.",
"promoteServerAdminError": "Error al promover al usuario",
"demoteServerAdmin": "Degradar de Admin del Servidor",
"demoteServerAdminTitle": "Degradar de Admin del Servidor",
"demoteServerAdminQuestion": "¿Estás seguro de que quieres degradar a {selectedUser} de administrador del servidor?",
"demoteServerAdminMessage": "{selectedUser} perderá todos los privilegios de administrador del servidor.",
"demoteServerAdminWarning": "Esto se puede deshacer en cualquier momento promoviendo al usuario.",
"demoteServerAdminConfirm": "Degradar de Admin del Servidor",
"demoteServerAdminSuccess": "Usuario Degradado",
"demoteServerAdminSuccessDescription": "{selectedUser} ya no es administrador del servidor.",
"demoteServerAdminError": "Error al degradar al usuario",
"managedSelfhosted": "Autogestionado",
"otpEnable": "Activar doble factor",
"otpDisable": "Desactivar doble factor",
@@ -1573,6 +1599,8 @@
"search": "Buscar…",
"searchPlaceholder": "Buscar...",
"emptySearchOptions": "No se encontraron opciones",
"ipFilterSearchPlaceholder": "Introduzca una dirección IP…",
"ipFilterEmptyMessage": "Introduzca una dirección IP para filtrar por",
"create": "Crear",
"orgs": "Organizaciones",
"loginError": "Ocurrió un error inesperado. Por favor, inténtelo de nuevo.",
@@ -1918,7 +1946,7 @@
"aiProviderCapabilitiesSelect": "Seleccionar capacidades",
"aiProviderCapabilitiesEmpty": "No se encontraron capacidades",
"aiProviderCapabilitiesSearch": "Buscar capacidades...",
"aiCapabilityOpenaiChat": "OpenAI Chat Completions",
"aiCapabilityOpenaiChat": "Completaciones de Chat OpenAI",
"aiCapabilityOpenaiChatDescription": "Admite /v1/chat/completions",
"aiCapabilityOpenaiResponses": "Respuestas de OpenAI",
"aiCapabilityOpenaiResponsesDescription": "Admite /v1/responses",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Presupuesto",
"resourceBudgetSettingsDescription": "Configure cómo este portal de IA restringe el uso en función de los límites de gasto o tokens",
"sidebarApiKeys": "Claves API",
"sidebarOrgs": "Organizaciones",
"sidebarProvisioning": "Aprovisionamiento",
"sidebarSettings": "Ajustes",
"sidebarAllUsers": "Todos los usuarios",
@@ -2596,6 +2625,7 @@
"createDomainType": "Tipo:",
"createDomainName": "Nombre:",
"createDomainValue": "Valor:",
"multiSelectFilterCount": "{count} seleccionado",
"createDomainCnameRecords": "Registros CNAME",
"createDomainARecords": "Registros A",
"createDomainRecordNumber": "Registro {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Obtén Olm funcionando en tu sistema",
"clientOlmCredentials": "Credenciales",
"clientOlmCredentialsDescription": "Así es como el cliente se autentificará con el servidor",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Punto final",
"olmId": "ID",
"olmSecretKey": "Secreto",
"clientCredentialsSave": "Guardar las credenciales",
@@ -2710,6 +2740,7 @@
"healthScheme": "Método",
"healthSelectScheme": "Seleccionar método",
"healthCheckPortInvalid": "El puerto debe estar entre 1 y 65535",
"healthCheckHostnameInvalid": "El nombre de host no debe contener espacios en blanco",
"healthCheckPath": "Ruta",
"healthHostname": "IP / Nombre del host",
"healthPort": "Puerto",
@@ -2899,7 +2930,7 @@
"privateResourceAllowIcmpPing": "Permitir ping ICMP",
"privateResourceNetworkAccess": "Acceso de red",
"privateResourceNetworkAccessDescription": "Controlar el acceso a puertos TCP/UDP y si se permite ping ICMP para este recurso.",
"hostSettings": "Host",
"hostSettings": "Anfitrión",
"cidrSettings": "CIDR",
"createInternalResourceDialogResourceProperties": "Propiedades del recurso",
"createInternalResourceDialogName": "Nombre",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Añadir un rango CIDR (e.g. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Error al cargar las subredes",
"remoteExitNodeNetworkingLabelsTitle": "Etiquetas de Preferencias",
"remoteExitNodeNetworkingLabelsDescription": "Los sitios con estas etiquetas se verán obligados a conectarse a través de este nodo de salida remoto.",
"remoteExitNodeNetworkingLabelsDescription": "Los sitios con estas etiquetas preferirán conectarse a través de este nodo de salida remoto.",
"remoteExitNodeNetworkingLabelsButtonText": "Seleccionar etiquetas...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Buscar etiquetas...",
"remoteExitNodeNetworkingLabelsLoadError": "Error al cargar las etiquetas",
@@ -3059,7 +3090,7 @@
"rulesMatchRegion": "Seleccione una agrupación regional de países",
"rulesErrorInvalidRegion": "Región no válida",
"rulesErrorInvalidRegionDescription": "Por favor, seleccione una región válida.",
"regionAfrica": "Africa",
"regionAfrica": "África",
"regionNorthernAfrica": "África septentrional",
"regionEasternAfrica": "África oriental",
"regionMiddleAfrica": "África central",
@@ -3081,7 +3112,7 @@
"regionNorthernEurope": "Europa septentrional",
"regionSouthernEurope": "Europa meridional",
"regionWesternEurope": "Europa Occidental",
"regionOceania": "Oceania",
"regionOceania": "Oceanía",
"regionAustraliaAndNewZealand": "Australia y Nueva Zelanda",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Eliminar",
"idpGoogleConfiguration": "Configuración de Google",
"idpGoogleConfigurationDescription": "Configurar las credenciales de Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Tu ID de cliente de Google OAuth2",
"idpGoogleClientSecretDescription": "Secreto del cliente de Google OAuth2",
"idpAzureConfiguration": "Configuración de Azure Entra ID",
"idpAzureConfigurationDescription": "Configurar credenciales de Azure Entra ID OAuth2",
"idpTenantId": "ID del inquilino",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "iD del inquilino",
"idpAzureTenantIdDescription": "ID de inquilino Azure (encontrado en la descripción de Azure Active Directory)",
"idpAzureClientIdDescription": "ID de cliente de registro de Azure App",
"idpAzureClientSecretDescription": "Azure App Registro Cliente secreto",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "ID de cliente de registro de Azure App",
"idpAzureClientSecretDescription2": "Azure App Registro Cliente secreto",
"idpGoogleDescription": "Proveedor OAuth2/OIDC de Google",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Proveedor OAuth2/OIDC de Google",
"subnet": "Subred",
"utilitySubnet": "Subred de Utilidad",
"subnetDescription": "La subred para la configuración de red de esta organización.",
@@ -3470,6 +3501,7 @@
"priority": "Prioridad",
"priorityDescription": "Las rutas de prioridad más alta son evaluadas primero. Prioridad = 100 significa orden automático (decisiones del sistema). Utilice otro número para hacer cumplir la prioridad manual.",
"instanceName": "Nombre de instancia",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Configurar ruta coincidente",
"pathMatchModalDescription": "Configurar cómo deben coincidir las peticiones entrantes en función de su ruta.",
"pathMatchType": "Tipo de partida",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Permitido por regla",
"allowedNoAuth": "No se permite autorización",
"validAccessToken": "Token de Acceso Válido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Autenticación Básica del Encabezado",
"validPincode": "Definir Pincode",
"validPassword": "Contraseña válida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Válido hasta",
"validSSO": "Clave válida",
"validVirtualAPIKey": "Clave API Virtual Válida",
"view": "Ver",
"configManaged": "Configuración Gestionada",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Regenerar las credenciales invalidará las anteriores y causará una desconexión. Asegúrese de actualizar cualquier configuración que use estas credenciales.",
"confirm": "Confirmar",
"regenerateCredentialsConfirmation": "¿Está seguro que desea regenerar las credenciales?",
"endpoint": "Endpoint",
"endpoint": "Punto final",
"Id": "Id",
"SecretKey": "Clave secreta",
"niceId": "ID bonita",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Se ha producido un error al actualizar el ID de Niza.",
"niceIdCannotBeEmpty": "El ID de Niza no puede estar vacío",
"enterIdentifier": "Introducir identificador",
"identifier": "Identifier",
"identifier": "Ruta del identificador",
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
"loginSelectAuthenticationMethod": "Seleccione un método de autenticación para continuar.",
"noData": "Sin datos",
"machineClients": "Clientes de la máquina",
"install": "Instalar",
"downloadInstaller": "Error al descargar",
"run": "Ejecutar",
"envFile": "Archivo de Entorno",
"serviceFile": "Archivo de Servicio",
@@ -3966,6 +3999,7 @@
"disconnected": "Desconectado",
"approvalsEmptyStateTitle": "Aprobaciones de dispositivo no habilitadas",
"approvalsEmptyStateDescription": "Habilita las aprobaciones de dispositivos para que los roles requieran aprobación del administrador antes de que los usuarios puedan conectar nuevos dispositivos.",
"approvalsEmptyStateHowToTitle": "Cómo habilitar",
"approvalsEmptyStateStep1Title": "Ir a roles",
"approvalsEmptyStateStep1Description": "Navega a la configuración de roles de tu organización para configurar las aprobaciones de dispositivos.",
"approvalsEmptyStateStep2Title": "Habilitar aprobaciones de dispositivo",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Ver como Administrador",
"resourceLauncherResourceDetailsDescription": "Información de conexión y estado de este recurso.",
"resourceLauncherResourceDetails": "Detalles del recurso",
"resourceLauncherSitesDescription": "El recurso es accesible a través de los siguientes sitios.",
"resourceLauncherViewSiteAsAdmin": "Ver Sitio como Administrador",
"resourceLauncherFilterBySite": "Filtrar por Sitio",
"resourceLauncherSshCommand": "Comando SSH",
"resourceLauncherSshCommandDescription": "Usa el CLI de Pangolin para abrir una sesión SSH para este recurso.",
"resourceLauncherAuthMethodsDescription": "Métodos de autenticación habilitados para este recurso.",
"resourceLauncherPrivateClientRequired": "Conéctese con un cliente en su dispositivo para acceder a este recurso en privado.",
"resourceLauncherPrivateClientRequiredTitle": "Se requiere conexión del cliente",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Modo teclado Unicode",
"sessionToolbarShow": "Mostrar barra de herramientas",
"sessionToolbarHide": "Ocultar barra de herramientas",
"actionUpdateSiteApprovals": "Actualizar aprobaciones del sitio"
"actionUpdateSiteApprovals": "Actualizar aprobaciones del sitio",
"check": "Comprobar"
}
+60 -20
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Cibles sur ce site",
"siteSetting": "Paramètres de {siteName}",
"siteNewtTunnel": "Site Newt (Recommandé)",
"pangolinSite": "Site Pangolin",
"siteNewtTunnelDescription": "La façon la plus simple de créer un point d'entrée dans n'importe quel réseau. Pas de configuration supplémentaire.",
"siteWg": "WireGuard basique",
"siteWgDescription": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Supprimer la clé d'API",
"apiKeysManage": "Gérer les clés d'API",
"apiKeysDescription": "Les clés d'API sont utilisées pour s'authentifier avec l'API d'intégration",
"orgsManage": "Gérer les organisations",
"orgsDescription": "Voir et gérer toutes les organisations sur cette instance",
"provisioningKeysTitle": "Clé de provisioning",
"provisioningKeysManage": "Gérer les clés de provisioning",
"provisioningKeysDescription": "Les clés de provisioning sont utilisées pour authentifier la fourniture automatique de sites pour votre organisation.",
@@ -716,6 +719,7 @@
"nameOptional": "Nom (Optionnel)",
"accessControls": "Contrôles d'accès",
"userDescription2": "Gérer les paramètres de cet utilisateur",
"userGeneralSettingsDescription": "Gérer les rôles et paramètres de cet utilisateur dans l'organisation",
"accessRoleErrorAdd": "Échec de l'ajout de l'utilisateur au rôle",
"accessRoleErrorAddDescription": "Une erreur s'est produite lors de l'ajout de l'utilisateur au rôle.",
"userSaved": "Utilisateur enregistré",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Lorsque les règles sont désactivées, tout le trafic passe par l'authentification.",
"policyAccessRulesFallthroughOn": "Lorsqu'aucune règle ne correspond, le trafic passe par l'authentification.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/administrateur/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Enregistrer les règles",
"resourceErrorCreate": "Erreur lors de la création de la ressource",
@@ -948,7 +952,7 @@
"unknownCommand": "Commande inconnue",
"newtErrorFetchReleases": "Échec de la récupération des informations de version : {err}",
"newtErrorFetchLatest": "Erreur lors de la récupération de la dernière version : {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Point de terminaison",
"newtId": "ID",
"newtSecretKey": "Secrète",
"newtVersion": "Version",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "En savoir plus sur JMESPath",
"idpJmespathLabel": "Chemin d'identification",
"idpJmespathLabelDescription": "Le JMESPath vers l'identifiant de l'utilisateur dans le jeton ID",
"idpIdentifierChangeTitle": "Avertissement de changement de chemin d'identification",
"idpIdentifierChangeDescription": "Vous êtes sur le point de modifier le chemin d'identification. Cela affectera la manière dont les utilisateurs existants sont mappés. Les utilisateurs qui se sont connectés via ce fournisseur d'identité peuvent ne plus être reconnus comme les mêmes utilisateurs.",
"idpIdentifierChangeConfirmMessage": "Je confirme",
"idpIdentifierChangeWarningText": "Cela affectera la manière dont les utilisateurs existants sont mappés",
"idpJmespathEmailPathOptional": "Chemin de l'email (Optionnel)",
"idpJmespathEmailPathOptionalDescription": "Le JMESPath vers l'email de l'utilisateur dans le jeton ID",
"idpJmespathNamePathOptional": "Chemin du nom (Optionnel)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Générer le code de réinitialisation du mot de passe",
"passwordResetCodeGenerated": "Code de réinitialisation du mot de passe généré",
"passwordResetCodeGeneratedDescription": "Partagez ce code avec l'utilisateur. Il peut l'utiliser pour réinitialiser son mot de passe.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "Réinitialiser l'URL",
"passwordNew": "Nouveau mot de passe",
"passwordNewConfirm": "Confirmer le nouveau mot de passe",
"changePassword": "Changer le mot de passe",
@@ -1420,6 +1428,24 @@
"logoutError": "Erreur lors de la déconnexion",
"signingAs": "Connecté en tant que",
"serverAdmin": "Admin Serveur",
"promoteServerAdmin": "Promouvoir en tant qu'administrateur serveur",
"promoteServerAdminTitle": "Promouvoir en tant qu'administrateur serveur",
"promoteServerAdminQuestion": "Êtes-vous sûr de vouloir promouvoir {selectedUser} en tant qu'administrateur serveur ?",
"promoteServerAdminMessage": "Les administrateurs serveurs ont les privilèges les plus élevés et peuvent gérer le serveur.",
"promoteServerAdminWarning": "Cela peut être annulé à tout moment en rétrogradant l'utilisateur.",
"promoteServerAdminConfirm": "Promouvoir en tant qu'administrateur serveur",
"promoteServerAdminSuccess": "Utilisateur promu",
"promoteServerAdminSuccessDescription": "{selectedUser} est maintenant un administrateur serveur.",
"promoteServerAdminError": "Échec de la promotion de l'utilisateur",
"demoteServerAdmin": "Rétrograder d'administrateur serveur",
"demoteServerAdminTitle": "Rétrograder d'administrateur serveur",
"demoteServerAdminQuestion": "Êtes-vous sûr de vouloir rétrograder {selectedUser} de l'administrateur serveur ?",
"demoteServerAdminMessage": "{selectedUser} perdra tous les privilèges d'administrateur serveur.",
"demoteServerAdminWarning": "Cela peut être annulé à tout moment en promouvant l'utilisateur.",
"demoteServerAdminConfirm": "Rétrograder d'administrateur serveur",
"demoteServerAdminSuccess": "Utilisateur rétrogradé",
"demoteServerAdminSuccessDescription": "{selectedUser} n'est plus un administrateur serveur.",
"demoteServerAdminError": "Échec de la rétrogradation de l'utilisateur",
"managedSelfhosted": "Gestion autonome",
"otpEnable": "Activer l'authentification à deux facteurs",
"otpDisable": "Désactiver l'authentification à deux facteurs",
@@ -1573,6 +1599,8 @@
"search": "Rechercher…",
"searchPlaceholder": "Recherche...",
"emptySearchOptions": "Aucune option trouvée",
"ipFilterSearchPlaceholder": "Entrez une adresse IP…",
"ipFilterEmptyMessage": "Entrez une adresse IP pour filtrer",
"create": "Créer",
"orgs": "Organisations",
"loginError": "Une erreur inattendue s'est produite. Veuillez réessayer.",
@@ -1594,7 +1622,7 @@
"commandPaletteSearching": "Recherche en cours...",
"commandPaletteNavigation": "Navigation",
"commandPaletteOrganizations": "Organisations",
"commandPaletteSites": "Sites",
"commandPaletteSites": "Nœuds",
"commandPaletteResources": "Ressource",
"commandPaletteUsers": "Utilisateurs",
"commandPaletteClients": "Liste des clients",
@@ -1986,7 +2014,7 @@
"aiProviderModelsBudgetConfigured": "Budget configuré",
"aiProviderModelsEditTitle": "Modifier le modèle",
"aiProviderModelsEditDescription": "Mettez à jour la clé du modèle ou configurez son budget.",
"aiProviderModelsBudgetTab": "Budget",
"aiProviderModelsBudgetTab": "Configurer le budget de ce fournisseur pour restreindre l'utilisation en fonction des dépenses ou des limites de jetons",
"aiProviderModelsKeyLabel": "Clé du modèle",
"aiProviderModelsKeyRequired": "Entrez une clé de modèle",
"aiProviderModelsKeyDuplicate": "Cette clé de modèle est déjà sur une liste",
@@ -2086,9 +2114,10 @@
"aiUsageUnnamedVirtualApiKey": "Clé sans nom",
"aiUsageLoading": "Chargement...",
"aiUsageNoData": "Pas de données",
"resourceBudgetSettings": "Budget",
"resourceBudgetSettings": "Configurer le budget de ce fournisseur pour restreindre l'utilisation en fonction des dépenses ou des limites de jetons",
"resourceBudgetSettingsDescription": "Configurez comment ce portail AI limite l'utilisation en fonction des dépenses ou des limites de jetons",
"sidebarApiKeys": "Clés API",
"sidebarOrgs": "Organisations",
"sidebarProvisioning": "Mise en place",
"sidebarSettings": "Réglages",
"sidebarAllUsers": "Tous les utilisateurs",
@@ -2596,6 +2625,7 @@
"createDomainType": "Type :",
"createDomainName": "Nom :",
"createDomainValue": "Valeur :",
"multiSelectFilterCount": "{count} sélectionné",
"createDomainCnameRecords": "Enregistrements CNAME",
"createDomainARecords": "Enregistrements A",
"createDomainRecordNumber": "Enregistrement {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Faites fonctionner Olm sur votre système",
"clientOlmCredentials": "Identifiants",
"clientOlmCredentialsDescription": "C'est ainsi que le client s'authentifie avec le serveur",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Point de terminaison",
"olmId": "ID",
"olmSecretKey": "Secrète",
"clientCredentialsSave": "Enregistrer les informations d'identification",
@@ -2710,6 +2740,7 @@
"healthScheme": "Méthode",
"healthSelectScheme": "Sélectionnez la méthode",
"healthCheckPortInvalid": "Le port doit être compris entre 1 et 65535",
"healthCheckHostnameInvalid": "Le nom d'hôte ne doit pas contenir d'espaces blancs",
"healthCheckPath": "Chemin d'accès",
"healthHostname": "IP / Hôte",
"healthPort": "Port",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Ajouter une plage CIDR (par exemple 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Échec du chargement des sous-réseaux",
"remoteExitNodeNetworkingLabelsTitle": "Étiquettes de préférences",
"remoteExitNodeNetworkingLabelsDescription": "Les sites avec ces étiquettes devront se connecter via ce nœud de sortie distant.",
"remoteExitNodeNetworkingLabelsDescription": "Les sites dotés de ces étiquettes préféreront se connecter via ce nœud de sortie distant.",
"remoteExitNodeNetworkingLabelsButtonText": "Sélectionner des étiquettes...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Chercher des étiquettes...",
"remoteExitNodeNetworkingLabelsLoadError": "Échec du chargement des étiquettes",
@@ -3018,7 +3049,7 @@
"description": "Entrez les identifiants du noeud existant que vous souhaitez adopter",
"nodeIdLabel": "Nœud ID",
"nodeIdDescription": "L'ID du noeud existant que vous voulez adopter",
"secretLabel": "Secret",
"secretLabel": "Clé secrète",
"secretDescription": "La clé secrète du noeud existant",
"submitButton": "Noeud d'Adopt"
},
@@ -3026,7 +3057,7 @@
"title": "Informations d'identification générées",
"description": "Utilisez ces identifiants générés pour configurer le noeud",
"nodeIdTitle": "Nœud ID",
"secretTitle": "Secret",
"secretTitle": "Clé secrète",
"saveCredentialsTitle": "Ajouter des identifiants à la config",
"saveCredentialsDescription": "Ajoutez ces informations d'identification à votre fichier de configuration du nœud Pangolin auto-hébergé pour compléter la connexion.",
"submitButton": "Créer un noeud"
@@ -3155,7 +3186,7 @@
"roleMappingRemoveRule": "Supprimer",
"idpGoogleConfiguration": "Configuration Google",
"idpGoogleConfigurationDescription": "Configurer les identifiants Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Votre identifiant client Google OAuth2",
"idpGoogleClientSecretDescription": "Secret client OAuth2 de Google",
"idpAzureConfiguration": "Configuration de l'entra ID Azure",
"idpAzureConfigurationDescription": "Configurer les identifiants OAuth2 Azure Entra ID",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "ID client d'enregistrement de l'application Azure",
"idpAzureClientSecretDescription2": "Secret du client d'enregistrement de l'application Azure",
"idpGoogleDescription": "Fournisseur Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/Fournisseur OIDC",
"subnet": "Sous-réseau",
"utilitySubnet": "Routeur utilitaire",
"subnetDescription": "Le sous-réseau de la configuration réseau de cette organisation.",
@@ -3470,6 +3501,7 @@
"priority": "Priorité",
"priorityDescription": "Les routes de haute priorité sont évaluées en premier. La priorité = 100 signifie l'ordre automatique (décision du système). Utilisez un autre nombre pour imposer la priorité manuelle.",
"instanceName": "Nom de l'instance",
"clearInstanceName": "Réinitialiser l'association de serveur",
"pathMatchModalTitle": "Configurer le chemin correspondant",
"pathMatchModalDescription": "Définissez comment les requêtes entrantes doivent être trouvées en fonction de leur chemin.",
"pathMatchType": "Type de correspondance",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Autorisé par la règle",
"allowedNoAuth": "Aucune authentification autorisée",
"validAccessToken": "Jeton d'accès valide",
"validHeaderAuth": "Valid header auth",
"validHeaderAuth": "Authentification d'en-tête valide",
"validPincode": "Valid Pincode",
"validPassword": "Mot de passe valide",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Email valide",
"validSSO": "SSO valide",
"validVirtualAPIKey": "Clé API Virtuelle Valide",
"view": "Afficher",
"configManaged": "Configuration gérée",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Abandonné par la règle",
"noSessions": "Aucune session",
"temporaryRequestToken": "Jeton de requête temporaire",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Pas d'authentification valide",
"ip": "IP",
"reason": "Raison",
"requestLogs": "Journal des Requêtes HTTP",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "La régénération des identifiants invalidera les identifiants précédents et provoquera une déconnexion. Assurez-vous de mettre à jour toutes les configurations qui utilisent ces identifiants.",
"confirm": "Confirmer",
"regenerateCredentialsConfirmation": "Voulez-vous vraiment régénérer les identifiants ?",
"endpoint": "Endpoint",
"endpoint": "Point de terminaison",
"Id": "Id",
"SecretKey": "Clé privée",
"niceId": "Joli ID",
@@ -3759,6 +3791,7 @@
"noData": "Aucune donnée",
"machineClients": "Clients Machines",
"install": "Installer",
"downloadInstaller": "Télécharger l'installateur",
"run": "Exécuter",
"envFile": "Fichier Environnement",
"serviceFile": "Fichier de Service",
@@ -3883,7 +3916,7 @@
"deviceMessageArchive": "Le périphérique sera archivé et retiré de la liste des périphériques actifs.",
"deviceArchiveConfirm": "Dispositif d'archivage",
"archiveDevice": "Dispositif d'archivage",
"archive": "Archive",
"archive": "Archiver",
"deviceUnarchived": "Appareil désarchivé",
"deviceUnarchivedDescription": "L'appareil a été désarchivé avec succès.",
"errorUnarchivingDevice": "Erreur lors de la désarchivage du périphérique",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Version du noyau",
"deviceModel": "Modèle de l'appareil",
"serialNumber": "Numéro de série",
"hostname": "Hostname",
"hostname": "Nom d'hôte",
"firstSeen": "Première vue",
"lastSeen": "Dernière vue",
"biometricsEnabled": "biométrique activée",
@@ -3966,6 +3999,7 @@
"disconnected": "Déconnecté",
"approvalsEmptyStateTitle": "Approbations de l'appareil non activées",
"approvalsEmptyStateDescription": "Activer les autorisations de l'appareil pour les rôles qui nécessitent l'approbation de l'administrateur avant que les utilisateurs puissent connecter de nouveaux appareils.",
"approvalsEmptyStateHowToTitle": "Comment activer",
"approvalsEmptyStateStep1Title": "Aller aux Rôles",
"approvalsEmptyStateStep1Description": "Accédez aux paramètres de rôles de votre organisation pour configurer les autorisations de l'appareil.",
"approvalsEmptyStateStep2Title": "Activer les autorisations de l'appareil",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Voir en tant qu'admin",
"resourceLauncherResourceDetailsDescription": "Informations de connexion et statut pour cette ressource.",
"resourceLauncherResourceDetails": "Détails de la ressource",
"resourceLauncherSitesDescription": "La ressource est accessible via les sites suivants.",
"resourceLauncherViewSiteAsAdmin": "Voir le site en tant qu'admin",
"resourceLauncherFilterBySite": "Filtrer par site",
"resourceLauncherSshCommand": "Commande SSH",
"resourceLauncherSshCommandDescription": "Utilisez le CLI Pangolin pour ouvrir une session SSH vers cette ressource.",
"resourceLauncherAuthMethodsDescription": "Méthodes d'authentification activées pour cette ressource.",
"resourceLauncherPrivateClientRequired": "Connectez-vous avec un client sur votre appareil pour accéder à cette ressource en privé.",
"resourceLauncherPrivateClientRequiredTitle": "Connexion client requise",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Mode clavier Unicode",
"sessionToolbarShow": "Afficher la barre d'outils",
"sessionToolbarHide": "Masquer la barre d'outils",
"actionUpdateSiteApprovals": "Mettre à jour les approbations de site"
"actionUpdateSiteApprovals": "Mettre à jour les approbations de site",
"check": "Vérifier"
}
+92 -52
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Obiettivi su questo sito",
"siteSetting": "Impostazioni del sito {siteName}",
"siteNewtTunnel": "Nuovo Sito (Consigliato)",
"pangolinSite": "Sito Pangolin",
"siteNewtTunnelDescription": "Modo più semplice per creare un entrypoint in qualsiasi rete. Nessuna configurazione aggiuntiva.",
"siteWg": "WireGuard Base",
"siteWgDescription": "Usa un qualsiasi client WireGuard per stabilire un tunnel. Impostazione NAT manuale richiesta.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Elimina Chiave API",
"apiKeysManage": "Gestisci Chiavi API",
"apiKeysDescription": "Le chiavi API sono utilizzate per autenticarsi con l'API di integrazione",
"orgsManage": "Gestisci Organizzazioni",
"orgsDescription": "Visualizza e gestisci tutte le organizzazioni su questa istanza",
"provisioningKeysTitle": "Chiave di provisioning",
"provisioningKeysManage": "Gestisci Chiavi di provisioning",
"provisioningKeysDescription": "Le chiavi di provisioning vengono utilizzate per autenticare il provisioning automatico del sito per la tua organizzazione.",
@@ -716,11 +719,12 @@
"nameOptional": "Nome (Opzionale)",
"accessControls": "Controlli di Accesso",
"userDescription2": "Gestisci le impostazioni di questo utente",
"userGeneralSettingsDescription": "Gestisci i ruoli e le impostazioni di questo utente nell'organizzazione",
"accessRoleErrorAdd": "Impossibile aggiungere l'utente al ruolo",
"accessRoleErrorAddDescription": "Si è verificato un errore durante l'aggiunta dell'utente al ruolo.",
"userSaved": "Utente salvato",
"userSavedDescription": "L'utente è stato aggiornato.",
"autoProvisioned": "Auto Provisioned",
"autoProvisioned": "Provisioning Automatico",
"autoProvisionSettings": "Impostazioni Automatiche di provisioning",
"autoProvisionedDescription": "Permetti a questo utente di essere gestito automaticamente dal provider di identità",
"accessControlsDescription": "Gestisci cosa questo utente può accedere e fare nell'organizzazione",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Nome Server TLS non valido. Usa il formato nome dominio o salva vuoto per rimuovere il Nome Server TLS.",
"proxyEnableSSL": "Abilita TLS",
"proxyEnableSSLDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alle risorse interne target.",
"target": "Target",
"target": "Destinazione",
"configureTarget": "Configura Risorse Interne",
"targetErrorFetch": "Impossibile recuperare i target",
"targetErrorFetchDescription": "Si è verificato un errore durante il recupero dei target",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Quando le regole sono disabilitate, tutto il traffico passa all'autenticazione.",
"policyAccessRulesFallthroughOn": "Quando nessuna regola corrisponde, il traffico passa all'autenticazione.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/amministratore/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Salva Regole",
"resourceErrorCreate": "Errore nella creazione della risorsa",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Scopri di più su JMESPath",
"idpJmespathLabel": "Percorso Identificativo",
"idpJmespathLabelDescription": "Il JMESPath per l'identificatore dell'utente nel token ID",
"idpIdentifierChangeTitle": "Avviso di cambio percorso identificatore",
"idpIdentifierChangeDescription": "Stai per cambiare il percorso identificativo. Questo influirà su come gli utenti esistenti sono mappati. Gli utenti che in precedenza hanno effettuato l'accesso attraverso questo provider di identità potrebbero non essere più riconosciuti come gli stessi utenti.",
"idpIdentifierChangeConfirmMessage": "Confermo",
"idpIdentifierChangeWarningText": "Questo influirà su come gli utenti esistenti sono mappati",
"idpJmespathEmailPathOptional": "Percorso Email (Opzionale)",
"idpJmespathEmailPathOptionalDescription": "Il JMESPath per l'email dell'utente nel token ID",
"idpJmespathNamePathOptional": "Percorso Nome (Opzionale)",
@@ -1243,7 +1251,7 @@
"inviteAlready": "Sembra che sei stato invitato!",
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
"signupQuestion": "Hai già un account?",
"login": "Log In",
"login": "Accedi",
"resourceNotFound": "Risorsa Non Trovata",
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Genera Codice Di Ripristino Password",
"passwordResetCodeGenerated": "Codice Di Reimpostazione Password Generato",
"passwordResetCodeGeneratedDescription": "Condividi questo codice con l'utente. Possono usarlo per reimpostare la password.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "URL di Reimpostazione",
"passwordNew": "Nuova Password",
"passwordNewConfirm": "Conferma Nuova Password",
"changePassword": "Cambia Password",
@@ -1372,7 +1380,7 @@
"pageNotFound": "Pagina Non Trovata",
"pageNotFoundDescription": "Oops! La pagina che stai cercando non esiste.",
"overview": "Panoramica",
"home": "Home",
"home": "Pagina Iniziale",
"settings": "Impostazioni",
"usersAll": "Tutti Gli Utenti",
"license": "Licenza",
@@ -1420,6 +1428,24 @@
"logoutError": "Errore durante il logout",
"signingAs": "Accesso come",
"serverAdmin": "Amministratore Server",
"promoteServerAdmin": "Promuovi a amministratore del server",
"promoteServerAdminTitle": "Promuovi a Amministratore del Server",
"promoteServerAdminQuestion": "Sei sicuro di voler promuovere {selectedUser} a amministratore del server?",
"promoteServerAdminMessage": "Gli amministratori del server hanno i massimi privilegi e possono gestire il server.",
"promoteServerAdminWarning": "Questo può essere annullato in qualsiasi momento retrocedendo l'utente.",
"promoteServerAdminConfirm": "Promuovi a Amministratore del Server",
"promoteServerAdminSuccess": "Utente Promosso",
"promoteServerAdminSuccessDescription": "{selectedUser} è ora un amministratore del server.",
"promoteServerAdminError": "Impossibile promuovere l'utente",
"demoteServerAdmin": "Retrocedere da amministratore del server",
"demoteServerAdminTitle": "Retrocedere da Amministratore del Server",
"demoteServerAdminQuestion": "Sei sicuro di voler retrocedere {selectedUser} da amministratore del server?",
"demoteServerAdminMessage": "{selectedUser} perderà tutti i privilegi di amministratore del server.",
"demoteServerAdminWarning": "Questo può essere annullato in qualsiasi momento promuovendo l'utente.",
"demoteServerAdminConfirm": "Retrocedere da amministratore del server",
"demoteServerAdminSuccess": "Utente retrocesso",
"demoteServerAdminSuccessDescription": "{selectedUser} non è più un amministratore del server.",
"demoteServerAdminError": "Impossibile retrocedere l'utente",
"managedSelfhosted": "Gestito Auto-Ospitato",
"otpEnable": "Abilita Autenticazione a Due Fattori",
"otpDisable": "Disabilita Autenticazione a Due Fattori",
@@ -1573,6 +1599,8 @@
"search": "Cerca…",
"searchPlaceholder": "Cerca...",
"emptySearchOptions": "Nessuna opzione trovata",
"ipFilterSearchPlaceholder": "Inserisci un indirizzo IP…",
"ipFilterEmptyMessage": "Inserisci un indirizzo IP per filtrare",
"create": "Crea",
"orgs": "Organizzazioni",
"loginError": "Si è verificato un errore imprevisto. Riprova.",
@@ -1655,7 +1683,7 @@
"orgErrorNoProvided": "Nessuna organizzazione fornita",
"apiKeysErrorNoUpdate": "Nessuna chiave API da aggiornare",
"sidebarOverview": "Panoramica",
"sidebarHome": "Home",
"sidebarHome": "Pagina Iniziale",
"sidebarSites": "Siti",
"sidebarApprovals": "Richieste Di Approvazione",
"sidebarResources": "Risorse",
@@ -1676,7 +1704,7 @@
"commandAiProviders": "Provider AI",
"sidebarVirtualApiKeys": "Chiavi API Virtuali",
"sidebarMyApiKeys": "Le Tue Chiavi API",
"sidebarAccount": "Launcher",
"sidebarAccount": "Avvio",
"commandVirtualApiKeys": "Chiavi API Virtuali",
"virtualApiKeysTitle": "Gestisci Chiavi API Virtuali",
"virtualApiKeysDescription": "Crea e gestisci chiavi API manuali per l'accesso ai gateway AI pubblici",
@@ -2026,7 +2054,7 @@
"aiUsageTabOverview": "Panoramica",
"aiUsageTabProviders": "Provider",
"aiUsageTabResources": "Risorse",
"aiUsageFilterProvider": "Provider",
"aiUsageFilterProvider": "Fornitore",
"aiUsageFilterModel": "Modello",
"aiUsageFilterResource": "Risorsa",
"aiUsageFilterRole": "Ruolo",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Budget",
"resourceBudgetSettingsDescription": "Configura come questo gateway AI limita l'uso basato sulla spesa o sui limiti di token",
"sidebarApiKeys": "Chiavi API",
"sidebarOrgs": "Organizzazioni",
"sidebarProvisioning": "Accantonamento",
"sidebarSettings": "Impostazioni",
"sidebarAllUsers": "Tutti Gli Utenti",
@@ -2099,7 +2128,7 @@
"sidebarMachineClients": "Macchine",
"sidebarDomains": "Domini",
"sidebarGeneral": "Gestisci",
"sidebarLogAndAnalytics": "Log & Analytics",
"sidebarLogAndAnalytics": "Log & Analisi",
"sidebarBluePrints": "Progetti",
"sidebarAlerting": "Allerta",
"sidebarHealthChecks": "Controlli di salute",
@@ -2136,7 +2165,7 @@
"commandLogsStreaming": "Streaming di Eventi",
"commandManagement": "Gestione",
"commandAlerting": "Avvisi",
"commandProvisioning": "Provisioning",
"commandProvisioning": "Accantonamento",
"commandBluePrints": "Modelli",
"commandApiKeys": "Chiavi API",
"commandBillingAndLicenses": "Fatturazione & Licenze",
@@ -2152,7 +2181,7 @@
"alertingSearchRules": "Cerca regole…",
"alertingAddRule": "Crea Regola",
"alertingColumnSource": "Fonte",
"alertingColumnTrigger": "Trigger",
"alertingColumnTrigger": "Attivazione",
"alertingColumnActions": "Azioni",
"alertingColumnEnabled": "Abilitato",
"alertingDeleteQuestion": "Si prega di confermare di voler eliminare questa regola di allerta.",
@@ -2190,7 +2219,7 @@
"alertingSelectResources": "Seleziona risorse…",
"alertingResourcesSelected": "{count} risorse selezionate",
"alertingResourcesEmpty": "Nessuna risorsa con target nei primi 10 risultati.",
"alertingSectionTrigger": "Trigger",
"alertingSectionTrigger": "Attivazione",
"alertingTrigger": "Quando allertare",
"alertingTriggerSiteOnline": "Sito online",
"alertingTriggerSiteOffline": "Sito offline",
@@ -2264,7 +2293,7 @@
"alertingNodeNotConfigured": "Non ancora configurato",
"alertingNodeActionsCount": "{count, plural, one {# azione} other {# azioni}}",
"alertingNodeRoleSource": "Fonte",
"alertingNodeRoleTrigger": "Trigger",
"alertingNodeRoleTrigger": "Attivazione",
"alertingNodeRoleAction": "Azione",
"alertingTabRules": "Regole di Allerta",
"alertingTabHealthChecks": "Controlli di Salute",
@@ -2292,7 +2321,7 @@
"standaloneHcSaved": "Controllo di salute salvato",
"standaloneHcColumnHealth": "Salute",
"standaloneHcColumnMode": "Modalità",
"standaloneHcColumnTarget": "Target",
"standaloneHcColumnTarget": "Obiettivo",
"standaloneHcHealthStateHealthy": "Sano",
"standaloneHcHealthStateUnhealthy": "Non Sano",
"standaloneHcHealthStateUnknown": "Sconosciuto",
@@ -2347,7 +2376,7 @@
"containerImage": "Immagine",
"containerState": "Stato",
"containerNetworks": "Reti",
"containerHostnameIp": "Hostname/IP",
"containerHostnameIp": "Nome host/IP",
"containerLabels": "Etichette",
"containerLabelsCount": "{count, plural, one {# etichetta} other {# etichette}}",
"containerLabelsTitle": "Etichette Del Contenitore",
@@ -2418,7 +2447,7 @@
"billing": "Fatturazione",
"orgBillingDescription": "Gestisci le informazioni di fatturazione e gli abbonamenti",
"github": "GitHub",
"pangolinHosted": "Pangolin Hosted",
"pangolinHosted": "Ospitato su Pangolin",
"fossorial": "Fossoriale",
"completeAccountSetup": "Completa la Configurazione dell'Account",
"completeAccountSetupDescription": "Imposta la tua password per iniziare",
@@ -2596,9 +2625,10 @@
"createDomainType": "Tipo:",
"createDomainName": "Nome:",
"createDomainValue": "Valore:",
"multiSelectFilterCount": "{count} selezionato",
"createDomainCnameRecords": "Record CNAME",
"createDomainARecords": "Record A",
"createDomainRecordNumber": "Record {number}",
"createDomainRecordNumber": "Record {numero}",
"createDomainTxtRecords": "Record TXT",
"createDomainSaveTheseRecords": "Salva Questi Record",
"createDomainSaveTheseRecordsDescription": "Assicurati di salvare questi record DNS poiché non li vedrai più.",
@@ -2710,6 +2740,7 @@
"healthScheme": "Metodo",
"healthSelectScheme": "Seleziona Metodo",
"healthCheckPortInvalid": "La porta deve essere compresa tra 1 e 65535",
"healthCheckHostnameInvalid": "Il nome dell'host non deve contenere spazi",
"healthCheckPath": "Percorso",
"healthHostname": "IP / Nome host",
"healthPort": "Porta",
@@ -2722,7 +2753,7 @@
"requireDeviceApproval": "Richiede Approvazioni Dispositivo",
"requireDeviceApprovalDescription": "Gli utenti con questo ruolo hanno bisogno di nuovi dispositivi approvati da un amministratore prima di poter connettersi e accedere alle risorse.",
"sshSettings": "SSH",
"inferenceSettings": "AI Gateway",
"inferenceSettings": "Gateway AI",
"sshAccess": "Accesso SSH",
"rdpSettings": "RDP",
"vncSettings": "VNC",
@@ -2734,7 +2765,7 @@
"vncServerDescription": "Configura la destinazione e la porta del server VNC",
"sshServerMode": "Modalità",
"sshServerModeStandard": "Server SSH Standard",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModePangolin": "SSH di Pangolin",
"sshServerModeStandardDescription": "Instrada comandi sulla rete a un server SSH come OpenSSH.",
"sshServerModeNative": "Server SSH Nativo",
"sshServerModeNativeDescription": "Esegue comandi direttamente sull'host tramite il connettore del sito. Non è richiesta la configurazione di rete.",
@@ -2846,7 +2877,7 @@
"resourcesTableClients": "Client",
"resourcesTableAndOnlyAccessibleInternally": "e sono accessibili solo internamente quando connessi con un client.",
"resourcesTableHealthy": "Sano",
"resourcesTableDegraded": "Degraded",
"resourcesTableDegraded": "Degradato",
"resourcesTableUnhealthy": "Non Sano",
"resourcesTableUnknown": "Sconosciuto",
"resourcesTableNotMonitored": "Non monitorato",
@@ -2878,7 +2909,7 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeInference": "AI Gateway",
"editInternalResourceDialogModeInference": "Gateway AI",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Metodo HTTP",
"editInternalResourceDialogEnableSsl": "Abilita TLS",
@@ -2938,7 +2969,7 @@
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"createInternalResourceDialogModeInference": "AI Gateway",
"createInternalResourceDialogModeInference": "Gateway AI",
"scheme": "Metodo HTTP",
"createInternalResourceDialogScheme": "Metodo HTTP",
"createInternalResourceDialogEnableSsl": "Abilita TLS",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Aggiungi un intervallo CIDR (ad esempio 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Caricamento sottoreti fallito",
"remoteExitNodeNetworkingLabelsTitle": "Etichette Preferenze",
"remoteExitNodeNetworkingLabelsDescription": "I siti con queste etichette saranno collegati attraverso questo nodo di uscita remoto.",
"remoteExitNodeNetworkingLabelsDescription": "I siti con queste etichette preferiranno connettersi tramite questo nodo di uscita remoto.",
"remoteExitNodeNetworkingLabelsButtonText": "Seleziona etichette...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Cerca etichette...",
"remoteExitNodeNetworkingLabelsLoadError": "Caricamento etichette fallito",
@@ -3085,11 +3116,11 @@
"regionAustraliaAndNewZealand": "Australia e Nuova Zelanda",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"regionPolynesia": "Polinesia",
"managedSelfHosted": {
"title": "Gestito Auto-Ospitato",
"description": "Server Pangolin self-hosted più affidabile e a bassa manutenzione con campanelli e fischietti extra",
"introTitle": "Managed Self-Hosted Pangolin",
"introTitle": "Gestito Auto-Ospitato",
"introDescription": "è un'opzione di distribuzione progettata per le persone che vogliono la semplicità e l'affidabilità extra mantenendo i loro dati privati e self-hosted.",
"introDetail": "Con questa opzione, esegui ancora il tuo nodo Pangolin - i tunnel, la terminazione TLS e il traffico rimangono tutti sul tuo server. La differenza è che la gestione e il monitoraggio sono gestiti attraverso il nostro cruscotto cloud, che sblocca una serie di vantaggi:",
"benefitSimplerOperations": {
@@ -3135,7 +3166,7 @@
"idpTypeLabel": "Tipo Provider Identità",
"roleMappingExpressionPlaceholder": "es. contiene(gruppi, 'admin') && 'Admin' <unk> <unk> 'Membro'",
"roleMappingModeFixedRoles": "Ruoli Fissi",
"roleMappingModeMappingBuilder": "Mapping Builder",
"roleMappingModeMappingBuilder": "Costruttore di Mappature",
"roleMappingModeRawExpression": "Espressione Raw",
"roleMappingFixedRolesPlaceholderSelect": "Seleziona uno o più ruoli",
"roleMappingFixedRolesPlaceholderFreeform": "Digita nomi dei ruoli (corrispondenza esatta per organizzazione)",
@@ -3155,8 +3186,8 @@
"roleMappingRemoveRule": "Rimuovi",
"idpGoogleConfiguration": "Configurazione Google",
"idpGoogleConfigurationDescription": "Configura le credenziali di Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientSecretDescription": "Google OAuth2 Client Secret",
"idpGoogleClientIdDescription": "Il Tuo Client Id Google OAuth2",
"idpGoogleClientSecretDescription": "Il Tuo Client Google OAuth2 Secret",
"idpAzureConfiguration": "Configurazione Azure Entra ID",
"idpAzureConfigurationDescription": "Configura le credenziali OAuth2 di Azure Entra ID",
"idpTenantId": "ID Tenant",
@@ -3173,8 +3204,8 @@
"idpTenantIdLabel": "ID Tenant",
"idpAzureClientIdDescription2": "Azure App Id Registrazione Client",
"idpAzureClientSecretDescription2": "Azure App Registrazione Client Segreto",
"idpGoogleDescription": "Google OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpGoogleDescription": "Provider OAuth2/OIDC generico",
"idpAzureDescription": "provider Microsoft Azure OAuth2/OIDC",
"subnet": "Sottorete",
"utilitySubnet": "Sottorete di utilità",
"subnetDescription": "La sottorete per la configurazione di rete di questa organizzazione.",
@@ -3247,7 +3278,7 @@
"mustUpgradeToUse": "Devi aggiornare il tuo abbonamento per utilizzare questa funzionalità.",
"subscriptionRequiredTierToUse": "Questa funzione richiede <tierLink>{tier}</tierLink> o superiore.",
"upgradeToTierToUse": "Aggiorna ad <tierLink>{tier}</tierLink> o superiore per utilizzare questa funzionalità.",
"subscriptionTierTier1": "Home",
"subscriptionTierTier1": "Casa",
"subscriptionTierTier2": "Squadra",
"subscriptionTierTier3": "Business",
"subscriptionTierEnterprise": "Impresa",
@@ -3329,7 +3360,7 @@
"resourceHeaderAuthSetupTitleDescription": "Imposta le credenziali di autenticazione di base (nome utente e password) per proteggere questa risorsa con Autenticazione intestazione HTTP. Accedi usando il formato https://username:password@resource.example.com",
"resourceHeaderAuthSubmit": "Imposta Autenticazione Intestazione",
"actionSetResourceHeaderAuth": "Imposta Autenticazione Intestazione",
"enterpriseEdition": "Enterprise Edition",
"enterpriseEdition": "Edizione Enterprise",
"unlicensed": "Senza Licenza",
"beta": "Beta",
"manageUserDevices": "Dispositivi Utente",
@@ -3470,6 +3501,7 @@
"priority": "Priorità",
"priorityDescription": "I percorsi prioritari più alti sono valutati prima. Priorità = 100 significa ordinamento automatico (decidi di sistema). Usa un altro numero per applicare la priorità manuale.",
"instanceName": "Nome Istanza",
"clearInstanceName": "Reimposta Associazione Server",
"pathMatchModalTitle": "Configura Corrispondenza Percorso",
"pathMatchModalDescription": "Impostare come le richieste in arrivo devono essere abbinate in base al loro percorso.",
"pathMatchType": "Tipo di Corrispondenza",
@@ -3480,7 +3512,7 @@
"clear": "Pulisci",
"saveChanges": "Salva Modifiche",
"pathMatchRegexPlaceholder": "^/api/.*",
"pathMatchDefaultPlaceholder": "/path",
"pathMatchDefaultPlaceholder": "/percorso",
"pathMatchPrefixHelp": "Esempio: /api corrisponde /api, /api/users etc.",
"pathMatchExactHelp": "Esempio: /api corrisponde solo /api",
"pathMatchRegexHelp": "Esempio: ^/api/.* corrisponde /api/anything",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Consentito dalla regola",
"allowedNoAuth": "Non Consentito Auth",
"validAccessToken": "Token Di Accesso Valido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Autenticazione header valida",
"validPincode": "Codice PIN valido",
"validPassword": "Password Valida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Email valida",
"validSSO": "SSO valido",
"validVirtualAPIKey": "Chiave API Virtuale Valida",
"view": "Visualizza",
"configManaged": "Gestione Configurazione",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Eliminato dalla regola",
"noSessions": "Nessuna Sessione",
"temporaryRequestToken": "Token Di Richiesta Temporaneo",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Nessuna Autenticazione Valida",
"ip": "IP",
"reason": "Motivo",
"requestLogs": "Log Richieste HTTP",
@@ -3561,7 +3593,7 @@
"commandLogsAi": "Log delle Sessioni",
"sidebarLogsAiUsage": "Analytics sull'Uso",
"commandLogsAiUsage": "Analytics sull'Uso",
"provider": "Provider",
"provider": "Fornitore",
"capability": "Capacità",
"model": "Modello",
"virtualApiKey": "Chiave API Virtuale",
@@ -3637,7 +3669,7 @@
"loadingDNSRecords": "Caricamento record DNS...",
"olmUpdateAvailableInfo": "È disponibile una versione aggiornata di Olm. Si prega di aggiornare all'ultima versione per la migliore esperienza.",
"updateAvailableInfo": "È disponibile una versione aggiornata. Si prega di aggiornare all'ultima versione per la migliore esperienza.",
"client": "Client",
"client": "Cliente",
"proxyProtocol": "Impostazioni Protocollo Proxy",
"proxyProtocolDescription": "Configurare il protocollo proxy per preservare gli indirizzi IP client per i servizi TCP.",
"enableProxyProtocol": "Abilita Protocollo Proxy",
@@ -3694,7 +3726,7 @@
"deviceAuthorize": "Autorizza {applicationName}",
"deviceConnected": "Dispositivo Connesso!",
"deviceAuthorizedMessage": "Il dispositivo è autorizzato ad accedere al tuo account. Ritorna all'applicazione client.",
"pangolinCloud": "Pangolin Cloud",
"pangolinCloud": "Cloud di Pangolin",
"viewDevices": "Visualizza Dispositivi",
"viewDevicesDescription": "Gestisci i tuoi dispositivi connessi",
"noDevices": "Nessun dispositivo trovato",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Si è verificato un errore durante l'aggiornamento del Nice ID.",
"niceIdCannotBeEmpty": "Il Nice ID non può essere vuoto",
"enterIdentifier": "Inserisci identificatore",
"identifier": "Identifier",
"identifier": "Identificatore",
"deviceLoginUseDifferentAccount": "Non tu? Usa un account diverso.",
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo sta richiedendo l'accesso a questo account.",
"loginSelectAuthenticationMethod": "Selezionare un metodo di autenticazione per continuare.",
"noData": "Nessun Dato",
"machineClients": "Machine Clients",
"machineClients": "Client Macchina",
"install": "Installa",
"downloadInstaller": "Scarica Installatore",
"run": "Esegui",
"envFile": "File di ambiente",
"serviceFile": "File di servizio",
@@ -3922,7 +3955,7 @@
"signupOrgTip": "Stai cercando di accedere tramite il provider di identità della tua organizzazione?",
"signupOrgLink": "Accedi o registrati con la tua organizzazione",
"verifyEmailLogInWithDifferentAccount": "Usa un account diverso",
"logIn": "Log In",
"logIn": "Accedi",
"deviceInformation": "Informazioni Sul Dispositivo",
"deviceInformationDescription": "Informazioni sul dispositivo e sull'agente",
"deviceSecurity": "Sicurezza Del Dispositivo",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Versione Del Kernel",
"deviceModel": "Modello Di Dispositivo",
"serialNumber": "Numero D'Ordine",
"hostname": "Hostname",
"hostname": "Nome host",
"firstSeen": "Prima Visto",
"lastSeen": "Visto L'Ultima",
"biometricsEnabled": "Biometria Abilitata",
@@ -3966,6 +3999,7 @@
"disconnected": "Disconnesso",
"approvalsEmptyStateTitle": "Approvazioni Dispositivo Non Abilitato",
"approvalsEmptyStateDescription": "Abilita le approvazioni del dispositivo per i ruoli per richiedere l'approvazione dell'amministratore prima che gli utenti possano collegare nuovi dispositivi.",
"approvalsEmptyStateHowToTitle": "Come Abilitare",
"approvalsEmptyStateStep1Title": "Vai ai ruoli",
"approvalsEmptyStateStep1Description": "Vai alle impostazioni dei ruoli della tua organizzazione per configurare le approvazioni del dispositivo.",
"approvalsEmptyStateStep2Title": "Abilita Approvazioni Dispositivo",
@@ -4053,7 +4087,7 @@
"httpDestAuthBearerPlaceholder": "La tua chiave API o token",
"httpDestAuthBasicTitle": "Autenticazione Base",
"httpDestAuthBasicDescription": "Aggiunge un'intestazione Authorization: Basic '<credentials>'. Fornire le credenziali come username:password.",
"httpDestAuthBasicPlaceholder": "username:password",
"httpDestAuthBasicPlaceholder": "nomeutente:password",
"httpDestAuthCustomTitle": "Intestazione Personalizzata",
"httpDestAuthCustomDescription": "Specifica un nome e un valore di intestazione HTTP personalizzati per l'autenticazione (ad esempio X-API-Key).",
"httpDestAuthCustomHeaderNamePlaceholder": "Nome intestazione (es. X-API-Key)",
@@ -4071,7 +4105,7 @@
"httpDestBodyTemplateHint": "Usa le variabili del modello per fare riferimento ai campi dell'evento nel tuo payload.",
"httpDestPayloadFormatTitle": "Formato Payload",
"httpDestPayloadFormatDescription": "Come gli eventi sono serializzati in ogni organismo di richiesta.",
"httpDestFormatJsonArrayTitle": "JSON Array",
"httpDestFormatJsonArrayTitle": "Array JSON",
"httpDestFormatJsonArrayDescription": "Una richiesta per lotto, corpo è un array JSON. Compatibile con la maggior parte dei webhooks generici e Datadog.",
"httpDestFormatNdjsonTitle": "NDJSON",
"httpDestFormatNdjsonDescription": "Una richiesta per lotto, corpo è newline-delimited JSON - un oggetto per linea, nessun array esterno. Richiesto da Splunk HEC, Elastic / OpenSearch, e Grafana Loki.",
@@ -4106,7 +4140,7 @@
"healthCheckTabConnection": "Connessione",
"healthCheckTabAdvanced": "Avanzato",
"healthCheckStrategyNotAvailable": "Questa strategia non è disponibile. Contatta le vendite per abilitare questa funzionalità.",
"uptime30d": "Uptime (30d)",
"uptime30d": "Uptime (30g)",
"idpAddActionCreateNew": "Crea nuovo provider di identità",
"idpAddActionImportFromOrg": "Importa da un'altra organizzazione",
"idpImportDialogTitle": "Importa Provider di Identità",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Visualizza come Admin",
"resourceLauncherResourceDetailsDescription": "Informazioni e stato della connessione per questa risorsa.",
"resourceLauncherResourceDetails": "Dettagli Risorsa",
"resourceLauncherSitesDescription": "La risorsa è accessibile tramite i seguenti siti.",
"resourceLauncherViewSiteAsAdmin": "Visualizza Sito come Admin",
"resourceLauncherFilterBySite": "Filtra per Sito",
"resourceLauncherSshCommand": "Comando SSH",
"resourceLauncherSshCommandDescription": "Usa il CLI di Pangolin per aprire una sessione SSH a questa risorsa.",
"resourceLauncherAuthMethodsDescription": "Metodi di autenticazione abilitati per questa risorsa.",
"resourceLauncherPrivateClientRequired": "Connettiti con un client sul tuo dispositivo per accedere a questa risorsa privatamente.",
"resourceLauncherPrivateClientRequiredTitle": "Connessione Client Richiesta",
@@ -4254,7 +4293,7 @@
"resourceLauncherTcp": "TCP",
"resourceLauncherUdp": "UDP",
"resourceLauncherUnlabeled": "Non Etichettato",
"resourceLauncherAiGateway": "AI Gateway",
"resourceLauncherAiGateway": "Gateway AI",
"resourceLauncherNoSite": "Nessun Sito",
"resourceLauncherAvailableModels": "Modelli Disponibili",
"resourceLauncherAvailableModelsDescription": "Modelli che puoi usare con questo gateway AI.",
@@ -4322,7 +4361,7 @@
"sshErrorSignKeyFailed": "Impossibile firmare la chiave SSH per l'autenticazione push PAM. Ti sei autenticato come utente?",
"sshTerminalError": "Errore: {error}",
"sshConnectionClosedCode": "Connessione chiusa (codice {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyPlaceholder": "-----INIZIO CHIAVE PRIVATA OPENSSH-----",
"sshPrivateKeyRequired": "È richiesta una chiave privata",
"vncTitle": "VNC",
"vncSignInDescription": "Inserisci le tue credenziali VNC per connetterti",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Modalità tastiera Unicode",
"sessionToolbarShow": "Mostra barra degli strumenti",
"sessionToolbarHide": "Nascondi barra degli strumenti",
"actionUpdateSiteApprovals": "Aggiorna Approvazioni del Sito"
"actionUpdateSiteApprovals": "Aggiorna Approvazioni del Sito",
"check": "Controlla"
}
+48 -8
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "이 사이트의 대상",
"siteSetting": "{siteName} 설정",
"siteNewtTunnel": "뉴트 사이트 (추천)",
"pangolinSite": "판골린 사이트",
"siteNewtTunnelDescription": "네트워크의 진입점을 생성하는 가장 쉬운 방법입니다. 추가 설정이 필요 없습니다.",
"siteWg": "기본 WireGuard",
"siteWgDescription": "모든 WireGuard 클라이언트를 사용하여 터널을 설정하세요. 수동 NAT 설정이 필요합니다.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "API 키 삭제",
"apiKeysManage": "API 키 관리",
"apiKeysDescription": "API 키는 통합 API와 인증하는 데 사용됩니다.",
"orgsManage": "조직 관리",
"orgsDescription": "이 인스턴스에서 모든 조직을 보고 관리합니다",
"provisioningKeysTitle": "프로비저닝 키",
"provisioningKeysManage": "프로비저닝 키 관리",
"provisioningKeysDescription": "프로비저닝 키는 조직의 자동 사이트 프로비저닝 인증에 사용됩니다.",
@@ -716,6 +719,7 @@
"nameOptional": "이름 (선택 사항)",
"accessControls": "접근 제어",
"userDescription2": "이 사용자의 설정 관리",
"userGeneralSettingsDescription": "조직 내에서 이 사용자의 역할 및 설정을 관리하세요",
"accessRoleErrorAdd": "사용자를 역할에 추가하는 데 실패했습니다.",
"accessRoleErrorAddDescription": "사용자를 역할에 추가하는 동안 오류가 발생했습니다.",
"userSaved": "사용자 저장됨",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "규칙이 비활성화되면, 모든 트래픽은 인증으로 넘어갑니다.",
"policyAccessRulesFallthroughOn": "매칭되는 규칙이 없으면, 트래픽은 인증으로 넘어갑니다.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/관리자/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "규칙 저장",
"resourceErrorCreate": "리소스 생성 오류",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "JMESPath에 대해 더 알아보기",
"idpJmespathLabel": "식별자 경로",
"idpJmespathLabelDescription": "ID 토큰에서 사용자 식별자에 대한 경로",
"idpIdentifierChangeTitle": "식별자 경로 변경 경고",
"idpIdentifierChangeDescription": "식별자 경로를 변경하려고 합니다. 이는 기존 사용자의 매핑 방법에 영향을 미칩니다. 이전에 이 ID 공급자를 통해 로그인한 사용자는 더 이상 동일한 사용자로 인식되지 않을 수 있습니다. ",
"idpIdentifierChangeConfirmMessage": "확인합니다",
"idpIdentifierChangeWarningText": "이는 기존 사용자의 매핑 방법에 영향을 미칩니다",
"idpJmespathEmailPathOptional": "이메일 경로 (선택 사항)",
"idpJmespathEmailPathOptionalDescription": "ID 토큰에서 사용자의 이메일 경로",
"idpJmespathNamePathOptional": "이름 경로 (선택 사항)",
@@ -1420,6 +1428,24 @@
"logoutError": "로그아웃 중 오류 발생",
"signingAs": "로그인한 사용자",
"serverAdmin": "서버 관리자",
"promoteServerAdmin": "서버 관리자 승격",
"promoteServerAdminTitle": "서버 관리자 승격",
"promoteServerAdminQuestion": "{selectedUser}을 서버 관리자로 승격하시겠습니까?",
"promoteServerAdminMessage": "서버 관리자는 최고 권한을 갖고 서버를 관리할 수 있습니다.",
"promoteServerAdminWarning": "언제든지 사용자를 강등하여 이 작업을 되돌릴 수 있습니다.",
"promoteServerAdminConfirm": "서버 관리자 승격",
"promoteServerAdminSuccess": "사용자가 승격되었습니다",
"promoteServerAdminSuccessDescription": "{selectedUser}님이 이제 서버 관리자가 되었습니다.",
"promoteServerAdminError": "사용자 승격에 실패했습니다",
"demoteServerAdmin": "서버 관리자에서 강등",
"demoteServerAdminTitle": "서버 관리자에서 강등",
"demoteServerAdminQuestion": "{selectedUser}을 서버 관리자에서 강등하시겠습니까?",
"demoteServerAdminMessage": "{selectedUser}은 모든 서버 관리자 권한을 잃게 됩니다",
"demoteServerAdminWarning": "언제든지 사용자를 승격하여 이 작업을 되돌릴 수 있습니다.",
"demoteServerAdminConfirm": "서버 관리자에서 강등",
"demoteServerAdminSuccess": "사용자가 강등되었습니다",
"demoteServerAdminSuccessDescription": "{selectedUser}님이 더 이상 서버 관리자가 아닙니다.",
"demoteServerAdminError": "사용자 강등에 실패했습니다",
"managedSelfhosted": "관리 자체 호스팅",
"otpEnable": "이중 인증 활성화",
"otpDisable": "이중 인증 비활성화",
@@ -1573,6 +1599,8 @@
"search": "검색…",
"searchPlaceholder": "검색...",
"emptySearchOptions": "옵션이 없습니다",
"ipFilterSearchPlaceholder": "IP 주소를 입력하세요…",
"ipFilterEmptyMessage": "필터링할 IP 주소를 입력하세요",
"create": "생성",
"orgs": "조직",
"loginError": "예기치 않은 오류가 발생했습니다. 다시 시도해주세요.",
@@ -1846,8 +1874,8 @@
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Vercel AI Gateway",
"aiProviderTypeCustom": "Custom",
"aiProviderTypeVercelAiGateway": "Vercel AI 게이트웨이",
"aiProviderTypeCustom": "사용자 정의",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
"aiProviderTypeGoogleGeminiDescription": "Google Gemini API",
@@ -1855,7 +1883,7 @@
"aiProviderTypeBedrockDescription": "Amazon Bedrock Runtime API",
"aiProviderTypeMicrosoftFoundryDescription": "Microsoft Foundry API",
"aiProviderTypeOpenRouterDescription": "OpenRouter API",
"aiProviderTypeVercelAiGatewayDescription": "Vercel AI Gateway API",
"aiProviderTypeVercelAiGatewayDescription": "Vercel AI 게이트웨이 API",
"aiProviderTypeCustomDescription": "자체 엔드포인트를 가져오거나 사이트 타겟을 통해 라우트하십시오",
"aiProviderUpstreamUrl": "상류 URL",
"aiProviderUpstreamUrlDescription": "공급자 API의 기본 URL",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "예산",
"resourceBudgetSettingsDescription": "이 AI 게이트웨이가 지출 또는 토큰 제한에 따라 사용을 제한하는 방법을 구성하십시오",
"sidebarApiKeys": "API 키",
"sidebarOrgs": "조직",
"sidebarProvisioning": "프로비저닝",
"sidebarSettings": "설정",
"sidebarAllUsers": "모든 사용자",
@@ -2596,6 +2625,7 @@
"createDomainType": "유형:",
"createDomainName": "이름:",
"createDomainValue": "값:",
"multiSelectFilterCount": "{count} 선택됨",
"createDomainCnameRecords": "CNAME 레코드",
"createDomainARecords": "A 레코드",
"createDomainRecordNumber": "레코드 {number}",
@@ -2710,6 +2740,7 @@
"healthScheme": "방법",
"healthSelectScheme": "방법 선택",
"healthCheckPortInvalid": "포트는 1에서 65535 사이여야 합니다",
"healthCheckHostnameInvalid": "호스트 이름에는 공백이 포함될 수 없습니다",
"healthCheckPath": "경로",
"healthHostname": "IP / 호스트",
"healthPort": "포트",
@@ -2734,7 +2765,7 @@
"vncServerDescription": "VNC 서버의 목적지 및 포트를 구성합니다",
"sshServerMode": "모드",
"sshServerModeStandard": "표준 SSH 서버",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModePangolin": "판골린 SSH",
"sshServerModeStandardDescription": "네트워크를 통해 OpenSSH와 같은 SSH 서버로 명령을 전달합니다.",
"sshServerModeNative": "네이티브 SSH 서버",
"sshServerModeNativeDescription": "사이트 커넥터를 통해 호스트에서 직접 명령을 실행합니다. 네트워크 구성이 필요 없습니다.",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "CIDR 범위 추가 (예: 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "서브넷 로드 실패",
"remoteExitNodeNetworkingLabelsTitle": "우선순위 레이블",
"remoteExitNodeNetworkingLabelsDescription": "이 레이블이 있는 사이트는 이 원격 출구 노드를 통해 연결니다.",
"remoteExitNodeNetworkingLabelsDescription": "이 레이블이 있는 사이트는 이 원격 종료 노드를 통해 연결하는 것을 선호합니다.",
"remoteExitNodeNetworkingLabelsButtonText": "레이블 선택...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "레이블 검색...",
"remoteExitNodeNetworkingLabelsLoadError": "레이블 로드 실패",
@@ -3340,7 +3371,7 @@
"manageMachineClientsDescription": "서버와 시스템이 리소스에 개인적으로 연결하는 데 사용하는 클라이언트를 생성하고 관리하십시오",
"machineClientsBannerTitle": "서버 및 자동 시스템",
"machineClientsBannerDescription": "머신 클라이언트는 특정 사용자와 연결되지 않은 서버 및 자동화된 시스템을 위한 것입니다. 이들은 ID와 비밀을 통해 인증하며, Pangolin CLI, Olm CLI, 또는 Olm 컨테이너로 실행될 수 있습니다.",
"machineClientsBannerPangolinCLI": "Pangolin CLI",
"machineClientsBannerPangolinCLI": "판골린 CLI",
"machineClientsBannerOlmCLI": "Olm CLI",
"machineClientsBannerOlmContainer": "Olm 컨테이너",
"clientsTableUserClients": "사용자",
@@ -3470,6 +3501,7 @@
"priority": "우선순위",
"priorityDescription": "우선 순위가 높은 경로가 먼저 평가됩니다. 우선 순위 = 100은 자동 정렬(시스템 결정)이 의미합니다. 수동 우선 순위를 적용하려면 다른 숫자를 사용하세요.",
"instanceName": "인스턴스 이름",
"clearInstanceName": "서버 연결 해제",
"pathMatchModalTitle": "경로 매칭 설정",
"pathMatchModalDescription": "경로별로 들어오는 요청을 어떻게 매칭할지 설정합니다.",
"pathMatchType": "일치 유형",
@@ -3759,6 +3791,7 @@
"noData": "데이터 없음",
"machineClients": "기계 클라이언트",
"install": "설치",
"downloadInstaller": "설치 프로그램 다운로드",
"run": "실행",
"envFile": "환경 파일",
"serviceFile": "서비스 파일",
@@ -3966,6 +3999,7 @@
"disconnected": "연결 해제됨",
"approvalsEmptyStateTitle": "장치 승인 비활성화됨",
"approvalsEmptyStateDescription": "사용자가 새 장치를 연결하기 전에 관리자의 승인을 필요로 하도록 역할에 대해 장치 승인을 활성화하세요.",
"approvalsEmptyStateHowToTitle": "활성화 방법",
"approvalsEmptyStateStep1Title": "역할로 이동",
"approvalsEmptyStateStep1Description": "조직의 역할 설정으로 이동하여 장치 승인을 구성하십시오.",
"approvalsEmptyStateStep2Title": "장치 승인 활성화",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "관리자로 보기",
"resourceLauncherResourceDetailsDescription": "이 리소스에 대한 연결 정보 및 상태입니다.",
"resourceLauncherResourceDetails": "리소스 세부 정보",
"resourceLauncherSitesDescription": "리소스는 다음 사이트를 통해 액세스할 수 있습니다.",
"resourceLauncherViewSiteAsAdmin": "관리자로 사이트 보기",
"resourceLauncherFilterBySite": "사이트별 필터",
"resourceLauncherSshCommand": "SSH 명령",
"resourceLauncherSshCommandDescription": "Pangolin CLI를 사용하여 이 리소스에 대한 SSH 세션을 엽니다.",
"resourceLauncherAuthMethodsDescription": "이 리소스에 활성화된 인증 방법입니다.",
"resourceLauncherPrivateClientRequired": "이 리소스에 개인적으로 접근하려면 기기에서 클라이언트로 연결하십시오.",
"resourceLauncherPrivateClientRequiredTitle": "클라이언트 연결 필요",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "유니코드 키보드 모드",
"sessionToolbarShow": "툴바 보기",
"sessionToolbarHide": "툴바 숨기기",
"actionUpdateSiteApprovals": "사이트 승인 업데이트"
"actionUpdateSiteApprovals": "사이트 승인 업데이트",
"check": "확인"
}
+59 -19
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Mål på dette nettstedet",
"siteSetting": "{siteName} Innstillinger",
"siteNewtTunnel": "Nyhetsnettsted (anbefalt)",
"pangolinSite": "Pangolin Site",
"siteNewtTunnelDescription": "Lekkeste måte å lage et inngangspunkt til ethvert nettverk. Ingen ekstra oppsett på.",
"siteWg": "Grunnleggende WireGuard",
"siteWgDescription": "Bruk hvilken som helst WireGuard-klient for å etablere en tunnel. Manuell NAT-oppsett kreves.",
@@ -184,7 +185,7 @@
"accessToken": "Tilgangsnøkkel",
"usageExamples": "Brukseksempler",
"tokenId": "Token-ID",
"requestHeades": "Request Headers",
"requestHeades": "Forespørselshoder",
"queryParameter": "Forespørsel Params",
"importantNote": "Viktig merknad",
"shareImportantDescription": "Av sikkerhetsgrunner anbefales det å bruke headere fremfor query parametere der det er mulig, da query parametere kan logges i serverlogger eller nettleserhistorikk.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Slett API-nøkkel",
"apiKeysManage": "Administrer API-nøkler",
"apiKeysDescription": "API-nøkler brukes for å autentisere med integrasjons-API",
"orgsManage": "Administrer organisasjoner",
"orgsDescription": "Vis og administrer alle organisasjoner på denne instansen",
"provisioningKeysTitle": "Foreløpig nøkkel",
"provisioningKeysManage": "Behandle bestemmende nøkler",
"provisioningKeysDescription": "Bestemmelsesnøkler brukes til å godkjenne automatisert nettstedsløsning for din organisasjon.",
@@ -716,6 +719,7 @@
"nameOptional": "Navn (valgfritt)",
"accessControls": "Tilgangskontroller",
"userDescription2": "Administrer innstillingene for denne brukeren",
"userGeneralSettingsDescription": "Administrer denne brukerens roller og innstillinger i organisasjonen",
"accessRoleErrorAdd": "Kunne ikke legge til bruker i rolle",
"accessRoleErrorAddDescription": "Det oppstod en feil under tilordning av brukeren til rollen.",
"userSaved": "Bruker lagret",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Ugyldig TLS-servernavn. Bruk domenenavnformat, eller la stå tomt for å fjerne TLS-servernavnet.",
"proxyEnableSSL": "Aktiver TLS",
"proxyEnableSSLDescription": "Aktivere SSL/TLS-kryptering for sikker HTTPS tilkobling til målene.",
"target": "Target",
"target": "Mål",
"configureTarget": "Konfigurer mål",
"targetErrorFetch": "Kunne ikke hente mål",
"targetErrorFetchDescription": "Det oppsto en feil under henting av mål",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Når regler er deaktivert, går all trafikk gjennom til autentisering.",
"policyAccessRulesFallthroughOn": "Når ingen regler samsvarer, fortsetter trafikken til autentisering.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/administrator/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Lagre Regler",
"resourceErrorCreate": "Feil under oppretting av ressurs",
@@ -948,7 +952,7 @@
"unknownCommand": "Ukjent kommando",
"newtErrorFetchReleases": "Feilet å hente utgivelsesinfo: {err}",
"newtErrorFetchLatest": "Feil ved henting av siste utgivelse: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Endepunkt",
"newtId": "ID",
"newtSecretKey": "Sikkerhetsnøkkel",
"newtVersion": "Versjon",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Lær mer om JMESPath",
"idpJmespathLabel": "Identifikatorsti",
"idpJmespathLabelDescription": "Stien til brukeridentifikatoren i ID-tokenet",
"idpIdentifierChangeTitle": "Advarsel om identifikatorbanendring",
"idpIdentifierChangeDescription": "Du er i ferd med å endre identifikatorbanen. Dette vil påvirke hvordan eksisterende brukere kartlegges. Brukere som tidligere logget inn gjennom denne identitetsleverandøren kan ikke lenger bli gjenkjent som de samme brukerne.",
"idpIdentifierChangeConfirmMessage": "Jeg bekrefter",
"idpIdentifierChangeWarningText": "Dette vil påvirke hvordan eksisterende brukere kartlegges",
"idpJmespathEmailPathOptional": "E-poststi (Valgfritt)",
"idpJmespathEmailPathOptionalDescription": "Stien til brukerens e-postadresse i ID-tokenet",
"idpJmespathNamePathOptional": "Navn Sti (Valgfritt)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Lag tilbakestillingskode for passord",
"passwordResetCodeGenerated": "Passord tilbakestillingskoden er generert",
"passwordResetCodeGeneratedDescription": "Del denne koden med brukeren. De kan bruke den til å tilbakestille passordet.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "Tilbakestill URL",
"passwordNew": "Nytt passord",
"passwordNewConfirm": "Bekreft nytt passord",
"changePassword": "Endre passord",
@@ -1420,6 +1428,24 @@
"logoutError": "Feil ved utlogging",
"signingAs": "Logget inn som",
"serverAdmin": "Serveradministrator",
"promoteServerAdmin": "Forfrem til Server Admin",
"promoteServerAdminTitle": "Forfrem til Server Admin",
"promoteServerAdminQuestion": "Er du sikker på at du vil forfremme {selectedUser} til server admin?",
"promoteServerAdminMessage": "Server administratorer har de høyeste rettighetene og kan administrere serveren.",
"promoteServerAdminWarning": "Dette kan angres når som helst ved å degradere brukeren.",
"promoteServerAdminConfirm": "Forfrem til Server Admin",
"promoteServerAdminSuccess": "Bruker forfremmet",
"promoteServerAdminSuccessDescription": "{selectedUser} er nå server admin.",
"promoteServerAdminError": "Kunne ikke forfremme bruker",
"demoteServerAdmin": "Degradér fra server admin",
"demoteServerAdminTitle": "Degradér fra Server Admin",
"demoteServerAdminQuestion": "Er du sikker på at du vil degradere {selectedUser} fra server admin?",
"demoteServerAdminMessage": "{selectedUser} vil miste alle rettigheter som server admin.",
"demoteServerAdminWarning": "Dette kan angres når som helst ved å forfremme brukeren.",
"demoteServerAdminConfirm": "Degradér fra server admin",
"demoteServerAdminSuccess": "Bruker degradert",
"demoteServerAdminSuccessDescription": "{selectedUser} er ikke lenger server admin.",
"demoteServerAdminError": "Kunne ikke degradere bruker",
"managedSelfhosted": "Administrert selv-hostet",
"otpEnable": "Aktiver tofaktor",
"otpDisable": "Deaktiver tofaktor",
@@ -1573,6 +1599,8 @@
"search": "Søk…",
"searchPlaceholder": "Søk...",
"emptySearchOptions": "Ingen valg funnet",
"ipFilterSearchPlaceholder": "Angi en IP-adresse…",
"ipFilterEmptyMessage": "Angi en IP-adresse å filtrere etter",
"create": "Opprett",
"orgs": "Organisasjoner",
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
@@ -1671,7 +1699,7 @@
"sidebarInvitations": "Invitasjoner",
"sidebarRoles": "Roller",
"sidebarShareableLinks": "Delbare lenker",
"sidebarAiGateway": "AI Gateway",
"sidebarAiGateway": "AI Portal",
"sidebarAiProviders": "Leverandører",
"commandAiProviders": "AI-leverandører",
"sidebarVirtualApiKeys": "Virtuelle API-nøkler",
@@ -1831,7 +1859,7 @@
"aiBudgetPeriodYearly": "Årlig",
"aiBudgetPeriodLifetime": "Livstid",
"aiBudgetUnitUsd": "USD",
"aiBudgetUnitTokens": "Tokens",
"aiBudgetUnitTokens": "Token",
"aiBudgetConflictError": "Et budsjett for denne tilbakestillingsperioden og forbrukstype eksisterer allerede",
"aiBudgetInvalidAmountError": "Angi et maksimalt forbruk større enn 0",
"aiBudgetUpdated": "Budsjetter oppdatert",
@@ -1846,7 +1874,7 @@
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Vercel AI Gateway",
"aiProviderTypeVercelAiGateway": "Offentlige AI-portene",
"aiProviderTypeCustom": "Tilpasset",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
@@ -1899,7 +1927,7 @@
"aiProviderBudgetAmount": "Budsjettbeløp",
"aiProviderBudgetUnit": "Budsjettenhet",
"aiProviderBudgetUnitUsd": "USD",
"aiProviderBudgetUnitTokens": "Tokens",
"aiProviderBudgetUnitTokens": "Token",
"aiProviderEnabled": "Aktivert",
"aiProviderEnabledDescription": "Deaktiver denne leverandøren fullstendig på tvers av alle ressurser",
"aiProviderErrorCreate": "Kunne ikke opprette AI-leverandør",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Budsjett",
"resourceBudgetSettingsDescription": "Konfigurer hvordan denne AI-portalen begrenser bruk basert på utgifter eller token-grenser",
"sidebarApiKeys": "API-nøkler",
"sidebarOrgs": "Organisasjoner",
"sidebarProvisioning": "Levering",
"sidebarSettings": "Innstillinger",
"sidebarAllUsers": "Alle brukere",
@@ -2596,6 +2625,7 @@
"createDomainType": "Type:",
"createDomainName": "Navn:",
"createDomainValue": "Verdi:",
"multiSelectFilterCount": "{count} valgt",
"createDomainCnameRecords": "CNAME-oppføringer",
"createDomainARecords": "A-oppføringer",
"createDomainRecordNumber": "Oppføring {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Få Olm til å kjøre på systemet ditt",
"clientOlmCredentials": "Legitimasjon",
"clientOlmCredentialsDescription": "Dette er hvordan klienten vil godkjenne med serveren",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Endepunkt",
"olmId": "ID",
"olmSecretKey": "Sikkerhetsnøkkel",
"clientCredentialsSave": "Lagre brukeropplysninger",
@@ -2710,6 +2740,7 @@
"healthScheme": "Metode",
"healthSelectScheme": "Velg metode",
"healthCheckPortInvalid": "Porten må være mellom 1 og 65535",
"healthCheckHostnameInvalid": "Vertsnavnet må ikke inneholde mellomrom",
"healthCheckPath": "Sti",
"healthHostname": "IP / Vert",
"healthPort": "Port",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Ingen proxy-ressurser funnet.",
"resourcesTableNoInternalResourcesFound": "Ingen private ressurser funnet.",
"resourcesTableDestination": "Destinasjon",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Navn",
"resourcesTableAliasAddress": "Alias adresse",
"resourcesTableAliasAddressInfo": "Denne adressen er en del av organisasjonens undernettverk. Den brukes til å løse aliasposter ved hjelp av intern DNS-oppløsning.",
"resourcesTableClients": "Klienter",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
"editInternalResourceDialogDestinationIPDescription": "IP eller vertsnavn til ressursen på nettstedets nettverk.",
"editInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Navn",
"editInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
"createInternalResourceDialogNoSitesAvailable": "Ingen tilgjengelige steder",
"createInternalResourceDialogNoSitesAvailableDescription": "Du må ha minst ett Newt-område med et subnett konfigurert for å opprette private ressurser.",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Destinasjon",
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
"createInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Navn",
"createInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
"internalResourceAliasLocalWarning": "Alias som slutter på .local kan forårsake oppløsningsproblemer på grunn av mDNS på enkelte nettverk.",
"internalResourceDownstreamSchemeRequired": "Skjema er påkrevd for HTTP-ressurser",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Legg til et CIDR-område (f.eks. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Feil ved lasting av subnett",
"remoteExitNodeNetworkingLabelsTitle": "Preferanseetiketter",
"remoteExitNodeNetworkingLabelsDescription": "Områder med disse etikettene vil bli tvunget til å koble gjennom denne fjerne utgangsnoden.",
"remoteExitNodeNetworkingLabelsDescription": "Nettsteder med disse etikettene vil foretrekke å koble til gjennom denne eksterne utgangsnoden.",
"remoteExitNodeNetworkingLabelsButtonText": "Velg etiketter...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Søk etiketter...",
"remoteExitNodeNetworkingLabelsLoadError": "Feil ved lasting av etiketter",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Fjern",
"idpGoogleConfiguration": "Google Konfigurasjon",
"idpGoogleConfigurationDescription": "Konfigurer Google OAuth2 legitimasjonen",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Din Google OAuth2-klient-ID",
"idpGoogleClientSecretDescription": "Google OAuth2-klienten hemmelighet",
"idpAzureConfiguration": "Azure Entra ID konfigurasjon",
"idpAzureConfigurationDescription": "Konfigurer Azure Entra ID OAuth2 legitimasjon",
"idpTenantId": "Leietaker-ID",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "Leietaker-ID",
"idpAzureTenantIdDescription": "Azure leant ID (funnet i Azure Active Directory-oversikten)",
"idpAzureClientIdDescription": "Azure App registrerings klient-ID",
"idpAzureClientSecretDescription": "Azure App Registrering Klient Hemmelig",
@@ -3470,6 +3501,7 @@
"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.",
"instanceName": "Forekomst navn",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Konfigurere matching av sti",
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
"pathMatchType": "Trefftype",
@@ -3526,7 +3558,7 @@
"allowedNoAuth": "Tillatt Ingen Auth",
"validAccessToken": "Gyldig tilgangsnøkkel",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPincode": "Gyldig PIN-kode",
"validPassword": "Gyldig passord",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Regenerering av legitimasjon vil ugyldiggjøre de forrige og forårsake en frakobling. Sørg for å oppdatere alle konfigurasjoner som bruker disse legitimasjonene.",
"confirm": "Bekreft",
"regenerateCredentialsConfirmation": "Er du sikker på at du vil regenerere legetimasjonene?",
"endpoint": "Endpoint",
"endpoint": "Endepunkt",
"Id": "Id",
"SecretKey": "Hemmelig nøkkel",
"niceId": "God ID",
@@ -3759,6 +3791,7 @@
"noData": "Ingen data",
"machineClients": "Maskinklienter",
"install": "Installer",
"downloadInstaller": "Download Installer",
"run": "Kjør",
"envFile": "Miljøfil",
"serviceFile": "Tjenestefil",
@@ -3966,6 +3999,7 @@
"disconnected": "Frakoblet",
"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.",
"approvalsEmptyStateHowToTitle": "How to Enable",
"approvalsEmptyStateStep1Title": "Gå til roller",
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Vis som administrator",
"resourceLauncherResourceDetailsDescription": "Tilkoblingsinformasjon og status for denne ressursen.",
"resourceLauncherResourceDetails": "Ressursdetaljer",
"resourceLauncherSitesDescription": "Ressursen er tilgjengelig via de følgende nettstedene.",
"resourceLauncherViewSiteAsAdmin": "Vis nettsted som administrator",
"resourceLauncherFilterBySite": "Filtrer etter nettsted",
"resourceLauncherSshCommand": "SSH-kommando",
"resourceLauncherSshCommandDescription": "Bruk Pangolin CLI for å åpne en SSH-sesjon til denne ressursen.",
"resourceLauncherAuthMethodsDescription": "Godkjenningsmetoder aktivert for denne ressursen.",
"resourceLauncherPrivateClientRequired": "Koble til med en klient på enheten din for å få privat tilgang til denne ressursen.",
"resourceLauncherPrivateClientRequiredTitle": "Klienttilkobling kreves",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode tastaturmodus",
"sessionToolbarShow": "Vis verktøylinje",
"sessionToolbarHide": "Skjul verktøylinje",
"actionUpdateSiteApprovals": "Oppdater Stedsgodkjenninger"
"actionUpdateSiteApprovals": "Oppdater Stedsgodkjenninger",
"check": "Sjekk"
}
+88 -48
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Doelen op deze site",
"siteSetting": "{siteName} instellingen",
"siteNewtTunnel": "Nieuwste site (Aanbevolen)",
"pangolinSite": "Pangolin-site",
"siteNewtTunnelDescription": "Makkelijkste manier om een ingangspunt in een netwerk te maken. Geen extra opzet.",
"siteWg": "Basis WireGuard",
"siteWgDescription": "Gebruik een WireGuard client om een tunnel te bouwen. Handmatige NAT installatie vereist.",
@@ -183,7 +184,7 @@
"shareTokenDescription": "Het toegangstoken kan als queryparameter of in aanvraagheaders worden meegegeven. Standaard moet het bij elke aanvraag worden verzonden. Als sessie-persistentie is ingeschakeld, ruilt de eerste aanvraag deze in voor een sessiecookie.",
"accessToken": "Toegangs-token",
"usageExamples": "Voorbeelden van gebruik",
"tokenId": "Token ID",
"tokenId": "Token-ID",
"requestHeades": "Aanvraag van headers",
"queryParameter": "Queryparameter",
"importantNote": "Belangrijke opmerking",
@@ -465,6 +466,8 @@
"apiKeysDelete": "API sleutel verwijderen",
"apiKeysManage": "API sleutels beheren",
"apiKeysDescription": "API sleutels worden gebruikt om toegang te verifiëren met de integratie API ",
"orgsManage": "Organisaties Beheren",
"orgsDescription": "Bekijk en beheer alle organisaties op dit systeem",
"provisioningKeysTitle": "Vertrekkende sleutel",
"provisioningKeysManage": "Beheren van Provisioning Sleutels",
"provisioningKeysDescription": "Provisionerende sleutels worden gebruikt om geautomatiseerde sitebepaling voor uw organisatie te verifiëren.",
@@ -716,6 +719,7 @@
"nameOptional": "Naam (optioneel)",
"accessControls": "Toegang Bediening",
"userDescription2": "Beheer de instellingen van deze gebruiker",
"userGeneralSettingsDescription": "Beheer de rollen en instellingen van deze gebruiker in de organisatie",
"accessRoleErrorAdd": "Gebruiker aan rol toevoegen mislukt",
"accessRoleErrorAddDescription": "Er is een fout opgetreden tijdens het toevoegen van de rol.",
"userSaved": "Gebruiker opgeslagen",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Ongeldige TLS servernaam. Gebruik de domeinnaam of sla leeg op om de TLS servernaam te verwijderen.",
"proxyEnableSSL": "Schakel TLS in",
"proxyEnableSSLDescription": "SSL/TLS-versleuteling inschakelen voor beveiligde HTTPS-verbindingen naar de doelen.",
"target": "Target",
"target": "Doelwit",
"configureTarget": "Doelstellingen configureren",
"targetErrorFetch": "Ophalen van doelen mislukt",
"targetErrorFetchDescription": "Er is een fout opgetreden bij het ophalen van de objecten",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Wanneer regels zijn uitgeschakeld, passeert al het verkeer naar authenticatie.",
"policyAccessRulesFallthroughOn": "Wanneer geen regel overeenkomt, passeert het verkeer naar authenticatie.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/beheerder/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Regels opslaan",
"resourceErrorCreate": "Fout bij maken document",
@@ -948,7 +952,7 @@
"unknownCommand": "Onbekende opdracht",
"newtErrorFetchReleases": "Kan release-informatie niet ophalen: {err}",
"newtErrorFetchLatest": "Fout bij ophalen van laatste release: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Eindpunt",
"newtId": "ID",
"newtSecretKey": "Geheim",
"newtVersion": "Versie",
@@ -1161,7 +1165,7 @@
"idpOidcConfigureDescription": "Configureer de eindpunten van de OAuth2/OIDC provider en referenties",
"idpClientId": "Client ID",
"idpClientIdDescription": "De OAuth2-client-ID van de identiteitsaanbieder",
"idpClientSecret": "Client Secret",
"idpClientSecret": "Clientgeheim",
"idpClientSecretDescription": "Het OAuth2-clientgeheim van de identiteitsprovider",
"idpAuthUrl": "URL autorisatie",
"idpAuthUrlDescription": "De URL voor autorisatie OAuth2",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Meer informatie over JMESPath",
"idpJmespathLabel": "ID pad",
"idpJmespathLabelDescription": "Het pad naar het gebruiker-id in het ID-token",
"idpIdentifierChangeTitle": "Waarschuwing voor Wijziging van ID-pad",
"idpIdentifierChangeDescription": "U staat op het punt om het ID-pad te wijzigen. Dit zal invloed hebben op hoe bestaande gebruikers worden gemapt. Gebruikers die voorheen via deze identiteitsprovider inlogden, worden mogelijk niet meer als dezelfde gebruikers herkend.",
"idpIdentifierChangeConfirmMessage": "Ik bevestig",
"idpIdentifierChangeWarningText": "Dit beïnvloedt hoe bestaande gebruikers worden gemapt",
"idpJmespathEmailPathOptional": "E-mail pad (optioneel)",
"idpJmespathEmailPathOptionalDescription": "Het pad naar het e-mailadres van de gebruiker in het ID-token",
"idpJmespathNamePathOptional": "Naam pad (optioneel)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Herstelcode voor wachtwoord genereren",
"passwordResetCodeGenerated": "Wachtwoord reset code gegenereerd",
"passwordResetCodeGeneratedDescription": "Deel deze code met de gebruiker. Ze kunnen deze gebruiken om hun wachtwoord te resetten.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "Herstel-URL",
"passwordNew": "Nieuw wachtwoord",
"passwordNewConfirm": "Bevestig nieuw wachtwoord",
"changePassword": "Wachtwoord wijzigen",
@@ -1420,6 +1428,24 @@
"logoutError": "Fout bij uitloggen",
"signingAs": "Ingelogd als",
"serverAdmin": "Server beheer",
"promoteServerAdmin": "Promoveer naar Server Admin",
"promoteServerAdminTitle": "Promoveer naar Server Admin",
"promoteServerAdminQuestion": "Weet u zeker dat u {selectedUser} naar server admin wilt promoveren?",
"promoteServerAdminMessage": "Server admins hebben de hoogste privileges en kunnen de server beheren.",
"promoteServerAdminWarning": "Dit kan op elk moment worden hersteld door de gebruiker te degraderen.",
"promoteServerAdminConfirm": "Promoveer naar Server Admin",
"promoteServerAdminSuccess": "Gebruiker Geprobeerd",
"promoteServerAdminSuccessDescription": "{selectedUser} is nu een server admin.",
"promoteServerAdminError": "Het promoten van gebruiker is mislukt",
"demoteServerAdmin": "Degradeer van Server admin",
"demoteServerAdminTitle": "Degradeer van Server Admin",
"demoteServerAdminQuestion": "Weet u zeker dat u {selectedUser} van server admin wilt degraderen?",
"demoteServerAdminMessage": "{selectedUser} verliest alle server admin privileges.",
"demoteServerAdminWarning": "Dit kan op elk moment worden hersteld door de gebruiker te promoveren.",
"demoteServerAdminConfirm": "Degradeer van server admin",
"demoteServerAdminSuccess": "Gebruiker Gedegradeerd",
"demoteServerAdminSuccessDescription": "{selectedUser} is niet langer een server admin.",
"demoteServerAdminError": "Degradatie van gebruiker mislukt",
"managedSelfhosted": "Beheerde Self-Hosted",
"otpEnable": "Twee-factor inschakelen",
"otpDisable": "Tweestapsverificatie uitschakelen",
@@ -1573,6 +1599,8 @@
"search": "Zoeken…",
"searchPlaceholder": "Zoeken...",
"emptySearchOptions": "Geen opties gevonden",
"ipFilterSearchPlaceholder": "Voer een IP-adres in…",
"ipFilterEmptyMessage": "Voer een IP-adres in om op te filteren",
"create": "Aanmaken",
"orgs": "Organisaties",
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
@@ -1664,7 +1692,7 @@
"sidebarPolicies": "Gedeeld Beleid",
"sidebarResourcePolicies": "Openbare Bronnen",
"sidebarAccessControl": "Toegangs controle",
"sidebarLogsAndAnalytics": "Logs & Analytics",
"sidebarLogsAndAnalytics": "Logboeken & Analytics",
"sidebarTeam": "Team",
"sidebarUsers": "Gebruikers",
"sidebarAdmin": "Beheerder",
@@ -1846,7 +1874,7 @@
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Vercel AI Gateway",
"aiProviderTypeVercelAiGateway": "Vercel AI-poort",
"aiProviderTypeCustom": "Aangepast",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
@@ -1865,7 +1893,7 @@
"aiProviderApiKeyDescription": "API-sleutel gebruikt om verzoeken naar deze provider te authenticeren",
"aiProviderCustomHeadersDescription": "Headers verzonden bij elk verzoek naar deze provider. Nieuwe regel gescheiden: Header-Naam: waarde",
"aiProviderApiKeyLastChars": "API-sleutel",
"aiProviderAuthType": "Auth Type",
"aiProviderAuthType": "Authenticatietype",
"aiProviderAuthTypeSearch": "Zoek auth types...",
"aiProviderAuthTypeNotFound": "Geen auth type gevonden",
"aiProviderAuthTypeBearer": "Bearer",
@@ -1879,7 +1907,7 @@
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI Gateway",
"aiProviderAuthTypeCfAigAuthorizationDescription": "cf-aig-autorisatie: Bearer-sleutel. Gebruikt door Cloudflare AI Gateway",
"aiProviderAuthTypeNone": "Geen Auth",
"aiProviderAuthTypePassthrough": "Passthrough",
"aiProviderAuthTypePassthrough": "Transparant",
"aiProviderAuthTypeDescription": "Hoe de upstream API verzoeken authenticeert",
"aiProviderAuthTypePassthroughDescription": "Stuur de API Keys headers van de beller door naar de upstream",
"aiProviderAuthTypeNoneDescription": "Stuur geen authenticatieheaders naar de upstream",
@@ -1986,7 +2014,7 @@
"aiProviderModelsBudgetConfigured": "Budget geconfigureerd",
"aiProviderModelsEditTitle": "Model bewerken",
"aiProviderModelsEditDescription": "Werk de modelsleutel of budgetconfiguratie bij.",
"aiProviderModelsBudgetTab": "Budget",
"aiProviderModelsBudgetTab": "AI-budget",
"aiProviderModelsKeyLabel": "Model sleutel",
"aiProviderModelsKeyRequired": "Voer een modelsleutel in",
"aiProviderModelsKeyDuplicate": "Deze modelsleutel staat al op een lijst",
@@ -2086,9 +2114,10 @@
"aiUsageUnnamedVirtualApiKey": "Naamloze sleutel",
"aiUsageLoading": "Laden...",
"aiUsageNoData": "Geen data",
"resourceBudgetSettings": "Budget",
"resourceBudgetSettings": "AI-budget",
"resourceBudgetSettingsDescription": "Configureer hoe deze AI-gateway het gebruik beperkt op basis van uitgaven of tokenlimieten",
"sidebarApiKeys": "API sleutels",
"sidebarOrgs": "Organisaties",
"sidebarProvisioning": "Provisie",
"sidebarSettings": "Instellingen",
"sidebarAllUsers": "Alle gebruikers",
@@ -2099,7 +2128,7 @@
"sidebarMachineClients": "Machines",
"sidebarDomains": "Domeinen",
"sidebarGeneral": "Beheren",
"sidebarLogAndAnalytics": "Log & Analytics",
"sidebarLogAndAnalytics": "Logboeken & Analytics",
"sidebarBluePrints": "Blauwdrukken",
"sidebarAlerting": "Waarschuwingen",
"sidebarHealthChecks": "Gezondheidscontroles",
@@ -2170,7 +2199,7 @@
"alertingRuleEnabled": "Regel ingeschakeld",
"alertingSectionSource": "Bron",
"alertingSourceType": "Brontype",
"alertingSourceSite": "Site",
"alertingSourceSite": "Referentie",
"alertingSourceHealthCheck": "Gezondheidscontrole",
"alertingPickSites": "Sites",
"alertingPickHealthChecks": "Gezondheidscontroles",
@@ -2192,7 +2221,7 @@
"alertingResourcesEmpty": "Geen bronnen met doelen in de eerste 10 resultaten.",
"alertingSectionTrigger": "Trigger",
"alertingTrigger": "Wanneer te waarschuwen",
"alertingTriggerSiteOnline": "Site online",
"alertingTriggerSiteOnline": "Site Online Tijd",
"alertingTriggerSiteOffline": "Site offline",
"alertingTriggerSiteToggle": "Site status wijzigt",
"alertingTriggerHcHealthy": "Gezondheidscontrole gezond",
@@ -2224,7 +2253,7 @@
"alertingWebhookMethod": "HTTP-methode",
"alertingWebhookSecret": "Ondertekengeheim (optioneel)",
"alertingWebhookSecretPlaceholder": "HMAC-geheim",
"alertingWebhookHeaders": "Headers",
"alertingWebhookHeaders": "Kopteksten",
"alertingAddHeader": "Header toevoegen",
"alertingSelectSites": "Selecteer sites…",
"alertingSitesSelected": "{count} sites geselecteerd",
@@ -2442,7 +2471,7 @@
"sidebarCollapse": "Inklappen",
"sidebarExpand": "Uitklappen",
"productUpdateMoreInfo": "Nog {noOfUpdates} updates",
"productUpdateInfo": "{noOfUpdates} updates",
"productUpdateInfo": "Nog {noOfUpdates} updates",
"productUpdateWhatsNew": "Wat is nieuw",
"productUpdateTitle": "Update Producten",
"productUpdateEmpty": "Geen updates",
@@ -2596,6 +2625,7 @@
"createDomainType": "Type:",
"createDomainName": "Naam:",
"createDomainValue": "Waarde:",
"multiSelectFilterCount": "{count} geselecteerd",
"createDomainCnameRecords": "CNAME-records",
"createDomainARecords": "A Records",
"createDomainRecordNumber": "Record {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Laat Olm draaien op uw systeem",
"clientOlmCredentials": "Aanmeldgegevens",
"clientOlmCredentialsDescription": "Dit is hoe de client zich zal verifiëren met de server",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Eindpunt",
"olmId": "ID",
"olmSecretKey": "Geheim",
"clientCredentialsSave": "Sla de aanmeldgegevens op",
@@ -2710,6 +2740,7 @@
"healthScheme": "Methode",
"healthSelectScheme": "Selecteer methode",
"healthCheckPortInvalid": "Poort moet tussen 1 en 65535 zijn",
"healthCheckHostnameInvalid": "De hostnaam mag geen witruimtes bevatten",
"healthCheckPath": "Pad",
"healthHostname": "IP / Hostnaam",
"healthPort": "Poort",
@@ -2722,7 +2753,7 @@
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
"sshSettings": "SSH",
"inferenceSettings": "AI Gateway",
"inferenceSettings": "AI-gateway",
"sshAccess": "SSH Toegang",
"rdpSettings": "RDP",
"vncSettings": "VNC",
@@ -2878,7 +2909,7 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeInference": "AI Gateway",
"editInternalResourceDialogModeInference": "AI-gateway",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Schema",
"editInternalResourceDialogEnableSsl": "Schakel TLS in",
@@ -2899,13 +2930,13 @@
"privateResourceAllowIcmpPing": "Sta ICMP Ping toe",
"privateResourceNetworkAccess": "Netwerktoegang",
"privateResourceNetworkAccessDescription": "Beheer TCP/UDP poorttoegang en of ICMP-ping is toegestaan voor deze bron.",
"hostSettings": "Host",
"hostSettings": "Hostnaam",
"cidrSettings": "CIDR",
"createInternalResourceDialogResourceProperties": "Bron-eigenschappen",
"createInternalResourceDialogName": "Naam",
"createInternalResourceDialogSite": "Site",
"createInternalResourceDialogSite": "Referentie",
"selectSite": "Selecteer site...",
"multiSitesSelectorSitesCount": "{count, plural, one {# site} other {# sites}}",
"multiSitesSelectorSitesCount": "{count, plural, one {# locatie} other {# locaties}}",
"labelsSelectorLabelsCount": "{count, plural, one {# label} other {# labels}}",
"noSitesFound": "Geen sites gevonden.",
"createInternalResourceDialogProtocol": "Protocol",
@@ -2938,7 +2969,7 @@
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"createInternalResourceDialogModeInference": "AI Gateway",
"createInternalResourceDialogModeInference": "AI-gateway",
"scheme": "Schema",
"createInternalResourceDialogScheme": "Schema",
"createInternalResourceDialogEnableSsl": "Schakel TLS in",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Voeg een CIDR-bereik toe (bijv. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Kon subnets niet laden",
"remoteExitNodeNetworkingLabelsTitle": "Voorkeurslabels",
"remoteExitNodeNetworkingLabelsDescription": "Sites met deze labels worden verplicht om verbinding te maken via dit externe exit-knooppunt.",
"remoteExitNodeNetworkingLabelsDescription": "Sites met deze labels zullen bij voorkeur verbinding maken via deze externe exitnode.",
"remoteExitNodeNetworkingLabelsButtonText": "Selecteer labels...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Labels zoeken...",
"remoteExitNodeNetworkingLabelsLoadError": "Kon labels niet laden",
@@ -3081,11 +3112,11 @@
"regionNorthernEurope": "Noord-Europa",
"regionSouthernEurope": "Zuid-Europa",
"regionWesternEurope": "West-Europa",
"regionOceania": "Oceania",
"regionOceania": "Oceanië",
"regionAustraliaAndNewZealand": "Australië en Nieuw-Zeeland",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"regionMelanesia": "Melanesië",
"regionMicronesia": "Micronesië",
"regionPolynesia": "Polynesië",
"managedSelfHosted": {
"title": "Beheerde Self-Hosted",
"description": "betrouwbaardere en slecht onderhouden Pangolin server met extra klokken en klokkenluiders",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Verwijderen",
"idpGoogleConfiguration": "Google Configuratie",
"idpGoogleConfigurationDescription": "Configureer de Google OAuth2-referenties",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Uw Google OAuth2-client-ID",
"idpGoogleClientSecretDescription": "Google OAuth2 Clientgeheim",
"idpAzureConfiguration": "Azure Entra ID configuratie",
"idpAzureConfigurationDescription": "Azure Entra ID OAuth2 referenties configureren",
"idpTenantId": "Tenant-ID",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "tenant-ID",
"idpAzureTenantIdDescription": "Azure tenant ID (gevonden in Azure Active Directory overzicht)",
"idpAzureClientIdDescription": "Azure App registratie Client ID",
"idpAzureClientSecretDescription": "Azure App registratie client geheim",
@@ -3173,7 +3204,7 @@
"idpTenantIdLabel": "Tenant-ID",
"idpAzureClientIdDescription2": "Azure App registratie Client ID",
"idpAzureClientSecretDescription2": "Azure App registratie client geheim",
"idpGoogleDescription": "Google OAuth2/OIDC provider",
"idpGoogleDescription": "Algemene OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"subnet": "Subnet",
"utilitySubnet": "Hulpmiddel Subnet",
@@ -3340,7 +3371,7 @@
"manageMachineClientsDescription": "Creëer en beheer clients die servers en systemen gebruiken om privé verbinding te maken met bronnen",
"machineClientsBannerTitle": "Servers & Geautomatiseerde Systemen",
"machineClientsBannerDescription": "Machineclients zijn bedoeld voor servers en geautomatiseerde systemen die niet aan een specifieke gebruiker zijn gekoppeld. Ze verifiëren met een ID en geheim, en kunnen draaien met Pangolin CLI, Olm CLI, of Olm als een container.",
"machineClientsBannerPangolinCLI": "Pangolin CLI",
"machineClientsBannerPangolinCLI": "Pangoline Cloud",
"machineClientsBannerOlmCLI": "Olm CLI",
"machineClientsBannerOlmContainer": "Olm-container",
"clientsTableUserClients": "Gebruiker",
@@ -3470,6 +3501,7 @@
"priority": "Prioriteit",
"priorityDescription": "routes met hogere prioriteit worden eerst geëvalueerd. Prioriteit = 100 betekent automatisch bestellen (systeem beslist de). Gebruik een ander nummer om handmatige prioriteit af te dwingen.",
"instanceName": "Naam instantie",
"clearInstanceName": "Serverassociatie resetten",
"pathMatchModalTitle": "Configureren van overeenkomende pad",
"pathMatchModalDescription": "Stel in hoe inkomende verzoeken moeten worden gekoppeld aan hun pad.",
"pathMatchType": "Wedstrijd Type",
@@ -3502,7 +3534,7 @@
"pathRewriteExact": "Exacte",
"pathRewriteRegex": "Regex",
"pathRewriteStrip": "Verwijder",
"pathRewriteStripLabel": "strip",
"pathRewriteStripLabel": "verwijder",
"sidebarEnableEnterpriseLicense": "Activeer Enterprise Licentie",
"cannotbeUndone": "Dit kan niet ongedaan worden gemaakt.",
"toConfirm": "om te bevestigen.",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Toegestaan door regel",
"allowedNoAuth": "Toegestaan geen authenticatie",
"validAccessToken": "Geldige toegangstoken",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Geldige headerauthenticatie",
"validPincode": "Geldige pincode",
"validPassword": "Geldig wachtwoord",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Geldige e-mail",
"validSSO": "Geldige SSO",
"validVirtualAPIKey": "Geldige Virtuele API-sleutel",
"view": "Bekijk",
"configManaged": "Configuratie Beheerd",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Achtergelaten door regel",
"noSessions": "Geen sessies",
"temporaryRequestToken": "Tijdelijk verzoek token",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Geen geldige auth",
"ip": "IP-adres",
"reason": "Reden",
"requestLogs": "HTTP-aanvraaglogboeken",
@@ -3630,7 +3662,7 @@
"auto": "Automatisch",
"TTL": "TTL",
"howToAddRecords": "Hoe voeg ik Records toe",
"dnsRecord": "DNS Records",
"dnsRecord": "DNS-records",
"required": "vereist",
"domainSettingsUpdated": "Domeininstellingen succesvol bijgewerkt",
"orgOrDomainIdMissing": "Organisatie of domein ID ontbreekt",
@@ -3742,23 +3774,24 @@
"regenerateCredentialsWarning": "Het opnieuw genereren van inloggegevens zal de vorige ongeldig maken en een slechte verbinding veroorzaken. Zorg ervoor dat u alle configuraties die deze inloggegevens gebruiken bijwerkt.",
"confirm": "Bevestigen",
"regenerateCredentialsConfirmation": "Weet u zeker dat u de inloggegevens opnieuw wilt genereren?",
"endpoint": "Endpoint",
"endpoint": "Eindpunt",
"Id": "Id",
"SecretKey": "Geheime sleutel",
"niceId": "Leuk ID",
"niceIdUpdated": "Leuke ID bijgewerkt",
"niceIdUpdatedSuccessfully": "Nice ID Updated Successfully",
"niceIdUpdatedSuccessfully": "Mooi ID succesvol bijgewerkt",
"niceIdUpdateError": "Fout bij bijwerken ID Nice",
"niceIdUpdateErrorDescription": "Fout opgetreden tijdens het bijwerken van de ID van Nice.",
"niceIdCannotBeEmpty": "Nice ID mag niet leeg zijn",
"enterIdentifier": "ID invoeren",
"identifier": "Identifier",
"identifier": "ID pad",
"deviceLoginUseDifferentAccount": "Niet u? Gebruik een ander account.",
"deviceLoginDeviceRequestingAccessToAccount": "Een apparaat vraagt om toegang tot dit account.",
"loginSelectAuthenticationMethod": "Selecteer een verificatiemethode om door te gaan.",
"noData": "Geen gegevens",
"machineClients": "Machine Clienten",
"install": "Installeren",
"downloadInstaller": "Download Installer",
"run": "Uitvoeren",
"envFile": "Omgevingsbestand",
"serviceFile": "Servicebestand",
@@ -3827,7 +3860,7 @@
"internalResourceAuthDaemonStrategy": "SSH Auth Daemon locatie",
"internalResourceAuthDaemonStrategyDescription": "Kies waar de SSH authenticatie daemon wordt uitgevoerd: op de website (Newt) of op een externe host.",
"internalResourceAuthDaemonDescription": "De SSH authenticatie daemon zorgt voor SSH sleutelondertekening en PAM authenticatie voor deze resource. Kies of het wordt uitgevoerd op de website (Nieuw) of op een afzonderlijke externe host. Zie <docsLink>de documentatie</docsLink> voor meer.",
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
"internalResourceAuthDaemonDocsUrl": "https:\\/\\/docs.pangolin.net",
"internalResourceAuthDaemonStrategyPlaceholder": "Selecteer strategie",
"internalResourceAuthDaemonStrategyLabel": "Locatie",
"internalResourceAuthDaemonSite": "In de site",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Kernel versie",
"deviceModel": "Apparaat model",
"serialNumber": "Serienummer",
"hostname": "Hostname",
"hostname": "Hostnaam",
"firstSeen": "Eerst gezien",
"lastSeen": "Laatst gezien op",
"biometricsEnabled": "Biometrie ingeschakeld",
@@ -3966,6 +3999,7 @@
"disconnected": "Losgekoppeld",
"approvalsEmptyStateTitle": "Apparaat goedkeuringen niet ingeschakeld",
"approvalsEmptyStateDescription": "Apparaatgoedkeuringen voor rollen inschakelen om goedkeuring van de beheerder te vereisen voordat gebruikers nieuwe apparaten kunnen koppelen.",
"approvalsEmptyStateHowToTitle": "Hoe te activeren",
"approvalsEmptyStateStep1Title": "Ga naar rollen",
"approvalsEmptyStateStep1Description": "Navigeer naar de rolinstellingen van uw organisatie om apparaatgoedkeuringen te configureren.",
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
@@ -4229,7 +4263,7 @@
"resourceLauncherSortDescending": "Aflopend sorteren",
"resourceLauncherSettings": "Instellingen",
"resourceLauncherGroupBy": "Groep Op",
"resourceLauncherGroupBySite": "Site",
"resourceLauncherGroupBySite": "Referentie",
"resourceLauncherGroupByLabel": "Label",
"resourceLauncherGroupByNone": "Geen",
"resourceLauncherLayout": "Lay-out",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Bekijk als Admin",
"resourceLauncherResourceDetailsDescription": "Verbindingsinformatie en -status voor deze bron.",
"resourceLauncherResourceDetails": "Brongegevens",
"resourceLauncherSitesDescription": "De bron is toegankelijk via de volgende sites.",
"resourceLauncherViewSiteAsAdmin": "Bekijk site als Admin",
"resourceLauncherFilterBySite": "Filter op site",
"resourceLauncherSshCommand": "SSH Commando",
"resourceLauncherSshCommandDescription": "Gebruik de Pangolin CLI om een SSH-sessie te openen naar deze bron.",
"resourceLauncherAuthMethodsDescription": "Authenticatiemethoden ingeschakeld voor deze bron.",
"resourceLauncherPrivateClientRequired": "Maak verbinding met een client op uw apparaat om deze bron privé te benaderen.",
"resourceLauncherPrivateClientRequiredTitle": "Client Verbinding Vereist",
@@ -4254,7 +4293,7 @@
"resourceLauncherTcp": "TCP",
"resourceLauncherUdp": "UDP",
"resourceLauncherUnlabeled": "Geen label",
"resourceLauncherAiGateway": "AI Gateway",
"resourceLauncherAiGateway": "AI-gateway",
"resourceLauncherNoSite": "Geen Site",
"resourceLauncherAvailableModels": "Beschikbare Modellen",
"resourceLauncherAvailableModelsDescription": "Modellen die u met deze AI-gateway kunt gebruiken.",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode toetsenbordmodus",
"sessionToolbarShow": "Toon werkbalk",
"sessionToolbarHide": "Verberg werkbalk",
"actionUpdateSiteApprovals": "Sitegoedkeuringen bijwerken"
"actionUpdateSiteApprovals": "Sitegoedkeuringen bijwerken",
"check": "Controleren"
}
+78 -38
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Cele na tej stronie",
"siteSetting": "Ustawienia {siteName}",
"siteNewtTunnel": "Newt Site (Rekomendowane)",
"pangolinSite": "Witryna Pangolin",
"siteNewtTunnelDescription": "Najprostszy sposób na stworzenie punktu wejścia w sieci. Nie ma dodatkowej konfiguracji.",
"siteWg": "Podstawowy WireGuard",
"siteWgDescription": "Użyj dowolnego klienta WireGuard do utworzenia tunelu. Wymagana jest ręczna konfiguracja NAT.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Usuń klucz API",
"apiKeysManage": "Zarządzaj kluczami API",
"apiKeysDescription": "Klucze API służą do uwierzytelniania z API integracji",
"orgsManage": "Zarządzaj organizacjami",
"orgsDescription": "Zobacz i zarządzaj wszystkimi organizacjami w tej instancji",
"provisioningKeysTitle": "Klucz Zaopatrzenia",
"provisioningKeysManage": "Zarządzaj kluczami zaopatrzenia",
"provisioningKeysDescription": "Klucze zaopatrzenia są używane do uwierzytelniania zautomatyzowanego zaopatrzenia twojej organizacji.",
@@ -716,6 +719,7 @@
"nameOptional": "Nazwa (Opcjonalnie)",
"accessControls": "Kontrola dostępu",
"userDescription2": "Zarządzaj ustawieniami tego użytkownika",
"userGeneralSettingsDescription": "Zarządzaj rolami i ustawieniami tego użytkownika w organizacji",
"accessRoleErrorAdd": "Nie udało się dodać użytkownika do roli",
"accessRoleErrorAddDescription": "Wystąpił błąd podczas dodawania użytkownika do roli.",
"userSaved": "Użytkownik zapisany",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Nieprawidłowa nazwa serwera TLS. Użyj formatu nazwy domeny lub zapisz pusty, aby usunąć nazwę serwera TLS.",
"proxyEnableSSL": "Włącz TLS",
"proxyEnableSSLDescription": "Włącz szyfrowanie SSL/TLS dla bezpiecznych połączeń HTTPS z celami.",
"target": "Target",
"target": "Cel",
"configureTarget": "Konfiguruj Targety",
"targetErrorFetch": "Nie udało się pobrać celów",
"targetErrorFetchDescription": "Wystąpił błąd podczas pobierania celów",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Gdy reguły są wyłączone, cały ruch przechodzi do uwierzytelniania.",
"policyAccessRulesFallthroughOn": "Gdy żadna reguła nie pasuje, ruch przechodzi do uwierzytelniania.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/administrator/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Zapisz zasady",
"resourceErrorCreate": "Błąd podczas tworzenia zasobu",
@@ -948,7 +952,7 @@
"unknownCommand": "Nieznane polecenie",
"newtErrorFetchReleases": "Nie udało się pobrać informacji o wydaniu: {err}",
"newtErrorFetchLatest": "Błąd podczas pobierania najnowszego wydania: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Koniec punktu pracy",
"newtId": "ID",
"newtSecretKey": "Sekret",
"newtVersion": "Wersja",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Dowiedz się więcej o JMESPath",
"idpJmespathLabel": "Ścieżka identyfikatora",
"idpJmespathLabelDescription": "JMESPath do identyfikatora użytkownika w tokenie ID",
"idpIdentifierChangeTitle": "Ostrzeżenie o zmianie ścieżki identyfikatora",
"idpIdentifierChangeDescription": "Zaraz zmienisz ścieżkę identyfikatora. To wpłynie na sposób mapowania istniejących użytkowników. Użytkownicy, którzy wcześniej logowali się przez tego dostawcę tożsamości, mogą nie być już rozpoznawani jako ci sami użytkownicy.",
"idpIdentifierChangeConfirmMessage": "Potwierdzam",
"idpIdentifierChangeWarningText": "To wpłynie na sposób mapowania istniejących użytkowników",
"idpJmespathEmailPathOptional": "Ścieżka email (Opcjonalnie)",
"idpJmespathEmailPathOptionalDescription": "JMESPath do emaila użytkownika w tokenie ID",
"idpJmespathNamePathOptional": "Ścieżka nazwy (Opcjonalnie)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Generuj kod resetowania hasła",
"passwordResetCodeGenerated": "Wygenerowany kod resetowania hasła",
"passwordResetCodeGeneratedDescription": "Udostępnij ten kod użytkownikowi. Mogą go użyć do zresetowania hasła.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "URL resetowania",
"passwordNew": "Nowe hasło",
"passwordNewConfirm": "Potwierdź nowe hasło",
"changePassword": "Zmień hasło",
@@ -1420,6 +1428,24 @@
"logoutError": "Błąd podczas wylogowywania",
"signingAs": "Zalogowany jako",
"serverAdmin": "Administrator serwera",
"promoteServerAdmin": "Promuj na administratora serwera",
"promoteServerAdminTitle": "Promuj na Administratora Serwera",
"promoteServerAdminQuestion": "Czy na pewno chcesz promować {selectedUser} na administratora serwera?",
"promoteServerAdminMessage": "Administratorzy serwera mają najwyższe uprawnienia i mogą zarządzać serwerem.",
"promoteServerAdminWarning": "Można to cofnąć w dowolnym momencie przez zdegradowanie użytkownika.",
"promoteServerAdminConfirm": "Promuj na Administratora Serwera",
"promoteServerAdminSuccess": "Użytkownik Promowany",
"promoteServerAdminSuccessDescription": "{selectedUser} jest teraz administratorem serwera.",
"promoteServerAdminError": "Nie udało się promować użytkownika",
"demoteServerAdmin": "Zdegraduj z administratora serwera",
"demoteServerAdminTitle": "Zdegraduj z Administratora Serwera",
"demoteServerAdminQuestion": "Czy na pewno chcesz zdegradować {selectedUser} z administratora serwera?",
"demoteServerAdminMessage": "{selectedUser} straci wszystkie uprawnienia administratora serwera.",
"demoteServerAdminWarning": "Można to cofnąć w dowolnym momencie przez promowanie użytkownika.",
"demoteServerAdminConfirm": "Zdegraduj z administratora serwera",
"demoteServerAdminSuccess": "Użytkownik zdyskredytowany",
"demoteServerAdminSuccessDescription": "{selectedUser} nie jest już administratorem serwera.",
"demoteServerAdminError": "Nie udało się zdegradować użytkownika",
"managedSelfhosted": "Zarządzane Samodzielnie-Hostingowane",
"otpEnable": "Włącz uwierzytelnianie dwuskładnikowe",
"otpDisable": "Wyłącz uwierzytelnianie dwuskładnikowe",
@@ -1573,6 +1599,8 @@
"search": "Szukaj…",
"searchPlaceholder": "Szukaj...",
"emptySearchOptions": "Nie znaleziono opcji",
"ipFilterSearchPlaceholder": "Wprowadź adres IP…",
"ipFilterEmptyMessage": "Wprowadź adres IP, aby filtrować",
"create": "Utwórz",
"orgs": "Organizacje",
"loginError": "Wystąpił nieoczekiwany błąd. Spróbuj ponownie.",
@@ -1676,7 +1704,7 @@
"commandAiProviders": "Dostawcy AI",
"sidebarVirtualApiKeys": "Wirtualne Klucze API",
"sidebarMyApiKeys": "Twoje Klucze API",
"sidebarAccount": "Launcher",
"sidebarAccount": "Uruchamiacz",
"commandVirtualApiKeys": "Wirtualne Klucze API",
"virtualApiKeysTitle": "Zarządzaj Wirtualnymi Kluczami API",
"virtualApiKeysDescription": "Utwórz i zarządzaj ręcznymi kluczami API dla dostępu do publicznych bram AI",
@@ -1846,16 +1874,16 @@
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Vercel AI Gateway",
"aiProviderTypeVercelAiGateway": "Bramka Vercel AI",
"aiProviderTypeCustom": "Niestandardowy",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
"aiProviderTypeGoogleGeminiDescription": "Google Gemini API",
"aiProviderTypeVertexAiDescription": "Google Vertex AI API",
"aiProviderTypeBedrockDescription": "Amazon Bedrock Runtime API",
"aiProviderTypeMicrosoftFoundryDescription": "Microsoft Foundry API",
"aiProviderTypeOpenRouterDescription": "OpenRouter API",
"aiProviderTypeVercelAiGatewayDescription": "Vercel AI Gateway API",
"aiProviderTypeOpenaiDescription": "API OpenAI",
"aiProviderTypeAnthropicDescription": "API Anthropic",
"aiProviderTypeGoogleGeminiDescription": "API Google Gemini",
"aiProviderTypeVertexAiDescription": "API Google Vertex AI",
"aiProviderTypeBedrockDescription": "API Amazon Bedrock Runtime",
"aiProviderTypeMicrosoftFoundryDescription": "API Microsoft Foundry",
"aiProviderTypeOpenRouterDescription": "API OpenRouter",
"aiProviderTypeVercelAiGatewayDescription": "API Bramki Vercel AI",
"aiProviderTypeCustomDescription": "Przynieś swój własny endpoint lub trasę przez cele witryny",
"aiProviderUpstreamUrl": "URL do góry",
"aiProviderUpstreamUrlDescription": "Podstawowy URL dla API dostawcy",
@@ -1876,7 +1904,7 @@
"aiProviderAuthTypeXGoogApiKeyDescription": "nagłówek x-goog-klucz-api. Używane przez Google Gemini",
"aiProviderAuthTypeHec": "Splunk HEC",
"aiProviderAuthTypeHecDescription": "Autoryzacja: klucz Splunk. Używane przez Splunk HTTP Event Collector",
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI Gateway",
"aiProviderAuthTypeCfAigAuthorization": "Bramka Cloudflare AI",
"aiProviderAuthTypeCfAigAuthorizationDescription": "cf-aig-autoryzacja: klucz Bearer. Używane przez Cloudflare AI Gateway",
"aiProviderAuthTypeNone": "Bez autoryzacji",
"aiProviderAuthTypePassthrough": "Przejście",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Budżet",
"resourceBudgetSettingsDescription": "Skonfiguruj, jak ta brama AI ogranicza użycie na podstawie limitów wydatków lub tokenów",
"sidebarApiKeys": "Klucze API",
"sidebarOrgs": "Organizacje",
"sidebarProvisioning": "Dostarczanie",
"sidebarSettings": "Ustawienia",
"sidebarAllUsers": "Wszyscy użytkownicy",
@@ -2596,6 +2625,7 @@
"createDomainType": "Typ:",
"createDomainName": "Nazwa:",
"createDomainValue": "Wartość:",
"multiSelectFilterCount": "{count, plural, one {# wybrany} few {# wybrane} many {# wybranych} other {# wybranych}}",
"createDomainCnameRecords": "Rekordy CNAME",
"createDomainARecords": "Rekordy A",
"createDomainRecordNumber": "Rekord {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Uruchom Olm na swoim systemie",
"clientOlmCredentials": "Dane logowania",
"clientOlmCredentialsDescription": "W ten sposób klient będzie uwierzytelniał się z serwerem",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Koniec punktu",
"olmId": "ID",
"olmSecretKey": "Sekret",
"clientCredentialsSave": "Zapisz dane logowania",
@@ -2710,6 +2740,7 @@
"healthScheme": "Metoda",
"healthSelectScheme": "Wybierz metodę",
"healthCheckPortInvalid": "Port musi być pomiędzy 1 a 65535",
"healthCheckHostnameInvalid": "Nazwa hosta nie może zawierać spacji",
"healthCheckPath": "Ścieżka",
"healthHostname": "IP / Nazwa hosta",
"healthPort": "Port",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Nie znaleziono zasobów proxy.",
"resourcesTableNoInternalResourcesFound": "Nie znaleziono prywatnych zasobów.",
"resourcesTableDestination": "Miejsce docelowe",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Pseudonim",
"resourcesTableAliasAddress": "Adres aliasu",
"resourcesTableAliasAddressInfo": "Ten adres jest częścią podsieci użyteczności organizacji. Jest używany do rozwiązywania rekordów aliasu przy użyciu wewnętrznej rozdzielczości DNS.",
"resourcesTableClients": "Klientami",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.",
"editInternalResourceDialogDestinationIPDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.",
"editInternalResourceDialogDestinationCidrDescription": "Zakres CIDR zasobu w sieci witryny.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Pseudonim",
"editInternalResourceDialogAliasDescription": "Opcjonalny wewnętrzny alias DNS dla tego zasobu.",
"createInternalResourceDialogNoSitesAvailable": "Brak dostępnych stron",
"createInternalResourceDialogNoSitesAvailableDescription": "Musisz mieć co najmniej jedną lokalizację Newt z skonfigurowaną podsiecią, aby tworzyć prywatne zasoby.",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Miejsce docelowe",
"createInternalResourceDialogDestinationHostDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.",
"createInternalResourceDialogDestinationCidrDescription": "Zakres CIDR zasobu w sieci witryny.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Pseudonim",
"createInternalResourceDialogAliasDescription": "Opcjonalny wewnętrzny alias DNS dla tego zasobu.",
"internalResourceAliasLocalWarning": "Alias kończący się na .local może powodować problemy z rozpoznawaniem z powodu mDNS w niektórych sieciach.",
"internalResourceDownstreamSchemeRequired": "Schemat jest wymagany dla zasobów HTTP",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Dodaj zakres CIDR (np. 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Nie udało się załadować podsieci",
"remoteExitNodeNetworkingLabelsTitle": "Etykiety preferencji",
"remoteExitNodeNetworkingLabelsDescription": "Strony z tymi etykietami będą zmuszone do połączenia się przez ten zdalny węzeł wyjściowy.",
"remoteExitNodeNetworkingLabelsDescription": "Strony z tymi etykietami będą preferować połączenie przez ten zdalny węzeł wyjściowy.",
"remoteExitNodeNetworkingLabelsButtonText": "Wybierz etykiety...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Szukaj etykiet...",
"remoteExitNodeNetworkingLabelsLoadError": "Nie udało się załadować etykiet",
@@ -3083,9 +3114,9 @@
"regionWesternEurope": "Europa Zachodnia",
"regionOceania": "Oceania",
"regionAustraliaAndNewZealand": "Australia i Nowa Zelandia",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"regionMelanesia": "Melanezja",
"regionMicronesia": "Mikronezja",
"regionPolynesia": "Polinezja",
"managedSelfHosted": {
"title": "Zarządzane Samodzielnie-Hostingowane",
"description": "Większa niezawodność i niska konserwacja serwera Pangolin z dodatkowymi dzwonkami i sygnałami",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Usuń",
"idpGoogleConfiguration": "Konfiguracja Google",
"idpGoogleConfigurationDescription": "Skonfiguruj dane logowania Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Identyfikator klienta Google OAuth2",
"idpGoogleClientSecretDescription": "Klucz tajny klienta Google OAuth2",
"idpAzureConfiguration": "Konfiguracja Azure Entra ID",
"idpAzureConfigurationDescription": "Skonfiguruj poświadczenia Aure Entra ID OAuth2",
"idpTenantId": "ID Najemcy",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "identyfikator-najemcy",
"idpAzureTenantIdDescription": "Identyfikator dzierżawcy azure (znaleziony w Azuure Active Directory podglądu)",
"idpAzureClientIdDescription": "Identyfikator klienta aplikacji Azure",
"idpAzureClientSecretDescription": "Klucz tajny klienta aplikacji Azure",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "Identyfikator klienta aplikacji Azure",
"idpAzureClientSecretDescription2": "Klucz tajny klienta aplikacji Azure",
"idpGoogleDescription": "Dostawca Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Dostawca Microsoft Azure OAuth2/OIDC",
"subnet": "Podsieć",
"utilitySubnet": "Użyteczna podsieć",
"subnetDescription": "Podsieć dla konfiguracji sieci tej organizacji.",
@@ -3470,6 +3501,7 @@
"priority": "Priorytet",
"priorityDescription": "Najpierw oceniane są trasy priorytetowe. Priorytet = 100 oznacza automatyczne zamawianie (decyzje systemowe). Użyj innego numeru, aby wyegzekwować ręczny priorytet.",
"instanceName": "Nazwa instancji",
"clearInstanceName": "Reset Server Association",
"pathMatchModalTitle": "Skonfiguruj dopasowanie ścieżki",
"pathMatchModalDescription": "Skonfiguruj sposób dopasowania przychodzących żądań na podstawie ich ścieżki.",
"pathMatchType": "Typ dopasowania",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Dozwolone przez regułę",
"allowedNoAuth": "Dozwolone Brak Auth",
"validAccessToken": "Ważny token dostępu",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Ważny Nagłówek Auth",
"validPincode": "Prawidłowy kod PIN",
"validPassword": "Prawidłowe hasło",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Ważny email",
"validSSO": "Prawidłowe SSO",
"validVirtualAPIKey": "Ważny Wirtualny Klucz API",
"view": "Zobacz",
"configManaged": "Konfiguracja zarządzana",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Upuszczone przez regułę",
"noSessions": "Brak sesji",
"temporaryRequestToken": "Tymczasowy token żądania",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Brak Ważnej Autoryzacji",
"ip": "IP",
"reason": "Powód",
"requestLogs": "Dzienniki żądań HTTP",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Regeneracja poświadczeń spowoduje unieważnienie poprzednich danych i spowoduje rozłączenie. Upewnij się, że aktualizacja wszystkich konfiguracji, które używają tych poświadczeń.",
"confirm": "Potwierdź",
"regenerateCredentialsConfirmation": "Czy na pewno chcesz wygenerować dane logowania?",
"endpoint": "Endpoint",
"endpoint": "Koniec punktu",
"Id": "Id",
"SecretKey": "Sekretny klucz",
"niceId": "Niepoprawne ID",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Wystąpił błąd podczas aktualizowania Nicei ID.",
"niceIdCannotBeEmpty": "Niepoprawny identyfikator nie może być pusty",
"enterIdentifier": "Wprowadź identyfikator",
"identifier": "Identifier",
"identifier": "Identyfikator",
"deviceLoginUseDifferentAccount": "Nie ty? Użyj innego konta.",
"deviceLoginDeviceRequestingAccessToAccount": "Urządzenie żąda dostępu do tego konta.",
"loginSelectAuthenticationMethod": "Wybierz metodę uwierzytelniania aby kontynuować.",
"noData": "Brak danych",
"machineClients": "Klienci maszyn",
"install": "Zainstaluj",
"downloadInstaller": "Download Installer",
"run": "Uruchom",
"envFile": "Plik środowiska",
"serviceFile": "Plik serwisu",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Wersja jądra",
"deviceModel": "Model urządzenia",
"serialNumber": "Numer seryjny",
"hostname": "Hostname",
"hostname": "Nazwa hosta",
"firstSeen": "Widziany po raz pierwszy",
"lastSeen": "Ostatnio widziane",
"biometricsEnabled": "Biometria włączona",
@@ -3966,6 +3999,7 @@
"disconnected": "Rozłączony",
"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.",
"approvalsEmptyStateHowToTitle": "How to Enable",
"approvalsEmptyStateStep1Title": "Przejdź do ról",
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
@@ -4008,8 +4042,8 @@
"s3DestTabFormat": "Format",
"s3DestNameLabel": "Nazwa",
"s3DestNamePlaceholder": "Moje miejsce docelowe S3",
"s3DestAccessKeyIdLabel": "AWS Access Key ID",
"s3DestSecretAccessKeyLabel": "AWS Secret Access Key",
"s3DestAccessKeyIdLabel": "AWS Identyfikator dostępu do klucza",
"s3DestSecretAccessKeyLabel": "AWS Sekretny Klucz Dostępu",
"s3DestSecretAccessKeyPlaceholder": "Twój AWS Secret Access Key",
"s3DestRegionLabel": "Region AWS",
"s3DestBucketLabel": "Nazwa kubła",
@@ -4158,7 +4192,7 @@
"webhookUrlLabel": "URL",
"webhookHeaderKeyPlaceholder": "Klucz",
"webhookHeaderValuePlaceholder": "Wartość",
"alertLabel": "Alert",
"alertLabel": "Alarm",
"domainPickerWildcardSubdomainNotAllowed": "Uniwersalne subdomeny nie są dozwolone.",
"domainPickerWildcardCertWarning": "Uniwersalne zasoby mogą wymagać dodatkowej konfiguracji, aby działać poprawnie.",
"domainPickerWildcardCertWarningLink": "Dowiedz się więcej",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Przeglądaj jako Administrator",
"resourceLauncherResourceDetailsDescription": "Informacje i status połączenia dla tego zasobu.",
"resourceLauncherResourceDetails": "Szczegóły zasobów",
"resourceLauncherSitesDescription": "Zasoby są dostępne na następujących stronach.",
"resourceLauncherViewSiteAsAdmin": "Przeglądaj stronę jako Administrator",
"resourceLauncherFilterBySite": "Filtruj według strony",
"resourceLauncherSshCommand": "Komenda SSH",
"resourceLauncherSshCommandDescription": "Użyj CLI Pangolin, aby otworzyć sesję SSH dla tego zasobu.",
"resourceLauncherAuthMethodsDescription": "Metody uwierzytelniania włączone dla tego zasobu.",
"resourceLauncherPrivateClientRequired": "Połącz się z klientem na swoim urządzeniu, aby uzyskać dostęp do tego zasobu prywatnie.",
"resourceLauncherPrivateClientRequiredTitle": "Wymagane połączenie z klientem",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Tryb klawiatury Unicode",
"sessionToolbarShow": "Pokaż pasek narzędzi",
"sessionToolbarHide": "Ukryj pasek narzędzi",
"actionUpdateSiteApprovals": "Zaktualizuj zgody na stronę"
"actionUpdateSiteApprovals": "Zaktualizuj zgody na stronę",
"check": "Sprawdź"
}
+72 -32
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Alvos neste site",
"siteSetting": "Configurações do {siteName}",
"siteNewtTunnel": "Novo Site (Recomendado)",
"pangolinSite": "Site Pangolin",
"siteNewtTunnelDescription": "Maneira mais fácil de criar um ponto de entrada em qualquer rede. Nenhuma configuração extra.",
"siteWg": "WireGuard Básico",
"siteWgDescription": "Use qualquer cliente do WireGuard para estabelecer um túnel. Configuração manual NAT é necessária.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Excluir Chave API",
"apiKeysManage": "Gerir Chaves API",
"apiKeysDescription": "As chaves API são usadas para autenticar com a API de integração",
"orgsManage": "Gerir Organizações",
"orgsDescription": "Ver e gerir todas as organizações nesta instância",
"provisioningKeysTitle": "Chave de provisionamento",
"provisioningKeysManage": "Gerenciar chaves de provisionamento",
"provisioningKeysDescription": "Chaves de provisionamento são usadas para autenticar o provisionamento automatizado do site para sua organização.",
@@ -716,6 +719,7 @@
"nameOptional": "Nome (Opcional)",
"accessControls": "Controlos de Acesso",
"userDescription2": "Gerir as configurações deste utilizador",
"userGeneralSettingsDescription": "Gerir os papéis e configurações deste usuário na organização",
"accessRoleErrorAdd": "Falha ao adicionar utilizador à função",
"accessRoleErrorAddDescription": "Ocorreu um erro ao adicionar utilizador à função.",
"userSaved": "Usuário salvo",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Nome do Servidor TLS inválido. Use o formato de nome de domínio ou salve vazio para remover o Nome do Servidor TLS.",
"proxyEnableSSL": "Ativar TLS",
"proxyEnableSSLDescription": "Habilitar criptografia SSL/TLS para conexões HTTPS seguras aos alvos.",
"target": "Target",
"target": "Alvo",
"configureTarget": "Configurar Alvos",
"targetErrorFetch": "Falha ao buscar alvos",
"targetErrorFetchDescription": "Ocorreu um erro ao buscar alvos",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Quando as regras estão desativadas, todo o tráfego passa para a autenticação.",
"policyAccessRulesFallthroughOn": "Quando nenhuma regra corresponde, o tráfego passa para a autenticação.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/administrador/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Guardar Regras",
"resourceErrorCreate": "Erro ao criar recurso",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Saiba mais sobre JMESPath",
"idpJmespathLabel": "Caminho do Identificador",
"idpJmespathLabelDescription": "O JMESPath para o identificador do utilizador no token ID",
"idpIdentifierChangeTitle": "Aviso de Mudança no Caminho do Identificador",
"idpIdentifierChangeDescription": "Você está prestes a mudar o caminho do identificador. Isso afetará como os usuários existentes são mapeados. Usuários que anteriormente fizeram login através deste provedor de identidade podem não ser mais reconhecidos como os mesmos usuários.",
"idpIdentifierChangeConfirmMessage": "Eu confirmo",
"idpIdentifierChangeWarningText": "Isto afetará como os usuários existentes são mapeados",
"idpJmespathEmailPathOptional": "Caminho do Email (Opcional)",
"idpJmespathEmailPathOptionalDescription": "O JMESPath para o email do utilizador no token ID",
"idpJmespathNamePathOptional": "Caminho do Nome (Opcional)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Gerar código de redefinição de senha",
"passwordResetCodeGenerated": "Código de redefinição de senha gerado",
"passwordResetCodeGeneratedDescription": "Compartilhe este código com o usuário. Eles podem usá-lo para redefinir sua senha.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "Redefinir URL",
"passwordNew": "Nova Palavra-passe",
"passwordNewConfirm": "Confirmar Nova Palavra-passe",
"changePassword": "Mudar a senha",
@@ -1420,6 +1428,24 @@
"logoutError": "Erro ao terminar sessão",
"signingAs": "Sessão iniciada como",
"serverAdmin": "Administrador do Servidor",
"promoteServerAdmin": "Promover a Administrador do Servidor",
"promoteServerAdminTitle": "Promover a Administrador do Servidor",
"promoteServerAdminQuestion": "Tem certeza que deseja promover {selectedUser} a administrador do servidor?",
"promoteServerAdminMessage": "Os administradores de servidor têm os privilégios mais altos e podem gerir o servidor.",
"promoteServerAdminWarning": "Isso pode ser desfeito a qualquer momento ao rebaixar o usuário.",
"promoteServerAdminConfirm": "Promover a Administrador do Servidor",
"promoteServerAdminSuccess": "Usuário Promovido",
"promoteServerAdminSuccessDescription": "{selectedUser} agora é um administrador do servidor.",
"promoteServerAdminError": "Falha ao promover o usuário",
"demoteServerAdmin": "Rebaixar de Administrador do Servidor",
"demoteServerAdminTitle": "Rebaixar de Administrador do Servidor",
"demoteServerAdminQuestion": "Tem certeza que deseja rebaixar {selectedUser} de administrador do servidor?",
"demoteServerAdminMessage": "{selectedUser} perderá todos os privilégios de administrador do servidor.",
"demoteServerAdminWarning": "Isso pode ser desfeito a qualquer momento ao promover o usuário.",
"demoteServerAdminConfirm": "Rebaixar de administrador do servidor",
"demoteServerAdminSuccess": "Usuário Rebaixado",
"demoteServerAdminSuccessDescription": "{selectedUser} não é mais um administrador do servidor.",
"demoteServerAdminError": "Falha ao rebaixar o usuário",
"managedSelfhosted": "Gerenciado Auto-Hospedado",
"otpEnable": "Ativar Autenticação de Dois Fatores",
"otpDisable": "Desativar Autenticação de Dois Fatores",
@@ -1573,6 +1599,8 @@
"search": "Pesquisar…",
"searchPlaceholder": "Buscar...",
"emptySearchOptions": "Nenhuma opção encontrada",
"ipFilterSearchPlaceholder": "Insira um endereço IP…",
"ipFilterEmptyMessage": "Insira um endereço IP para filtrar",
"create": "Criar",
"orgs": "Organizações",
"loginError": "Ocorreu um erro inesperado. Por favor, tente novamente.",
@@ -1791,7 +1819,7 @@
"aiClientConfigTabManual": "Configuração Manual",
"aiClientConfigPreset": "Configuração",
"aiClientConfigStep": "Etapa {number}",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigEndpointPlaceholder": "https://exemplo.recurso.url.com",
"aiClientConfigPlaceholderWarning": "Este snippet inclui valores de exemplo, como um nome de modelo ou URL de recurso. Substitua-os pelos seus antes de usá-lo.",
"aiClientConfigRevealError": "Não foi possível carregar sua chave API.",
"aiClientConfigRevealRetry": "Tente novamente",
@@ -1831,7 +1859,7 @@
"aiBudgetPeriodYearly": "Anual",
"aiBudgetPeriodLifetime": "Por toda a vida",
"aiBudgetUnitUsd": "USD",
"aiBudgetUnitTokens": "Tokens",
"aiBudgetUnitTokens": "Identificadores",
"aiBudgetConflictError": "Já existe um orçamento para este período de reinicialização e tipo de gasto",
"aiBudgetInvalidAmountError": "Digite um gasto máximo maior que 0",
"aiBudgetUpdated": "Orçamentos atualizados",
@@ -1842,7 +1870,7 @@
"aiProviderTypeOpenai": "OpenAI",
"aiProviderTypeAnthropic": "Antropic",
"aiProviderTypeGoogleGemini": "Google Gemini",
"aiProviderTypeVertexAi": "Vertex AI",
"aiProviderTypeVertexAi": "Vertex IA",
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
@@ -1899,7 +1927,7 @@
"aiProviderBudgetAmount": "Valor do Orçamento",
"aiProviderBudgetUnit": "Unidade de Orçamento",
"aiProviderBudgetUnitUsd": "USD",
"aiProviderBudgetUnitTokens": "Tokens",
"aiProviderBudgetUnitTokens": "Identificadores",
"aiProviderEnabled": "Ativado",
"aiProviderEnabledDescription": "Desativar totalmente este provedor em todos os recursos",
"aiProviderErrorCreate": "Falha ao criar provedor de IA",
@@ -2047,7 +2075,7 @@
"aiUsageTokenTypeReasoning": "Raciocínio",
"aiUsageRequests": "Solicitações",
"aiUsageCost": "Custo",
"aiUsageTokens": "Tokens",
"aiUsageTokens": "Identificadores",
"aiUsageOther": "Outro",
"aiUsageTotalRequests": "Total de Solicitações",
"aiUsageTotalTokens": "Total de Tokens",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Orçamento",
"resourceBudgetSettingsDescription": "Configure como este gateway de IA restringe o uso com base em gastos ou limites de tokens",
"sidebarApiKeys": "Chaves API",
"sidebarOrgs": "Organizações",
"sidebarProvisioning": "Provisionamento",
"sidebarSettings": "Configurações",
"sidebarAllUsers": "Todos os utilizadores",
@@ -2347,7 +2376,7 @@
"containerImage": "Imagem:",
"containerState": "Estado:",
"containerNetworks": "Redes",
"containerHostnameIp": "Hostname/IP",
"containerHostnameIp": "Nome do host/IP",
"containerLabels": "Marcadores",
"containerLabelsCount": "{count, plural, one {# rótulo} other {# rótulos}}",
"containerLabelsTitle": "Etiquetas do Contêiner",
@@ -2596,6 +2625,7 @@
"createDomainType": "Tipo:",
"createDomainName": "Nome:",
"createDomainValue": "Valor:",
"multiSelectFilterCount": "{count} selecionado",
"createDomainCnameRecords": "Registros CNAME",
"createDomainARecords": "Registros A",
"createDomainRecordNumber": "Registrar {number}",
@@ -2654,7 +2684,7 @@
"keepMeInTheLoop": "Mantenha-me à disposição com notícias, atualizações e novos recursos por e-mail."
},
"siteRequired": "Site é obrigatório.",
"olmTunnel": "Olm Tunnel",
"olmTunnel": "Túnel Olm",
"olmTunnelDescription": "Use Olm para conectividade do cliente",
"errorCreatingClient": "Erro ao criar cliente",
"clientDefaultsNotFound": "Padrões do cliente não encontrados",
@@ -2710,6 +2740,7 @@
"healthScheme": "Método",
"healthSelectScheme": "Selecione o Método",
"healthCheckPortInvalid": "A porta deve estar entre 1 e 65535",
"healthCheckHostnameInvalid": "O nome do host não deve conter espaços em branco",
"healthCheckPath": "Caminho",
"healthHostname": "IP / Nome do Host",
"healthPort": "Porta",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Nenhum recurso de proxy encontrado.",
"resourcesTableNoInternalResourcesFound": "Nenhum recurso privado encontrado.",
"resourcesTableDestination": "Destino",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Apelido",
"resourcesTableAliasAddress": "Endereço do Pseudônimo",
"resourcesTableAliasAddressInfo": "Este endereço faz parte da sub-rede de utilitários da organização. É usado para resolver registros de alias usando resolução de DNS interno.",
"resourcesTableClients": "Clientes",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "O endereço IP ou o nome do host do recurso na rede do site.",
"editInternalResourceDialogDestinationIPDescription": "O IP ou endereço do hostname do recurso na rede do site.",
"editInternalResourceDialogDestinationCidrDescription": "A faixa CIDR do recurso na rede do site.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Apelido",
"editInternalResourceDialogAliasDescription": "Um alias de DNS interno opcional para este recurso.",
"createInternalResourceDialogNoSitesAvailable": "Nenhum Site Disponível",
"createInternalResourceDialogNoSitesAvailableDescription": "Você precisa ter pelo menos um site Newt com uma sub-rede configurada para criar recursos privados.",
@@ -2899,7 +2930,7 @@
"privateResourceAllowIcmpPing": "Permitir ICMP Ping",
"privateResourceNetworkAccess": "Acesso à Rede",
"privateResourceNetworkAccessDescription": "Controlar o acesso à porta TCP/UDP e se o ICMP ping é permitido para este recurso.",
"hostSettings": "Host",
"hostSettings": "Servidor",
"cidrSettings": "CIDR",
"createInternalResourceDialogResourceProperties": "Propriedades do Recurso",
"createInternalResourceDialogName": "Nome",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Destino",
"createInternalResourceDialogDestinationHostDescription": "O endereço IP ou o nome do host do recurso na rede do site.",
"createInternalResourceDialogDestinationCidrDescription": "A faixa CIDR do recurso na rede do site.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Apelido",
"createInternalResourceDialogAliasDescription": "Um alias de DNS interno opcional para este recurso.",
"internalResourceAliasLocalWarning": "Os aliases terminando em .local podem causar problemas de resolução devido ao mDNS em algumas redes.",
"internalResourceDownstreamSchemeRequired": "Esquema é obrigatório para recursos HTTP",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Adicione um intervalo de CIDR (por exemplo, 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Falha ao carregar sub-redes",
"remoteExitNodeNetworkingLabelsTitle": "Etiquetas de Preferência",
"remoteExitNodeNetworkingLabelsDescription": "Os sites com essas etiquetas serão forçados a se conectar através deste nó de saída remoto.",
"remoteExitNodeNetworkingLabelsDescription": "Sites com estas etiquetas preferirão conectar-se por meio deste nó de saída remoto.",
"remoteExitNodeNetworkingLabelsButtonText": "Selecionar etiquetas...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Pesquisar etiquetas...",
"remoteExitNodeNetworkingLabelsLoadError": "Falha ao carregar etiquetas",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Remover",
"idpGoogleConfiguration": "Configuração do Google",
"idpGoogleConfigurationDescription": "Configurar as credenciais do Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "ID de Cliente OAuth2 do Google",
"idpGoogleClientSecretDescription": "Segredo de cliente OAuth2 do Google",
"idpAzureConfiguration": "Configuração de ID do Azure Entra",
"idpAzureConfigurationDescription": "Configurar credenciais do Azure Entra ID OAuth2",
"idpTenantId": "ID do Inquilino",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "id do inquilino",
"idpAzureTenantIdDescription": "ID do tenant Azure (encontrado na visão geral do diretório ativo Azure)",
"idpAzureClientIdDescription": "ID cliente de registro do aplicativo Azure",
"idpAzureClientSecretDescription": "Segredo cliente de registro do Azure App",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "ID cliente de registro do aplicativo Azure",
"idpAzureClientSecretDescription2": "Segredo cliente de registro do Azure App",
"idpGoogleDescription": "Provedor Google OAuth2/OIDC",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Provedor Microsoft Azure OAuth2/OIDC",
"subnet": "Sub-rede",
"utilitySubnet": "Sub-rede de utilidade",
"subnetDescription": "A sub-rede para a configuração de rede dessa organização.",
@@ -3220,7 +3251,7 @@
"domainPickerFreeDomainsPaidFeature": "Os domínios fornecidos são um recurso pago. Assine para obter um domínio incluído no seu plano - não há necessidade de trazer o seu próprio.",
"domainPickerVerified": "Verificada",
"domainPickerUnverified": "Não verificado",
"domainPickerManual": "Manual",
"domainPickerManual": "Manualmente",
"domainPickerInvalidSubdomainStructure": "Caracteres inválidos serão sanitizados ao serem salvos.",
"domainPickerError": "ERRO",
"domainPickerErrorLoadDomains": "Falha ao carregar domínios da organização",
@@ -3470,6 +3501,7 @@
"priority": "Prioridade",
"priorityDescription": "Rotas de alta prioridade são avaliadas primeiro. Prioridade = 100 significa ordem automática (decisões do sistema). Use outro número para aplicar prioridade manual.",
"instanceName": "Nome da Instância",
"clearInstanceName": "Resetar Associação do Servidor",
"pathMatchModalTitle": "Configurar Correspondência de Caminho",
"pathMatchModalDescription": "Configure como as solicitações de entrada devem ser correspondidas com base no caminho.",
"pathMatchType": "Tipo de Correspondência",
@@ -3516,7 +3548,7 @@
"searchLogs": "Pesquisar registros...",
"action": "Acão",
"actor": "Ator",
"timestamp": "Timestamp",
"timestamp": "Carimbo de data/hora",
"accessLogs": "Logs de Acesso",
"exportCsv": "Exportar como CSV",
"exportError": "Erro desconhecido ao exportar CSV",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Permitido por regra",
"allowedNoAuth": "Não Permitido Nenhuma Autenticação",
"validAccessToken": "Token de acesso válido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Autenticação de Cabeçalho Válido",
"validPincode": "Código PIN Válido",
"validPassword": "Senha válida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Email válido",
"validSSO": "SSO Válido",
"validVirtualAPIKey": "Chave de API Virtual Válida",
"view": "Visualizar",
"configManaged": "Configuração Gerenciada",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Derrubado pela regra",
"noSessions": "Sem Sessões",
"temporaryRequestToken": "Token de solicitação temporária",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Sem Autenticação Válida",
"ip": "PI",
"reason": "Motivo",
"requestLogs": "Registros de Pedidos HTTP",
@@ -3566,7 +3598,7 @@
"model": "Modelo",
"virtualApiKey": "Chave de API Virtual",
"noVirtualApiKey": "Sem chave de API virtual",
"stream": "Stream",
"stream": "Transmissão",
"streaming": "Transmitindo",
"nonStreaming": "Não-streaming",
"statusCode": "Código de Status",
@@ -3743,7 +3775,7 @@
"confirm": "Confirmar",
"regenerateCredentialsConfirmation": "Você tem certeza que deseja recriar as credenciais?",
"endpoint": "Endpoint",
"Id": "Id",
"Id": "ID",
"SecretKey": "Chave secreta",
"niceId": "Belo ID",
"niceIdUpdated": "Bom ID atualizado",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Ocorreu um erro ao atualizar a ID de Nice.",
"niceIdCannotBeEmpty": "Bom ID não pode estar vazio",
"enterIdentifier": "Inserir identificador",
"identifier": "Identifier",
"identifier": "Identificador",
"deviceLoginUseDifferentAccount": "Não é você? Use uma conta diferente.",
"deviceLoginDeviceRequestingAccessToAccount": "Um dispositivo está solicitando acesso a essa conta.",
"loginSelectAuthenticationMethod": "Selecione um método de autenticação para continuar.",
"noData": "Nenhum dado encontrado",
"machineClients": "Clientes de máquina",
"install": "Instale",
"downloadInstaller": "Baixar Instalador",
"run": "Executar",
"envFile": "Arquivo de Ambiente",
"serviceFile": "Arquivo de Serviço",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Versão do Kernel",
"deviceModel": "Modelo do dispositivo",
"serialNumber": "Número de Série",
"hostname": "Hostname",
"hostname": "Nome do Host",
"firstSeen": "Visto primeiro",
"lastSeen": "Visto por último",
"biometricsEnabled": "Biometria habilitada",
@@ -3966,6 +3999,7 @@
"disconnected": "Desconectado",
"approvalsEmptyStateTitle": "Aprovações do dispositivo não habilitado",
"approvalsEmptyStateDescription": "Habilitar aprovações do dispositivo para cargos que exigem aprovação do administrador antes que os usuários possam conectar novos dispositivos.",
"approvalsEmptyStateHowToTitle": "Como Habilitar",
"approvalsEmptyStateStep1Title": "Ir para Funções",
"approvalsEmptyStateStep1Description": "Navegue até as configurações dos papéis da sua organização para configurar as aprovações de dispositivo.",
"approvalsEmptyStateStep2Title": "Habilitar Aprovações do Dispositivo",
@@ -4106,7 +4140,7 @@
"healthCheckTabConnection": "Conexão",
"healthCheckTabAdvanced": "Avançado",
"healthCheckStrategyNotAvailable": "Esta estratégia não está disponível. Por favor, contacte vendas para ativar esta funcionalidade.",
"uptime30d": "Uptime (30d)",
"uptime30d": "Tempo de atividade (30d)",
"idpAddActionCreateNew": "Criar novo provedor de identidade",
"idpAddActionImportFromOrg": "Importar de outra organização",
"idpImportDialogTitle": "Importar Provedor de Identidade",
@@ -4126,7 +4160,7 @@
"idpUnassociatedDescription": "Provedor de identidade desassociado desta organização com sucesso",
"idpUnassociateMenu": "Desassociar",
"idpDeleteAllOrgsMenu": "Excluir",
"publicIpEndpoint": "Endpoint",
"publicIpEndpoint": "Ponto de extremidade",
"lastTriggeredAt": "Último Gatilho",
"reject": "Rejeitar",
"uptimeDaysAgo": "há {count} dias",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Visualizar como Administrador",
"resourceLauncherResourceDetailsDescription": "Informações de conexão e status para este recurso.",
"resourceLauncherResourceDetails": "Detalhes do Recurso",
"resourceLauncherSitesDescription": "O recurso está acessível através dos seguintes sites.",
"resourceLauncherViewSiteAsAdmin": "Visualizar Site como Administrador",
"resourceLauncherFilterBySite": "Filtrar por Site",
"resourceLauncherSshCommand": "Comando SSH",
"resourceLauncherSshCommandDescription": "Use o CLI do Pangolin para abrir uma sessão SSH para este recurso.",
"resourceLauncherAuthMethodsDescription": "Métodos de autenticação habilitados para este recurso.",
"resourceLauncherPrivateClientRequired": "Conecte com um cliente no seu dispositivo para acessar este recurso privadamente.",
"resourceLauncherPrivateClientRequiredTitle": "Conexão do Cliente Obrigatória",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Modo de teclado Unicode",
"sessionToolbarShow": "Mostrar barra de ferramentas",
"sessionToolbarHide": "Ocultar barra de ferramentas",
"actionUpdateSiteApprovals": "Atualizar Aprovações do Site"
"actionUpdateSiteApprovals": "Atualizar Aprovações do Site",
"check": "Verificar"
}
+72 -32
View File
@@ -51,7 +51,7 @@
"inviteNotAccepted": "Приглашение не принято",
"authCreateAccount": "Создайте учётную запись для начала работы",
"authNoAccount": "Нет учётной записи?",
"email": "Email",
"email": "Электронная почта",
"password": "Пароль",
"confirmPassword": "Подтвердите пароль",
"createAccount": "Создать учётную запись",
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Цели на этом сайте",
"siteSetting": "Настройки {siteName}",
"siteNewtTunnel": "Новый сайт (рекомендуется)",
"pangolinSite": "Сайт Pangolin",
"siteNewtTunnelDescription": "Самый простой способ создать точку входа в любую сеть. Дополнительная настройка не требуется.",
"siteWg": "Базовый WireGuard",
"siteWgDescription": "Используйте любой клиент WireGuard для открытия туннеля. Требуется ручная настройка NAT.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "Удаление ключа API",
"apiKeysManage": "Управление ключами API",
"apiKeysDescription": "Ключи API используются для аутентификации в интеграционном API",
"orgsManage": "Управление организациями",
"orgsDescription": "Просмотр и управление всеми организациями в этой инстанции",
"provisioningKeysTitle": "Ключ подготовки",
"provisioningKeysManage": "Управление ключами подготовки",
"provisioningKeysDescription": "Ключи подготовки используются для аутентификации автоматического обеспечения сайта для вашей организации.",
@@ -552,7 +555,7 @@
"licenseBannerDescription": "Откройте доступ к корпоративным функциям для вашей локально размещаемой версии Pangolin. Приобретите лицензионный ключ, чтобы активировать премиум-функции, затем добавьте его ниже.",
"licenseBannerGetLicense": "Получить лицензию",
"licenseBannerViewDocs": "Посмотреть документацию",
"communityEdition": "Community Edition",
"communityEdition": "Издание для сообщества",
"licenseAboutDescription": "Это для бизнес и корпоративных пользователей, использующих Pangolin в коммерческой среде. Если вы используете Pangolin для личного использования, вы можете игнорировать этот раздел.",
"licenseKeyActivated": "Лицензионный ключ активирован",
"licenseKeyActivatedDescription": "Лицензионный ключ был успешно активирован.",
@@ -716,6 +719,7 @@
"nameOptional": "Имя (необязательно)",
"accessControls": "Контроль доступа",
"userDescription2": "Управление настройками этого пользователя",
"userGeneralSettingsDescription": "Управление ролями и настройками этого пользователя в организации",
"accessRoleErrorAdd": "Не удалось добавить пользователя в роль",
"accessRoleErrorAddDescription": "Произошла ошибка при добавлении пользователя в роль.",
"userSaved": "Пользователь сохранён",
@@ -736,7 +740,7 @@
"proxyErrorTls": "Неверное имя TLS сервера. Используйте формат доменного имени или оставьте пустым для удаления имени TLS сервера.",
"proxyEnableSSL": "Включить TLS",
"proxyEnableSSLDescription": "Включить шифрование SSL/TLS для безопасных HTTPS соединений с целями.",
"target": "Target",
"target": "Цель",
"configureTarget": "Настроить адресаты",
"targetErrorFetch": "Не удалось получить цели",
"targetErrorFetchDescription": "Произошла ошибка при получении целей",
@@ -912,8 +916,8 @@
"policyAccessRulesFallthroughOff": "Когда правила отключены, весь трафик проходит для аутентификации.",
"policyAccessRulesFallthroughOn": "Когда правило не совпадает, трафик проходит для аутентификации.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesPlaceholderPath": "/админ/*",
"rulesPlaceholderGeo": "РУ, КН",
"rulesSave": "Сохранить правила",
"resourceErrorCreate": "Ошибка при создании ресурса",
"resourceErrorCreateDescription": "Произошла ошибка при создании ресурса",
@@ -948,7 +952,7 @@
"unknownCommand": "Неизвестная команда",
"newtErrorFetchReleases": "Не удалось получить информацию о релизе: {err}",
"newtErrorFetchLatest": "Ошибка при получении последнего релиза: {err}",
"newtEndpoint": "Endpoint",
"newtEndpoint": "Конечная точка",
"newtId": "ID",
"newtSecretKey": "Секретный ключ",
"newtVersion": "Версия",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "Узнать больше о JMESPath",
"idpJmespathLabel": "Путь идентификатора",
"idpJmespathLabelDescription": "Путь к идентификатору пользователя в ID токене",
"idpIdentifierChangeTitle": "Предупреждение о изменении пути идентификатора",
"idpIdentifierChangeDescription": "Вы собираетесь изменить путь идентификатора. Это повлияет на то, как отображаются существующие пользователи. Пользователи, которые ранее входили через этого поставщика идентификации, могут больше не распознаваться как те же пользователи.",
"idpIdentifierChangeConfirmMessage": "Я подтверждаю",
"idpIdentifierChangeWarningText": "Это повлияет на то, как отображаются существующие пользователи",
"idpJmespathEmailPathOptional": "Путь к email (необязательно)",
"idpJmespathEmailPathOptionalDescription": "Путь к email пользователя в ID токене",
"idpJmespathNamePathOptional": "Путь к имени (необязательно)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "Сгенерировать код сброса пароля",
"passwordResetCodeGenerated": "Код сброса пароля создан",
"passwordResetCodeGeneratedDescription": "Поделитесь этим кодом с пользователем. Они могут использовать его для сброса пароля.",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "Сброс URL",
"passwordNew": "Новый пароль",
"passwordNewConfirm": "Подтвердите новый пароль",
"changePassword": "Изменить пароль",
@@ -1420,6 +1428,24 @@
"logoutError": "Ошибка при выходе",
"signingAs": "Вы вошли как",
"serverAdmin": "Администратор сервера",
"promoteServerAdmin": "Повысить до администратора сервера",
"promoteServerAdminTitle": "Повысить до администратора сервера",
"promoteServerAdminQuestion": "Вы уверены, что хотите повысить {selectedUser} до администратора сервера?",
"promoteServerAdminMessage": "Администраторы серверов имеют наивысшие привилегии и могут управлять сервером.",
"promoteServerAdminWarning": "Это можно отменить в любое время, понизив пользователя.",
"promoteServerAdminConfirm": "Повысить до администратора сервера",
"promoteServerAdminSuccess": "Пользователь повышен",
"promoteServerAdminSuccessDescription": "{selectedUser} теперь администратор сервера.",
"promoteServerAdminError": "Не удалось повысить пользователя",
"demoteServerAdmin": "Понизить с администратора сервера",
"demoteServerAdminTitle": "Понизить с администратора сервера",
"demoteServerAdminQuestion": "Вы уверены, что хотите понизить {selectedUser} с администратора сервера?",
"demoteServerAdminMessage": "{selectedUser} потеряет все привилегии администратора сервера.",
"demoteServerAdminWarning": "Это можно отменить в любое время, повысив пользователя.",
"demoteServerAdminConfirm": "Понизить с администратора сервера",
"demoteServerAdminSuccess": "Пользователь понижен",
"demoteServerAdminSuccessDescription": "{selectedUser} больше не является администратором сервера.",
"demoteServerAdminError": "Не удалось понизить пользователя",
"managedSelfhosted": "Управляемый с самовывоза",
"otpEnable": "Включить Двухфакторную Аутентификацию",
"otpDisable": "Отключить двухфакторную аутентификацию",
@@ -1573,6 +1599,8 @@
"search": "Поиск…",
"searchPlaceholder": "Поиск...",
"emptySearchOptions": "Опции не найдены",
"ipFilterSearchPlaceholder": "Введите IP адрес…",
"ipFilterEmptyMessage": "Введите IP адрес для фильтрации",
"create": "Создать",
"orgs": "Организации",
"loginError": "Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз.",
@@ -1848,9 +1876,9 @@
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Шлюз Vercel AI",
"aiProviderTypeCustom": "Пользовательский",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
"aiProviderTypeGoogleGeminiDescription": "Google Gemini API",
"aiProviderTypeOpenaiDescription": "API OpenAI",
"aiProviderTypeAnthropicDescription": "API Anthropic",
"aiProviderTypeGoogleGeminiDescription": "API Google Gemini",
"aiProviderTypeVertexAiDescription": "API Google Vertex AI",
"aiProviderTypeBedrockDescription": "API Amazon Bedrock Runtime",
"aiProviderTypeMicrosoftFoundryDescription": "API Microsoft Foundry",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Бюджет",
"resourceBudgetSettingsDescription": "Настройте, как этот шлюз AI ограничивает использование на основе затрат или лимитов токенов",
"sidebarApiKeys": "API ключи",
"sidebarOrgs": "Организации",
"sidebarProvisioning": "Подготовка",
"sidebarSettings": "Настройки",
"sidebarAllUsers": "Все пользователи",
@@ -2351,7 +2380,7 @@
"containerLabels": "Метки",
"containerLabelsCount": "{count, plural, one {# метка} few {# метки} many {# меток} other {# меток}}",
"containerLabelsTitle": "Метки контейнера",
"containerLabelEmpty": "<empty>",
"containerLabelEmpty": "<пусто>",
"containerPorts": "Порты",
"containerPortsMore": "+{count} ещё",
"containerActions": "Действия",
@@ -2418,7 +2447,7 @@
"billing": "Выставление счетов",
"orgBillingDescription": "Управление платежной информацией и подписками",
"github": "GitHub",
"pangolinHosted": "Pangolin Hosted",
"pangolinHosted": "Панголин Hosted",
"fossorial": "Fossorial",
"completeAccountSetup": "Завершите настройку аккаунта",
"completeAccountSetupDescription": "Установите ваш пароль, чтобы начать",
@@ -2596,6 +2625,7 @@
"createDomainType": "Тип:",
"createDomainName": "Имя:",
"createDomainValue": "Значение:",
"multiSelectFilterCount": "Выбрано: {count}",
"createDomainCnameRecords": "CNAME Записи",
"createDomainARecords": "A Записи",
"createDomainRecordNumber": "Запись {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "Запустите Olm на вашей системе",
"clientOlmCredentials": "Полномочия",
"clientOlmCredentialsDescription": "Именно так клиент будет аутентифицироваться с сервером",
"olmEndpoint": "Endpoint",
"olmEndpoint": "Конечная точка",
"olmId": "ID",
"olmSecretKey": "Секретный ключ",
"clientCredentialsSave": "Сохранить учетные данные",
@@ -2710,6 +2740,7 @@
"healthScheme": "Метод",
"healthSelectScheme": "Выберите метод",
"healthCheckPortInvalid": "Порт должен быть в диапазоне от 1 до 65535",
"healthCheckHostnameInvalid": "Имя хоста не должно содержать пробелов",
"healthCheckPath": "Путь",
"healthHostname": "IP / хост",
"healthPort": "Порт",
@@ -2840,7 +2871,7 @@
"resourcesTableNoProxyResourcesFound": "Проксированных ресурсов не найдено.",
"resourcesTableNoInternalResourcesFound": "Частные ресурсы не найдены.",
"resourcesTableDestination": "Пункт назначения",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "Псевдоним",
"resourcesTableAliasAddress": "Псевдоним адреса",
"resourcesTableAliasAddressInfo": "Этот адрес является частью вспомогательной подсети организации. Он используется для разрешения псевдонимов с использованием внутреннего разрешения DNS.",
"resourcesTableClients": "Клиенты",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "IP адрес или имя хоста ресурса в сети сайта.",
"editInternalResourceDialogDestinationIPDescription": "IP или адрес хоста ресурса в сети сайта.",
"editInternalResourceDialogDestinationCidrDescription": "Диапазон CIDR ресурса в сети сайта.",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "Псевдоним",
"editInternalResourceDialogAliasDescription": "Дополнительный внутренний DNS псевдоним для этого ресурса.",
"createInternalResourceDialogNoSitesAvailable": "Нет доступных сайтов",
"createInternalResourceDialogNoSitesAvailableDescription": "Вам необходимо иметь хотя бы один сайт Newt с настроенной подсетью для создания частных ресурсов.",
@@ -2900,7 +2931,7 @@
"privateResourceNetworkAccess": "Сетевой доступ",
"privateResourceNetworkAccessDescription": "Управляйте доступом к портам TCP/UDP и настройте разрешение ICMP ping для данного ресурса.",
"hostSettings": "Хост",
"cidrSettings": "CIDR",
"cidrSettings": "СИДР",
"createInternalResourceDialogResourceProperties": "Свойства ресурса",
"createInternalResourceDialogName": "Имя",
"createInternalResourceDialogSite": "Сайт",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "Пункт назначения",
"createInternalResourceDialogDestinationHostDescription": "IP адрес или имя хоста ресурса в сети сайта.",
"createInternalResourceDialogDestinationCidrDescription": "Диапазон CIDR ресурса в сети сайта.",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "Псевдоним",
"createInternalResourceDialogAliasDescription": "Дополнительный внутренний DNS псевдоним для этого ресурса.",
"internalResourceAliasLocalWarning": "Псевдонимы, оканчивающиеся на .local, могут вызывать проблемы с разрешением из-за mDNS в некоторых сетях.",
"internalResourceDownstreamSchemeRequired": "Схема обязательна для HTTP ресурсов",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Добавить диапазон CIDR (например, 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Не удалось загрузить подсети",
"remoteExitNodeNetworkingLabelsTitle": "Этикетки предпочтений",
"remoteExitNodeNetworkingLabelsDescription": "Сайты с этими метками будут обязаны подключаться через этот удаленный узел выхода.",
"remoteExitNodeNetworkingLabelsDescription": "Сайты с такими метками предпочтут соединяться через этот удаленный узел выхода.",
"remoteExitNodeNetworkingLabelsButtonText": "Выберите метки...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Поиск меток...",
"remoteExitNodeNetworkingLabelsLoadError": "Не удалось загрузить метки",
@@ -3155,12 +3186,12 @@
"roleMappingRemoveRule": "Удалить",
"idpGoogleConfiguration": "Конфигурация Google",
"idpGoogleConfigurationDescription": "Настройка учетных данных Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "Ваш Google OAuth2 ID клиента",
"idpGoogleClientSecretDescription": "Секрет клиента Google OAuth2",
"idpAzureConfiguration": "Конфигурация Azure Entra ID",
"idpAzureConfigurationDescription": "Настройка учетных данных Azure Entra ID OAuth2",
"idpTenantId": "Идентификатор арендатора",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "идентификатор арендатора",
"idpAzureTenantIdDescription": "ID арендатора Azure (найден в обзоре Active Directory Azure)",
"idpAzureClientIdDescription": "Регистрационный номер клиента Azure App",
"idpAzureClientSecretDescription": "Секрет регистрации клиента Azure App",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "Регистрационный номер клиента Azure App",
"idpAzureClientSecretDescription2": "Секрет регистрации клиента Azure App",
"idpGoogleDescription": "Google OAuth2/OIDC провайдер",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC провайдер",
"subnet": "Подсеть",
"utilitySubnet": "Утилита подсети",
"subnetDescription": "Подсеть для конфигурации сети этой организации.",
@@ -3407,7 +3438,7 @@
},
"form": {
"useCaseQuestion": "Вы используете Pangolin для личного или делового использования?",
"firstName": "First Name",
"firstName": "Имя",
"lastName": "Фамилия",
"jobTitle": "Заголовок",
"primaryUseQuestion": "Что вы планируете использовать Панголин в первую очередь?",
@@ -3470,6 +3501,7 @@
"priority": "Приоритет",
"priorityDescription": "Маршруты с более высоким приоритетом оцениваются первым. Приоритет = 100 означает автоматическое упорядочение (решение системы). Используйте другой номер для обеспечения ручного приоритета.",
"instanceName": "Имя экземпляра",
"clearInstanceName": "Сбросить ассоциацию сервера",
"pathMatchModalTitle": "Настроить соответствие пути",
"pathMatchModalDescription": "Настройка соответствия входящих запросов на основе их пути.",
"pathMatchType": "Тип совпадения",
@@ -3493,7 +3525,7 @@
"pathRewriteStripPrefixOption": "Префикс вырезать - Удалить префикс",
"pathRewriteValue": "Перезаписать значение",
"pathRewriteRegexPlaceholder": "/new/$1",
"pathRewriteDefaultPlaceholder": "/new-path",
"pathRewriteDefaultPlaceholder": "/новый-путь",
"pathRewritePrefixHelp": "Заменить соответствующий префикс этим значением",
"pathRewriteExactHelp": "Замените весь путь этим значением, когда путь точно соответствует",
"pathRewriteRegexHelp": "Использовать группы захвата типа $1, $2 для замены",
@@ -3525,11 +3557,11 @@
"allowedByRule": "Разрешено правилом",
"allowedNoAuth": "Разрешено без авторизации",
"validAccessToken": "Действительный маркер доступа",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "Действительная аутентификация заголовка",
"validPincode": "Верный PIN-код",
"validPassword": "Допустимый пароль",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "Действительный email",
"validSSO": "Действительное SSO",
"validVirtualAPIKey": "Действительный виртуальный API ключ",
"view": "Просмотр",
"configManaged": "Конфигурация управляется",
@@ -3538,7 +3570,7 @@
"droppedByRule": "Отброшено по правилам",
"noSessions": "Нет сессий",
"temporaryRequestToken": "Временный токен запроса",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "Нет действительных методов аутентификации",
"ip": "IP",
"reason": "Причина",
"requestLogs": "HTTP Запросы Логи",
@@ -3742,7 +3774,7 @@
"regenerateCredentialsWarning": "Восстановление учётных данных приведет к аннулированию предыдущих учетных данных и отключению соединения. Убедитесь, что все конфигурации, использующие эти учетные данные.",
"confirm": "Подтвердить",
"regenerateCredentialsConfirmation": "Вы уверены, что хотите восстановить учетные данные?",
"endpoint": "Endpoint",
"endpoint": "Конечная точка",
"Id": "Id",
"SecretKey": "Секретный ключ",
"niceId": "Неплохой ID",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "Произошла ошибка при обновлении Nice ID.",
"niceIdCannotBeEmpty": "Неправильный ID не может быть пустым",
"enterIdentifier": "Введите идентификатор",
"identifier": "Identifier",
"identifier": "Идентификатор",
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
"loginSelectAuthenticationMethod": "Выберите метод аутентификации для продолжения.",
"noData": "Нет данных",
"machineClients": "Машинные клиенты",
"install": "Установить",
"downloadInstaller": "Скачать установщик",
"run": "Запустить",
"envFile": "Файл окружения",
"serviceFile": "Сервисный файл",
@@ -3936,7 +3969,7 @@
"kernelVersion": "Версия ядра",
"deviceModel": "Модель устройства",
"serialNumber": "Серийный номер",
"hostname": "Hostname",
"hostname": "Имя хоста",
"firstSeen": "Первый раз виден",
"lastSeen": "Последнее посещение",
"biometricsEnabled": "Включены биометрические данные",
@@ -3966,6 +3999,7 @@
"disconnected": "Отключено",
"approvalsEmptyStateTitle": "Утверждения устройства не включены",
"approvalsEmptyStateDescription": "Включите одобрение ролей для того, чтобы пользователи могли подключать новые устройства.",
"approvalsEmptyStateHowToTitle": "Как включить",
"approvalsEmptyStateStep1Title": "Перейти к ролям",
"approvalsEmptyStateStep1Description": "Перейдите в настройки ролей вашей организации для настройки утверждений устройств.",
"approvalsEmptyStateStep2Title": "Включить утверждения устройства",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Просмотр как администратор",
"resourceLauncherResourceDetailsDescription": "Информация о подключении и статус для данного ресурса.",
"resourceLauncherResourceDetails": "Детали ресурса",
"resourceLauncherSitesDescription": "Ресурс доступен на следующих сайтах.",
"resourceLauncherViewSiteAsAdmin": "Просмотр сайта как администратор",
"resourceLauncherFilterBySite": "Фильтр по сайту",
"resourceLauncherSshCommand": "SSH Команда",
"resourceLauncherSshCommandDescription": "Используйте Pangolin CLI для открытия SSH-сессии к этому ресурсу.",
"resourceLauncherAuthMethodsDescription": "Методы аутентификации, включенные для этого ресурса.",
"resourceLauncherPrivateClientRequired": "Подключитесь с клиентом на устройстве для доступа к этому ресурсу в частном порядке.",
"resourceLauncherPrivateClientRequiredTitle": "Требуется подключение клиента",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Режим клавиатуры Unicode",
"sessionToolbarShow": "Показать панель инструментов",
"sessionToolbarHide": "Скрыть панель инструментов",
"actionUpdateSiteApprovals": "Обновить утверждения сайта"
"actionUpdateSiteApprovals": "Обновить утверждения сайта",
"check": "Проверить"
}
+46 -6
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Bu sitedeki hedefler",
"siteSetting": "{siteName} Ayarları",
"siteNewtTunnel": "Newt Site (Önerilen)",
"pangolinSite": "Pangolin Sitesi",
"siteNewtTunnelDescription": "Ağınıza giriş noktası oluşturmanın en kolay yolu. Ekstra kurulum gerekmez.",
"siteWg": "Temel WireGuard",
"siteWgDescription": "Bir tünel oluşturmak için herhangi bir WireGuard istemcisi kullanın. Manuel NAT kurulumu gereklidir.",
@@ -465,6 +466,8 @@
"apiKeysDelete": "API Anahtarını Sil",
"apiKeysManage": "API Anahtarlarını Yönet",
"apiKeysDescription": "API anahtarları entegrasyon API'sini doğrulamak için kullanılır",
"orgsManage": "Örgütleri Yönet",
"orgsDescription": "Bu örnekteki tüm örgütleri görüntüle ve yönet",
"provisioningKeysTitle": "Tedarik Anahtarı",
"provisioningKeysManage": "Tedarik Anahtarlarını Yönet",
"provisioningKeysDescription": "Tedarik anahtarları, organizasyonunuz için otomatik site sağlama işlemini doğrulamak için kullanılır.",
@@ -716,6 +719,7 @@
"nameOptional": "İsim (İsteğe Bağlı)",
"accessControls": "Erişim Kontrolleri",
"userDescription2": "Bu kullanıcı üzerindeki ayarları yönetin",
"userGeneralSettingsDescription": "Bu kullanıcının örgütteki rollerini ve ayarlarını yönetin",
"accessRoleErrorAdd": "Kullanıcıyı role ekleme başarısız oldu",
"accessRoleErrorAddDescription": "Kullanıcı role eklenirken bir hata oluştu.",
"userSaved": "Kullanıcı kaydedildi",
@@ -758,7 +762,7 @@
"proxyUpdatedDescription": "Proxy ayarları başarıyla güncellendi",
"proxyErrorUpdate": "Proxy ayarları güncellenemedi",
"proxyErrorUpdateDescription": "Proxy ayarlarını güncellerken bir hata oluştu",
"targetAddr": "Host",
"targetAddr": "Sunucu",
"targetPort": "Bağlantı Noktası",
"targetProtocol": "Protokol",
"targetTlsSettings": "HTTPS & TLS Settings",
@@ -876,7 +880,7 @@
"policyAuthOrLogicBanner": "Ziyaretçiler aşağıdaki etkin yöntemlerden herhangi birini kullanarak kimlik doğrulaması yapabilirler. Hepsini tamamlamaları gerekmez.",
"policyAuthMethodActive": "Etkin",
"policyAuthMethodOff": "Kapalı",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoTitle": "Platform SSO'sunu Kullanın",
"policyAuthSsoDescription": "Organizasyonunuzun kimlik sağlayıcısı üzerinden oturum açmayı zorunlu kılın",
"policyAuthInferenceIdentityKeySignInUrl": "Giriş URL'si",
"policyAuthInferenceIdentityKeyHelpNoUrl": "Her kullanıcının zaten bir kimlik API anahtarı vardır, bu nedenle yalnızca kullanıcı olmayan istemciler veya ortak erişim için sanal API anahtarları oluşturmanız gerekir. Kullanıcılar bu kaynağın URL'si üzerinden kimlik sağlayıcılarıyla oturum açarak anahtarlarını alabilirler, bu girişten sonra gösterilecektir.",
@@ -912,7 +916,7 @@
"policyAccessRulesFallthroughOff": "Kurallar devre dışı bırakıldığında, tüm trafik kimlik doğrulamasına geçer.",
"policyAccessRulesFallthroughOn": "Herhangi bir kural eşleşmediğinde trafik kimlik doğrulamasına geçer.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderPath": "/yönetici/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Kuralları Kaydet",
"resourceErrorCreate": "Kaynak oluşturma hatası",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "JMESPath hakkında daha fazla bilgi edinin",
"idpJmespathLabel": "Tanımlayıcı Yolu",
"idpJmespathLabelDescription": "The JMESPath to the user identifier in the ID token",
"idpIdentifierChangeTitle": "Tanımlayıcı Yol Değişikliği Uyarısı",
"idpIdentifierChangeDescription": "Tanımlayıcı yolu değiştirmek üzeresiniz. Bu, mevcut kullanıcıların nasıl eşleneceğini etkileyecektir. Bu kimlik sağlayıcı üzerinden daha önce oturum açmış olan kullanıcılar artık aynı kullanıcılar olarak tanınmayabilir.",
"idpIdentifierChangeConfirmMessage": "Onaylıyorum",
"idpIdentifierChangeWarningText": "Bu, mevcut kullanıcıların nasıl eşleneceğini etkileyecek",
"idpJmespathEmailPathOptional": "E-posta Yolu (İsteğe Bağlı)",
"idpJmespathEmailPathOptionalDescription": "The JMESPath to the user's email in the ID token",
"idpJmespathNamePathOptional": "Ad Yolu (İsteğe Bağlı)",
@@ -1420,6 +1428,24 @@
"logoutError": "Çıkış yaparken hata",
"signingAs": "Olarak giriş yapıldı",
"serverAdmin": "Sunucu Yöneticisi",
"promoteServerAdmin": "Sunucu yöneticisine terfi et",
"promoteServerAdminTitle": "Sunucu Yöneticisine Terfi",
"promoteServerAdminQuestion": "{selectedUser} adresini sunucu yöneticisi olarak terfi etmek istediğinizden emin misiniz?",
"promoteServerAdminMessage": "Sunucu yöneticileri en yüksek ayrıcalıklara sahiptir ve sunucuyu yönetebilir.",
"promoteServerAdminWarning": "Bu, kullanıcıyı indirerek herhangi bir zamanda geri alınabilir.",
"promoteServerAdminConfirm": "Sunucu Yöneticisine Terfi",
"promoteServerAdminSuccess": "Kullanıcı Terfi Edildi",
"promoteServerAdminSuccessDescription": "{selectedUser} artık bir sunucu yöneticisi.",
"promoteServerAdminError": "Kullanıcıyı terfi ettirme başarısız oldu",
"demoteServerAdmin": "Sunucu yöneticisinden negatif terfi et",
"demoteServerAdminTitle": "Sunucu Yöneticisinden Negatif Terfi",
"demoteServerAdminQuestion": "{selectedUser} adresini sunucu yöneticiliğinden indirmek istediğinizden emin misiniz?",
"demoteServerAdminMessage": "{selectedUser} tüm sunucu yönetici ayrıcalıklarını kaybedecek.",
"demoteServerAdminWarning": "Bu, kullanıcıyı terfi ettirerek herhangi bir zamanda geri alınabilir.",
"demoteServerAdminConfirm": "Sunucu yöneticisinden negatif terfi et",
"demoteServerAdminSuccess": "Kullanıcı indirildi",
"demoteServerAdminSuccessDescription": "{selectedUser} artık bir sunucu yöneticisi değil.",
"demoteServerAdminError": "Kullanıcıyı indirirken başarısız oldu",
"managedSelfhosted": "Yönetilen Self-Hosted",
"otpEnable": "İki faktörlü özelliğini etkinleştir",
"otpDisable": "İki faktörlü özelliğini devre dışı bırak",
@@ -1573,6 +1599,8 @@
"search": "Ara…",
"searchPlaceholder": "Ara...",
"emptySearchOptions": "Seçenek bulunamadı",
"ipFilterSearchPlaceholder": "Bir IP adresi girin…",
"ipFilterEmptyMessage": "Filtrelemek için bir IP adresi girin",
"create": "Oluştur",
"orgs": "Organizasyonlar",
"loginError": "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "Bütçe",
"resourceBudgetSettingsDescription": "Bu AI ağ geçidi harcama veya jeton limitlerine göre kullanım nasıl sınırlayacağını yapılandırın",
"sidebarApiKeys": "API Anahtarları",
"sidebarOrgs": "Organizasyonlar",
"sidebarProvisioning": "Tedarik",
"sidebarSettings": "Ayarlar",
"sidebarAllUsers": "Tüm Kullanıcılar",
@@ -2596,6 +2625,7 @@
"createDomainType": "Tür:",
"createDomainName": "Ad:",
"createDomainValue": "Değer:",
"multiSelectFilterCount": "{count} seçildi",
"createDomainCnameRecords": "CNAME Kayıtları",
"createDomainARecords": "A Kayıtları",
"createDomainRecordNumber": "Kayıt {number}",
@@ -2710,6 +2740,7 @@
"healthScheme": "Yöntem",
"healthSelectScheme": "Yöntem Seç",
"healthCheckPortInvalid": "Bağlantı noktası 1 ile 65535 arasında olmalıdır",
"healthCheckHostnameInvalid": "Ana bilgisayar adı boşluk içermemelidir",
"healthCheckPath": "Yol",
"healthHostname": "IP / Hostname",
"healthPort": "Bağlantı Noktası",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "Bir CIDR aralığı ekle (örneğin, 10.0.0.0/8)",
"remoteExitNodeNetworkingSubnetsLoadError": "Alt ağlar yüklenemedi",
"remoteExitNodeNetworkingLabelsTitle": "Tercih Etiketleri",
"remoteExitNodeNetworkingLabelsDescription": "Bu etiketlere sahip siteler, bu uzak çıkış düğümü üzerinden bağlantı kurmaya zorlanacaktır.",
"remoteExitNodeNetworkingLabelsDescription": "Bu etiketlere sahip siteler, bağlantıyı bu uzak çıkış düğümü üzerinden tercih edecektir.",
"remoteExitNodeNetworkingLabelsButtonText": "Etiketleri seç...",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Etiketleri ara...",
"remoteExitNodeNetworkingLabelsLoadError": "Etiketler yüklenemedi",
@@ -3470,6 +3501,7 @@
"priority": "Öncelik",
"priorityDescription": "Daha yüksek öncelikli rotalar önce değerlendirilir. Öncelik = 100, otomatik sıralama anlamına gelir (sistem karar verir). Manuel öncelik uygulamak için başka bir numara kullanın.",
"instanceName": "Örnek İsmi",
"clearInstanceName": "Sunucu Birliği Sıfırla",
"pathMatchModalTitle": "Yol Eşleşmesini Yapılandır",
"pathMatchModalDescription": "Gelen isteklerin yolu temel alarak nasıl eşleştirilmesi gerektiğini ayarlayın.",
"pathMatchType": "Eşleşme Türü",
@@ -3480,7 +3512,7 @@
"clear": "Temizle",
"saveChanges": "Değişiklikleri Kaydet",
"pathMatchRegexPlaceholder": "^/api/.*",
"pathMatchDefaultPlaceholder": "/path",
"pathMatchDefaultPlaceholder": "/yol",
"pathMatchPrefixHelp": "Örnek: /api, /api/users vb.'ni eşleştirir.",
"pathMatchExactHelp": "Örnek: /api yalnızca /api'yi eşleştirir",
"pathMatchRegexHelp": "Örnek: ^/api/.* her şeyi eşleştirir /api/anything",
@@ -3759,6 +3791,7 @@
"noData": "Veri Yok",
"machineClients": "Makine İstemcileri",
"install": "Yükle",
"downloadInstaller": "Yükleyiciyi İndir",
"run": "Çalıştır",
"envFile": "Ortam Dosyası",
"serviceFile": "Servis Dosyası",
@@ -3966,6 +3999,7 @@
"disconnected": "Bağlantı Kesildi",
"approvalsEmptyStateTitle": "Cihaz Onayları Etkin Değil",
"approvalsEmptyStateDescription": "Kullanıcıların yeni cihazlara bağlanabilmeleri için yönetici onayı gerektiren rol cihaz onaylarını etkinleştirin.",
"approvalsEmptyStateHowToTitle": "Nasıl Etkinleştirilir",
"approvalsEmptyStateStep1Title": "Rollere Git",
"approvalsEmptyStateStep1Description": "Cihaz onaylarını yapılandırmak için kuruluşunuzun rol ayarlarına gidin.",
"approvalsEmptyStateStep2Title": "Cihaz Onaylarını Etkinleştir",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "Yönetici Olarak Görüntüle",
"resourceLauncherResourceDetailsDescription": "Bu kaynağın bağlantı bilgileri ve durumu.",
"resourceLauncherResourceDetails": "Kaynak Detayları",
"resourceLauncherSitesDescription": "Kaynak, aşağıdaki siteler üzerinden erişilebilir.",
"resourceLauncherViewSiteAsAdmin": "Siteyi Yönetici Olarak Görüntüle",
"resourceLauncherFilterBySite": "Siteye Göre Filtrele",
"resourceLauncherSshCommand": "SSH Komutu",
"resourceLauncherSshCommandDescription": "Pangolin CLI kullanarak bu kaynağa bir SSH oturumu açın.",
"resourceLauncherAuthMethodsDescription": "Bu kaynak için etkin kimlik doğrulama yöntemleri.",
"resourceLauncherPrivateClientRequired": "Bu kaynağa özel olarak erişmek için cihazınızda bir istemci ile bağlanın.",
"resourceLauncherPrivateClientRequiredTitle": "İstemci Bağlantısı Gerekli",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode klavye modu",
"sessionToolbarShow": "Araç çubuğunu göster",
"sessionToolbarHide": "Araç çubuğunu gizle",
"actionUpdateSiteApprovals": "Site Onaylarını Güncelle"
"actionUpdateSiteApprovals": "Site Onaylarını Güncelle",
"check": "Kontrol Et"
}
+86 -46
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "此站点上的目标",
"siteSetting": "{siteName} 设置",
"siteNewtTunnel": "新节点 (推荐)",
"pangolinSite": "Pangolin 站点",
"siteNewtTunnelDescription": "最简单的方式来创建任何网络的入口。没有额外的设置。",
"siteWg": "基本 WireGuard",
"siteWgDescription": "使用任何 WireGuard 客户端来建立隧道。需要手动配置 NAT。",
@@ -465,6 +466,8 @@
"apiKeysDelete": "删除 API 密钥",
"apiKeysManage": "管理 API 密钥",
"apiKeysDescription": "API 密钥用于认证集成 API",
"orgsManage": "管理组织",
"orgsDescription": "查看和管理此实例中的所有组织",
"provisioningKeysTitle": "预配密钥",
"provisioningKeysManage": "管理预配密钥",
"provisioningKeysDescription": "置备密钥用于验证您组织的自动站点配置。",
@@ -716,6 +719,7 @@
"nameOptional": "名称(可选)",
"accessControls": "访问控制",
"userDescription2": "管理此用户的设置",
"userGeneralSettingsDescription": "管理此用户在组织中的角色和设置",
"accessRoleErrorAdd": "添加用户到角色失败",
"accessRoleErrorAddDescription": "添加用户到角色时出错。",
"userSaved": "用户已保存",
@@ -736,7 +740,7 @@
"proxyErrorTls": "无效的 TLS 服务器名称。使用域名格式,或保存空以删除 TLS 服务器名称。",
"proxyEnableSSL": "启用 TLS",
"proxyEnableSSLDescription": "启用 SSL/TLS 加密以确保目标的 HTTPS 连接。",
"target": "Target",
"target": "目标",
"configureTarget": "配置目标",
"targetErrorFetch": "获取目标失败",
"targetErrorFetchDescription": "获取目标时出错",
@@ -912,8 +916,8 @@
"policyAccessRulesFallthroughOff": "禁用规则后,所有流量将通过身份验证。",
"policyAccessRulesFallthroughOn": "没有规则匹配时,流量将通过身份验证。",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesPlaceholderPath": "/管理员/*",
"rulesPlaceholderGeo": "俄罗斯, 朝鲜",
"rulesSave": "保存规则",
"resourceErrorCreate": "创建资源时出错",
"resourceErrorCreateDescription": "创建资源时出错",
@@ -948,8 +952,8 @@
"unknownCommand": "未知命令",
"newtErrorFetchReleases": "无法获取版本信息: {err}",
"newtErrorFetchLatest": "无法获取最新版信息: {err}",
"newtEndpoint": "Endpoint",
"newtId": "ID",
"newtEndpoint": "终端",
"newtId": "身份ID",
"newtSecretKey": "密钥",
"newtVersion": "版本",
"architecture": "架构",
@@ -1154,7 +1158,7 @@
"idpAutoProvisionUsers": "自动提供用户",
"idpAutoProvisionUsersDescription": "如果启用,用户将在首次登录时自动在系统中创建,并且能够映射用户到角色和组织。",
"idpAutoProvisionConfigureAfterCreate": "您可以在创建身份提供者后配置自动配置设置。",
"licenseBadge": "EE",
"licenseBadge": "企业版",
"idpType": "提供者类型",
"idpTypeDescription": "选择您想要配置的身份提供者类型",
"idpOidcConfigure": "OAuth2/OIDC 配置",
@@ -1176,6 +1180,10 @@
"idpJmespathAboutDescriptionLink": "了解更多 JMESPath 信息",
"idpJmespathLabel": "标识符路径",
"idpJmespathLabelDescription": "ID 令牌中用户标识符的路径",
"idpIdentifierChangeTitle": "标识符路径更改警告",
"idpIdentifierChangeDescription": "您即将更改标识符路径。这将影响现有用户的映射方式。以前通过此身份提供者登录的用户可能将不再被识别为相同用户。",
"idpIdentifierChangeConfirmMessage": "我确认",
"idpIdentifierChangeWarningText": "这将影响现有用户的映射方式",
"idpJmespathEmailPathOptional": "邮箱路径(可选)",
"idpJmespathEmailPathOptionalDescription": "ID 令牌中用户邮箱的路径",
"idpJmespathNamePathOptional": "用户名路径(可选)",
@@ -1307,7 +1315,7 @@
"generatePasswordResetCode": "生成密码重置代码",
"passwordResetCodeGenerated": "密码重置代码已生成",
"passwordResetCodeGeneratedDescription": "与用户分享此代码。他们可以用它来重置他们的密码。",
"passwordResetUrl": "Reset URL",
"passwordResetUrl": "重置网址",
"passwordNew": "新密码",
"passwordNewConfirm": "确认新密码",
"changePassword": "更改密码",
@@ -1420,6 +1428,24 @@
"logoutError": "注销错误",
"signingAs": "登录为",
"serverAdmin": "服务器管理",
"promoteServerAdmin": "晋升为服务器管理员",
"promoteServerAdminTitle": "晋升为服务器管理员",
"promoteServerAdminQuestion": "您确定要将 {selectedUser} 晋升为服务器管理员吗?",
"promoteServerAdminMessage": "服务器管理员拥有最高权限,可以管理服务器。",
"promoteServerAdminWarning": "可以随时通过降级用户来撤销此操作。",
"promoteServerAdminConfirm": "晋升为服务器管理员",
"promoteServerAdminSuccess": "用户已晋升",
"promoteServerAdminSuccessDescription": "{selectedUser} 现在是服务器管理员。",
"promoteServerAdminError": "无法晋升用户",
"demoteServerAdmin": "降级为服务器管理员",
"demoteServerAdminTitle": "降级为服务器管理员",
"demoteServerAdminQuestion": "您确定要将 {selectedUser} 从服务器管理员降级吗?",
"demoteServerAdminMessage": "{selectedUser} 将失去所有服务器管理员权限。",
"demoteServerAdminWarning": "可以随时通过晋升用户来撤销此操作。",
"demoteServerAdminConfirm": "降级为服务器管理员",
"demoteServerAdminSuccess": "用户已降级",
"demoteServerAdminSuccessDescription": "{selectedUser} 不再是服务器管理员。",
"demoteServerAdminError": "无法降级用户",
"managedSelfhosted": "托管自托管",
"otpEnable": "启用双因子认证",
"otpDisable": "禁用双因子认证",
@@ -1573,6 +1599,8 @@
"search": "搜索…",
"searchPlaceholder": "搜索...",
"emptySearchOptions": "未找到选项",
"ipFilterSearchPlaceholder": "输入IP地址…",
"ipFilterEmptyMessage": "输入要筛选的IP地址",
"create": "创建",
"orgs": "组织",
"loginError": "发生意外错误。请重试。",
@@ -1791,7 +1819,7 @@
"aiClientConfigTabManual": "手动配置",
"aiClientConfigPreset": "配置",
"aiClientConfigStep": "步骤 {number}",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigEndpointPlaceholder": "https://示例.资源.url.com",
"aiClientConfigPlaceholderWarning": "此片段包含示例值,如模型名称或资源 URL。在使用前代替它们为您自己的值。",
"aiClientConfigRevealError": "无法加载您的 API 密钥。",
"aiClientConfigRevealRetry": "再试一次",
@@ -1841,21 +1869,21 @@
"aiProviderTypeNotFound": "未找到提供者类型",
"aiProviderTypeOpenai": "OpenAI",
"aiProviderTypeAnthropic": "Anthropic",
"aiProviderTypeGoogleGemini": "Google Gemini",
"aiProviderTypeGoogleGemini": "谷歌 Gemini",
"aiProviderTypeVertexAi": "Vertex AI",
"aiProviderTypeBedrock": "Amazon Bedrock",
"aiProviderTypeMicrosoftFoundry": "Microsoft Foundry",
"aiProviderTypeOpenRouter": "OpenRouter",
"aiProviderTypeVercelAiGateway": "Vercel AI Gateway",
"aiProviderTypeMicrosoftFoundry": "微软车间",
"aiProviderTypeOpenRouter": "开放路由器",
"aiProviderTypeVercelAiGateway": "Vercel AI 网关",
"aiProviderTypeCustom": "自定义",
"aiProviderTypeOpenaiDescription": "OpenAI API",
"aiProviderTypeAnthropicDescription": "Anthropic API",
"aiProviderTypeGoogleGeminiDescription": "Google Gemini API",
"aiProviderTypeVertexAiDescription": "Google Vertex AI API",
"aiProviderTypeOpenaiDescription": "OpenAI 接口",
"aiProviderTypeAnthropicDescription": "Anthropic 接口",
"aiProviderTypeGoogleGeminiDescription": "Google Gemini 接口",
"aiProviderTypeVertexAiDescription": "Google Vertex AI 接口",
"aiProviderTypeBedrockDescription": "Amazon Bedrock 运行时 API",
"aiProviderTypeMicrosoftFoundryDescription": "Microsoft Foundry API",
"aiProviderTypeOpenRouterDescription": "OpenRouter API",
"aiProviderTypeVercelAiGatewayDescription": "Vercel AI Gateway API",
"aiProviderTypeMicrosoftFoundryDescription": "Microsoft Foundry 接口",
"aiProviderTypeOpenRouterDescription": "OpenRouter 接口",
"aiProviderTypeVercelAiGatewayDescription": "Vercel AI 网关接口",
"aiProviderTypeCustomDescription": "使用您自己的端点或通过站点目标进行路由",
"aiProviderUpstreamUrl": "上游URL",
"aiProviderUpstreamUrlDescription": "提供商API的基础URL",
@@ -1876,7 +1904,7 @@
"aiProviderAuthTypeXGoogApiKeyDescription": "x-goog-api-key头部。由Google Gemini使用",
"aiProviderAuthTypeHec": "Splunk HEC",
"aiProviderAuthTypeHecDescription": "授权:Splunk密钥。由Splunk HTTP事件收集器使用",
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI Gateway",
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI 网关",
"aiProviderAuthTypeCfAigAuthorizationDescription": "cf-aig-authorizationBearer key。由Cloudflare AI Gateway使用",
"aiProviderAuthTypeNone": "无身份验证",
"aiProviderAuthTypePassthrough": "透传",
@@ -2089,6 +2117,7 @@
"resourceBudgetSettings": "预算",
"resourceBudgetSettingsDescription": "配置此 AI 网关如何根据支出或令牌限制来限制使用",
"sidebarApiKeys": "API密钥",
"sidebarOrgs": "组织",
"sidebarProvisioning": "预配",
"sidebarSettings": "设置",
"sidebarAllUsers": "所有用户",
@@ -2596,6 +2625,7 @@
"createDomainType": "类型:",
"createDomainName": "名称:",
"createDomainValue": "值:",
"multiSelectFilterCount": "{count} 已选择",
"createDomainCnameRecords": "CNAME 记录",
"createDomainARecords": "A记录",
"createDomainRecordNumber": "记录 {number}",
@@ -2675,7 +2705,7 @@
"clientInstallOlmDescription": "在您的系统上运行 Olm",
"clientOlmCredentials": "全权证书",
"clientOlmCredentialsDescription": "这是客户端如何通过服务器进行身份验证",
"olmEndpoint": "Endpoint",
"olmEndpoint": "终端",
"olmId": "ID",
"olmSecretKey": "密钥",
"clientCredentialsSave": "保存证书",
@@ -2710,6 +2740,7 @@
"healthScheme": "方法",
"healthSelectScheme": "选择方法",
"healthCheckPortInvalid": "端口必须在 1 和 65535 之间",
"healthCheckHostnameInvalid": "主机名不得包含空格",
"healthCheckPath": "路径",
"healthHostname": "IP / 主机",
"healthPort": "端口",
@@ -2835,12 +2866,12 @@
"resourceEditDomain": "编辑域名",
"siteName": "站点名称",
"proxyPort": "端口",
"resourcesTableProxyResources": "",
"resourcesTableProxyResources": "公开资源",
"resourcesTableClientResources": "私有资源",
"resourcesTableNoProxyResourcesFound": "未找到代理资源。",
"resourcesTableNoInternalResourcesFound": "未找到私有资源。",
"resourcesTableDestination": "目标",
"resourcesTableAlias": "Alias",
"resourcesTableAlias": "别名",
"resourcesTableAliasAddress": "别名地址",
"resourcesTableAliasAddressInfo": "此地址是组织实用子网的一部分。它用来使用内部DNS解析来解析别名记录。",
"resourcesTableClients": "客户端",
@@ -2887,7 +2918,7 @@
"editInternalResourceDialogDestinationHostDescription": "站点网络上资源的 IP 地址或主机名。",
"editInternalResourceDialogDestinationIPDescription": "站点网络上资源的IP或主机名地址。",
"editInternalResourceDialogDestinationCidrDescription": "站点网络上资源的 CIDR 范围。",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAlias": "别名",
"editInternalResourceDialogAliasDescription": "此资源可选的内部DNS别名。",
"createInternalResourceDialogNoSitesAvailable": "暂无可用站点",
"createInternalResourceDialogNoSitesAvailableDescription": "您需要至少配置一个子网的Newt站点来创建私有资源。",
@@ -2946,7 +2977,7 @@
"createInternalResourceDialogDestination": "目标",
"createInternalResourceDialogDestinationHostDescription": "站点网络上资源的 IP 地址或主机名。",
"createInternalResourceDialogDestinationCidrDescription": "站点网络上资源的 CIDR 范围。",
"createInternalResourceDialogAlias": "Alias",
"createInternalResourceDialogAlias": "别名",
"createInternalResourceDialogAliasDescription": "此资源可选的内部DNS别名。",
"internalResourceAliasLocalWarning": "以 .local 结尾的别名可能会因某些网络上的 mDNS 而导致解析问题。",
"internalResourceDownstreamSchemeRequired": "HTTP 资源需要方案",
@@ -2993,7 +3024,7 @@
"remoteExitNodeNetworkingSubnetsPlaceholder": "添加CIDR范围(例如10.0.0.0/8",
"remoteExitNodeNetworkingSubnetsLoadError": "无法加载子网",
"remoteExitNodeNetworkingLabelsTitle": "首选标签",
"remoteExitNodeNetworkingLabelsDescription": "有这些标签的站点将强制通过此远程出口节点连接。",
"remoteExitNodeNetworkingLabelsDescription": "有这些标签的站点将优先通过此远程出口节点进行连接。",
"remoteExitNodeNetworkingLabelsButtonText": "选择标签……",
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "搜索标签……",
"remoteExitNodeNetworkingLabelsLoadError": "无法加载标签",
@@ -3065,7 +3096,7 @@
"regionMiddleAfrica": "中东",
"regionSouthernAfrica": "D. 南 非",
"regionWesternAfrica": "D. 西部非洲",
"regionAmericas": "Americas",
"regionAmericas": "美洲",
"regionCaribbean": "加勒比",
"regionCentralAmerica": "中美洲:",
"regionSouthAmerica": "南 非",
@@ -3081,11 +3112,11 @@
"regionNorthernEurope": "北欧洲",
"regionSouthernEurope": "南欧洲",
"regionWesternEurope": "西欧洲",
"regionOceania": "Oceania",
"regionOceania": "大洋洲",
"regionAustraliaAndNewZealand": "澳大利亚和新西兰",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"regionMelanesia": "美拉尼西亚",
"regionMicronesia": "密克罗尼西亚",
"regionPolynesia": "玻里尼西亚",
"managedSelfHosted": {
"title": "托管自托管",
"description": "更可靠和低维护自我托管的 Pangolin 服务器,带有额外的铃声和告密器",
@@ -3155,17 +3186,17 @@
"roleMappingRemoveRule": "删除",
"idpGoogleConfiguration": "Google 配置",
"idpGoogleConfigurationDescription": "配置 Google OAuth2 凭据",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
"idpGoogleClientIdDescription": "您的 Google OAuth2 客户端 ID",
"idpGoogleClientSecretDescription": "Google OAuth2 客户端密钥",
"idpAzureConfiguration": "Azure Entra ID 配置",
"idpAzureConfigurationDescription": "配置 Azure Entra ID OAuth2 凭据",
"idpTenantId": "租户 ID",
"idpTenantIdPlaceholder": "tenant-id",
"idpTenantIdPlaceholder": "租户 ID",
"idpAzureTenantIdDescription": "Azure 租户ID (在 Azure Active Directory 概览中找到)",
"idpAzureClientIdDescription": "Azure 应用注册客户端 ID",
"idpAzureClientSecretDescription": "Azure 应用程序注册客户端密钥",
"idpGoogleTitle": "谷歌",
"idpGoogleAlt": "Google",
"idpGoogleAlt": "谷歌",
"idpAzureTitle": "Azure Entra ID",
"idpAzureAlt": "Azure",
"idpGoogleConfigurationTitle": "Google 配置",
@@ -3174,7 +3205,7 @@
"idpAzureClientIdDescription2": "Azure 应用注册客户端 ID",
"idpAzureClientSecretDescription2": "Azure 应用程序注册客户端密钥",
"idpGoogleDescription": "Google OAuth2/OIDC 提供商",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC 提供商",
"subnet": "子网",
"utilitySubnet": "实用程序子网",
"subnetDescription": "此组织网络配置的子网。",
@@ -3340,7 +3371,7 @@
"manageMachineClientsDescription": "创建和管理服务器和系统用于私密连接到资源的客户端",
"machineClientsBannerTitle": "服务器与自动化系统",
"machineClientsBannerDescription": "机器客户端适用于不与特定用户关联的服务器与自动化系统。它们使用ID和密钥进行身份验证,并可以与Pangolin CLI、Olm CLI或作为容器运行。",
"machineClientsBannerPangolinCLI": "Pangolin CLI",
"machineClientsBannerPangolinCLI": "邦戈林 CLI",
"machineClientsBannerOlmCLI": "Olm CLI",
"machineClientsBannerOlmContainer": "Olm 容器",
"clientsTableUserClients": "用户",
@@ -3470,6 +3501,7 @@
"priority": "优先权",
"priorityDescription": "先评估更高优先级线路。优先级 = 100意味着自动排序(系统决定). 使用另一个数字强制执行手动优先级。",
"instanceName": "实例名称",
"clearInstanceName": "重置服务器关联",
"pathMatchModalTitle": "配置路径匹配",
"pathMatchModalDescription": "根据传入请求的路径设置匹配方式。",
"pathMatchType": "匹配类型",
@@ -3525,11 +3557,11 @@
"allowedByRule": "根据规则允许",
"allowedNoAuth": "无认证",
"validAccessToken": "有效访问令牌",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validHeaderAuth": "有效的头部认证",
"validPincode": "有效的 PIN 码",
"validPassword": "有效密码",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"validEmail": "有效的邮箱",
"validSSO": "有效的 SSO",
"validVirtualAPIKey": "有效虚拟 API 密钥",
"view": "查看",
"configManaged": "配置已管理",
@@ -3538,7 +3570,7 @@
"droppedByRule": "被规则删除",
"noSessions": "无会话",
"temporaryRequestToken": "临时请求令牌",
"noMoreAuthMethods": "No Valid Auth",
"noMoreAuthMethods": "没有有效的认证",
"ip": "IP",
"reason": "原因",
"requestLogs": "请求日志",
@@ -3742,8 +3774,8 @@
"regenerateCredentialsWarning": "重新生成凭据将使以前的凭据失效并导致断开连接。请确保更新使用这些凭据的任何配置。",
"confirm": "确认",
"regenerateCredentialsConfirmation": "您确定要重新生成凭据吗?",
"endpoint": "Endpoint",
"Id": "Id",
"endpoint": "终端",
"Id": "ID",
"SecretKey": "秘密密钥",
"niceId": "好的 ID",
"niceIdUpdated": "好的 ID 已更新",
@@ -3752,13 +3784,14 @@
"niceIdUpdateErrorDescription": "更新Nice ID时出错。",
"niceIdCannotBeEmpty": "好的 ID 不能为空",
"enterIdentifier": "输入标识符",
"identifier": "Identifier",
"identifier": "标识符",
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
"loginSelectAuthenticationMethod": "选择要继续的身份验证方法。",
"noData": "无数据",
"machineClients": "机器客户端",
"install": "安装",
"downloadInstaller": "下载安装程序",
"run": "运行",
"envFile": "环境文件",
"serviceFile": "服务文件",
@@ -3936,7 +3969,7 @@
"kernelVersion": "内核版本",
"deviceModel": "设备模型",
"serialNumber": "序列号",
"hostname": "Hostname",
"hostname": "主机名",
"firstSeen": "第一次查看",
"lastSeen": "上次查看时间",
"biometricsEnabled": "生物计已启用",
@@ -3966,6 +3999,7 @@
"disconnected": "断开连接",
"approvalsEmptyStateTitle": "设备批准未启用",
"approvalsEmptyStateDescription": "在用户连接新设备之前,允许设备批准角色,需要管理员批准。",
"approvalsEmptyStateHowToTitle": "如何启用",
"approvalsEmptyStateStep1Title": "转到角色",
"approvalsEmptyStateStep1Description": "导航到您组织的角色设置来配置设备批准。",
"approvalsEmptyStateStep2Title": "启用设备批准",
@@ -4245,6 +4279,11 @@
"resourceLauncherViewAsAdmin": "以管理员身份查看",
"resourceLauncherResourceDetailsDescription": "此资源的连接信息和状态。",
"resourceLauncherResourceDetails": "资源详情",
"resourceLauncherSitesDescription": "该资源可通过以下网站访问。",
"resourceLauncherViewSiteAsAdmin": "以管理员身份查看网站",
"resourceLauncherFilterBySite": "按网站过滤",
"resourceLauncherSshCommand": "SSH命令",
"resourceLauncherSshCommandDescription": "使用Pangolin CLI来打开该资源的SSH会话。",
"resourceLauncherAuthMethodsDescription": "此资源启用的身份验证方法。",
"resourceLauncherPrivateClientRequired": "请在您的设备上连接客户端以私密访问此资源。",
"resourceLauncherPrivateClientRequiredTitle": "需要客户端连接",
@@ -4355,5 +4394,6 @@
"rdpUnicodeKeyboardMode": "Unicode 键盘模式",
"sessionToolbarShow": "显示工具栏",
"sessionToolbarHide": "隐藏工具栏",
"actionUpdateSiteApprovals": "更新站点审批"
"actionUpdateSiteApprovals": "更新站点审批",
"check": "检查"
}
+5
View File
@@ -34,6 +34,11 @@ const nextConfig: NextConfig = {
source: "/:orgId/settings/resources/client/:path*",
destination: "/:orgId/settings/resources/private/:path*",
permanent: true
},
{
source: "/:orgId/settings/access/users/:userId/access-controls",
destination: "/:orgId/settings/access/users/:userId/general",
permanent: false
}
];
}
+1960 -3612
View File
File diff suppressed because it is too large Load Diff
+77 -74
View File
@@ -32,49 +32,50 @@
"format": "prettier --write ."
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "8.5.0",
"@aws-sdk/client-s3": "3.1056.0",
"@asteasolutions/zod-to-openapi": "9.1.0",
"@aws-sdk/client-s3": "3.1121.0",
"@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",
"@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.4.0",
"@hookform/resolvers": "5.9.1",
"@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2",
"@node-rs/argon2": "2.2.0",
"@novnc/novnc": "^1.7.0",
"@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-avatar": "1.2.6",
"@radix-ui/react-checkbox": "1.3.11",
"@radix-ui/react-collapsible": "1.1.20",
"@radix-ui/react-dialog": "1.1.23",
"@radix-ui/react-dropdown-menu": "2.1.24",
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-label": "2.1.8",
"@radix-ui/react-popover": "1.1.15",
"@radix-ui/react-progress": "1.1.8",
"@radix-ui/react-radio-group": "1.3.8",
"@radix-ui/react-scroll-area": "1.2.10",
"@radix-ui/react-select": "2.2.6",
"@radix-ui/react-separator": "1.1.8",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@radix-ui/react-label": "2.1.15",
"@radix-ui/react-popover": "1.1.23",
"@radix-ui/react-progress": "1.1.16",
"@radix-ui/react-radio-group": "1.4.7",
"@radix-ui/react-scroll-area": "1.2.18",
"@radix-ui/react-select": "2.3.7",
"@radix-ui/react-separator": "1.1.15",
"@radix-ui/react-slot": "1.3.3",
"@radix-ui/react-switch": "1.3.7",
"@radix-ui/react-tabs": "1.1.21",
"@radix-ui/react-toast": "1.2.23",
"@radix-ui/react-tooltip": "1.2.16",
"@react-email/body": "0.3.0",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.8",
"@react-email/render": "2.1.0",
"@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.1",
"@simplewebauthn/server": "13.3.3",
"@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.100.14",
"@tanstack/react-query": "5.102.8",
"@tanstack/react-table": "8.21.3",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"acme-client": "^5.4.0",
"arctic": "3.7.0",
"axios": "1.18.0",
"axios": "1.20.0",
"better-sqlite3": "11.9.1",
"canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1",
@@ -84,104 +85,106 @@
"cors": "2.8.6",
"crypto-js": "4.2.0",
"d3": "7.9.0",
"dns-packet": "^5.6.1",
"drizzle-orm": "0.45.2",
"express": "5.2.1",
"express-rate-limit": "8.5.2",
"express-rate-limit": "8.7.0",
"glob": "13.0.6",
"gpt-tokenizer": "^3.4.0",
"helmet": "8.2.0",
"gpt-tokenizer": "^4.0.0",
"helmet": "8.3.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.11.0",
"input-otp": "1.5.0",
"ioredis": "6.0.0",
"jmespath": "0.16.0",
"js-yaml": "4.3.1",
"js-yaml": "5.4.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "1.17.0",
"maxmind": "5.0.6",
"lru-cache": "11.5.2",
"lucide-react": "1.38.0",
"maxmind": "5.0.7",
"moment": "2.30.1",
"next": "16.3.1",
"next-intl": "4.13.0",
"next": "16.3.4",
"next-intl": "4.14.1",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "9.0.1",
"nodemailer": "9.1.0",
"oslo": "1.2.1",
"pg": "8.21.0",
"posthog-node": "5.35.6",
"pg": "8.23.0",
"posthog-node": "5.51.4",
"qrcode.react": "4.2.0",
"react": "19.2.6",
"react": "19.2.8",
"react-day-picker": "9.14.0",
"react-dom": "19.2.6",
"react-dom": "19.2.8",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.76.1",
"react-icons": "5.6.0",
"recharts": "3.8.1",
"react-hook-form": "7.87.0",
"react-icons": "5.7.0",
"recharts": "3.10.1",
"reodotdev": "1.1.0",
"semver": "7.8.1",
"semver": "7.8.5",
"sshpk": "1.18.0",
"stripe": "22.2.0",
"stripe": "22.6.0",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "10.1.1",
"uuid": "14.0.0",
"uuid": "14.0.2",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.21.0",
"ws": "8.21.3",
"yaml": "2.9.0",
"yargs": "18.0.0",
"zod": "4.4.3",
"yargs": "18.1.0",
"zod": "4.5.4",
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.69.1",
"@dotenvx/dotenvx": "2.23.0",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/ui": "^6.9.2",
"@tailwindcss/postcss": "4.3.0",
"@tanstack/react-query-devtools": "5.100.14",
"@react-email/ui": "^6.9.3",
"@tailwindcss/postcss": "4.3.3",
"@tanstack/react-query-devtools": "5.102.8",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/crypto-js": "4.2.2",
"@types/d3": "7.4.3",
"@types/dns-packet": "^5.6.5",
"@types/express": "5.0.6",
"@types/express-session": "1.19.0",
"@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.9.1",
"@types/nodemailer": "8.0.0",
"@types/node": "26.4.0",
"@types/nodemailer": "8.0.1",
"@types/nprogress": "0.2.3",
"@types/pg": "8.20.0",
"@types/react": "19.2.15",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "1.17.4",
"@types/pg": "8.23.1",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.5",
"@types/semver": "7.8.0",
"@types/sshpk": "1.17.5",
"@types/swagger-ui-express": "4.1.8",
"@types/topojson-client": "3.1.5",
"@types/ws": "8.18.1",
"@types/yargs": "17.0.35",
"babel-plugin-react-compiler": "1.0.0",
"drizzle-kit": "0.31.10",
"esbuild": "0.28.0",
"esbuild-node-externals": "1.22.0",
"eslint": "10.4.0",
"eslint-config-next": "16.2.6",
"postcss": "8.5.23",
"prettier": "3.8.3",
"react-email": "6.5.0",
"tailwindcss": "4.3.0",
"tsc-alias": "1.8.17",
"tsx": "4.22.3",
"esbuild": "0.28.2",
"esbuild-node-externals": "2.0.0",
"eslint": "10.9.1",
"eslint-config-next": "16.3.3",
"postcss": "8.5.26",
"prettier": "3.9.6",
"react-email": "6.9.3",
"tailwindcss": "4.3.3",
"tsc-alias": "1.9.2",
"tsx": "4.23.13",
"typescript": "6.0.3",
"typescript-eslint": "8.60.0"
"typescript-eslint": "8.68.0"
},
"overrides": {
"esbuild": "0.28.0",
"esbuild": "0.28.2",
"dompurify": "3.4.0",
"postcss": "8.5.23"
"postcss": "8.5.26"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 KiB

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 KiB

After

Width:  |  Height:  |  Size: 802 KiB

+8
View File
@@ -0,0 +1,8 @@
export async function startCertificateManager() {
// No-op: ACME certificate generation/management is only available in
// builds that include the private/enterprise feature set.
}
export async function stopCertificateManager() {
// No-op counterpart to startCertificateManager.
}
+85 -1
View File
@@ -26,7 +26,9 @@ import {
sites,
clients,
sessions,
labels
labels,
aiProviders,
virtualApiKeys
} from "./schema";
export const dnsChallenge = pgTable("dnsChallenges", {
@@ -614,6 +616,87 @@ export const trialNotifications = pgTable("trialNotifications", {
sentAt: bigint("sentAt", { mode: "number" }).notNull()
});
// Logs the aggregated prompt + response for a single AI gateway request, for
// session replay. One row per request (not per streaming chunk). `sessionId`
// is a fresh random id per row for now - no cross-request correlation yet,
// but the column exists so a future pass can link multiple rows into a real
// multi-turn session.
export const aiSessionLog = pgTable(
"aiSessionLog",
{
id: serial("id").primaryKey(),
sessionId: varchar("sessionId").notNull(),
orgId: varchar("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
providerId: integer("providerId").references(
() => aiProviders.providerId,
{ onDelete: "set null" }
),
capability: varchar("capability").notNull(),
resourceId: integer("resourceId").references(
() => resources.resourceId,
{ onDelete: "set null" }
),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "set null" }
),
userId: varchar("userId").references(() => users.userId, {
onDelete: "set null"
}),
virtualApiKeyId: varchar("virtualApiKeyId").references(
() => virtualApiKeys.virtualApiKeyId,
{ onDelete: "set null" }
),
requestedModel: varchar("requestedModel"),
isStream: boolean("isStream").notNull().default(false),
requestBody: text("requestBody"),
responseBody: text("responseBody"),
// Capability-agnostic message transcript (JSON-encoded
// NormalizedAiMessage[] from server/lib/aiMessageNormalization.ts),
// computed at write time so search/display never need per-capability
// parsing logic. Null when normalization couldn't recognize the
// shape - callers fall back to requestBody/responseBody.
normalizedRequest: text("normalizedRequest"),
normalizedResponse: text("normalizedResponse"),
// True if any of the request/response (raw or normalized) fields
// were cut short at AI_SESSION_LOG_MAX_BODY_CHARS before storage.
truncated: boolean("truncated").notNull().default(false),
statusCode: integer("statusCode"),
createdAt: bigint("createdAt", { mode: "number" }).notNull() // epoch seconds
},
(t) => [
index("idx_ai_session_log_org_created").on(t.orgId, t.createdAt),
index("idx_ai_session_log_org_provider_created").on(
t.orgId,
t.providerId,
t.createdAt
),
index("idx_ai_session_log_org_resource_created").on(
t.orgId,
t.resourceId,
t.createdAt
),
index("idx_ai_session_log_org_site_resource_created").on(
t.orgId,
t.siteResourceId,
t.createdAt
),
index("idx_ai_session_log_org_user_created").on(
t.orgId,
t.userId,
t.createdAt
),
index("idx_ai_session_log_org_virtual_api_key_created").on(
t.orgId,
t.virtualApiKeyId,
t.createdAt
),
index("idx_ai_session_log_session").on(t.sessionId)
]
);
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -660,3 +743,4 @@ export type AlertEmailRecipients = InferSelectModel<
>;
export type AlertWebhookActions = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type AiSessionLog = InferSelectModel<typeof aiSessionLog>;
+73 -131
View File
@@ -262,16 +262,20 @@ export const resourceAiModels = pgTable(
(t) => [primaryKey({ columns: [t.resourceId, t.modelId] })]
);
export const labels = pgTable("labels", {
labelId: serial("labelId").primaryKey(),
name: varchar("name").notNull(),
color: varchar("color").notNull(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const labels = pgTable(
"labels",
{
labelId: serial("labelId").primaryKey(),
name: varchar("name").notNull(),
color: varchar("color").notNull(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [index("idx_labels_orgid").on(t.orgId)]
);
export const launcherViews = pgTable("launcherViews", {
viewId: serial("viewId").primaryKey(),
@@ -678,6 +682,8 @@ export const newts = pgTable(
secretHash: varchar("secretHash").notNull(),
dateCreated: varchar("dateCreated").notNull(),
version: varchar("version"),
agent: varchar("agent"), // either newt or cli
agentVersion: varchar("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
})
@@ -693,15 +699,19 @@ export const twoFactorBackupCodes = pgTable("twoFactorBackupCodes", {
codeHash: varchar("codeHash").notNull()
});
export const sessions = pgTable("session", {
sessionId: varchar("id").primaryKey(),
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
issuedAt: bigint("issuedAt", { mode: "number" }),
deviceAuthUsed: boolean("deviceAuthUsed").notNull().default(false)
});
export const sessions = pgTable(
"session",
{
sessionId: varchar("id").primaryKey(),
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
issuedAt: bigint("issuedAt", { mode: "number" }),
deviceAuthUsed: boolean("deviceAuthUsed").notNull().default(false)
},
(t) => [index("idx_sessions_userid").on(t.userId)]
);
export const newtSessions = pgTable("newtSession", {
sessionId: varchar("id").primaryKey(),
@@ -711,19 +721,26 @@ export const newtSessions = pgTable("newtSession", {
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
});
export const userOrgs = pgTable("userOrgs", {
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isOwner: boolean("isOwner").notNull().default(false),
autoProvisioned: boolean("autoProvisioned").default(false),
pamUsername: varchar("pamUsername") // cleaned username for ssh and such
});
export const userOrgs = pgTable(
"userOrgs",
{
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isOwner: boolean("isOwner").notNull().default(false),
autoProvisioned: boolean("autoProvisioned").default(false),
pamUsername: varchar("pamUsername") // cleaned username for ssh and such
},
(t) => [
index("idx_userOrgs_userid").on(t.userId),
index("idx_userOrgs_orgid").on(t.orgId)
]
);
export const emailVerificationCodes = pgTable("emailVerificationCodes", {
codeId: serial("id").primaryKey(),
@@ -751,22 +768,26 @@ export const actions = pgTable("actions", {
description: varchar("description")
});
export const roles = pgTable("roles", {
roleId: serial("roleId").primaryKey(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isAdmin: boolean("isAdmin"),
name: varchar("name").notNull(),
description: varchar("description"),
requireDeviceApproval: boolean("requireDeviceApproval").default(false),
sshSudoMode: varchar("sshSudoMode", { length: 32 }).default("full"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: boolean("sshCreateHomeDir").default(true),
sshUnixGroups: text("sshUnixGroups").default("[]")
});
export const roles = pgTable(
"roles",
{
roleId: serial("roleId").primaryKey(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isAdmin: boolean("isAdmin"),
name: varchar("name").notNull(),
description: varchar("description"),
requireDeviceApproval: boolean("requireDeviceApproval").default(false),
sshSudoMode: varchar("sshSudoMode", { length: 32 }).default("full"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: boolean("sshCreateHomeDir").default(true),
sshUnixGroups: text("sshUnixGroups").default("[]")
},
(t) => [index("idx_roles_orgid").on(t.orgId)]
);
export const userOrgRoles = pgTable(
"userOrgRoles",
@@ -1409,7 +1430,10 @@ export const olms = pgTable(
}),
archived: boolean("archived").notNull().default(false)
},
(t) => [index("idx_olms_clientid").on(t.clientId)]
(t) => [
index("idx_olms_clientid").on(t.clientId),
index("idx_olms_userid").on(t.userId)
]
);
export const currentFingerprint = pgTable("currentFingerprint", {
@@ -1936,87 +1960,6 @@ export const aiBudgetBreachEvents = pgTable(
]
);
// Logs the aggregated prompt + response for a single AI gateway request, for
// session replay. One row per request (not per streaming chunk). `sessionId`
// is a fresh random id per row for now - no cross-request correlation yet,
// but the column exists so a future pass can link multiple rows into a real
// multi-turn session.
export const aiSessionLog = pgTable(
"aiSessionLog",
{
id: serial("id").primaryKey(),
sessionId: varchar("sessionId").notNull(),
orgId: varchar("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
providerId: integer("providerId").references(
() => aiProviders.providerId,
{ onDelete: "set null" }
),
capability: varchar("capability").notNull(),
resourceId: integer("resourceId").references(
() => resources.resourceId,
{ onDelete: "set null" }
),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "set null" }
),
userId: varchar("userId").references(() => users.userId, {
onDelete: "set null"
}),
virtualApiKeyId: varchar("virtualApiKeyId").references(
() => virtualApiKeys.virtualApiKeyId,
{ onDelete: "set null" }
),
requestedModel: varchar("requestedModel"),
isStream: boolean("isStream").notNull().default(false),
requestBody: text("requestBody"),
responseBody: text("responseBody"),
// Capability-agnostic message transcript (JSON-encoded
// NormalizedAiMessage[] from server/lib/aiMessageNormalization.ts),
// computed at write time so search/display never need per-capability
// parsing logic. Null when normalization couldn't recognize the
// shape - callers fall back to requestBody/responseBody.
normalizedRequest: text("normalizedRequest"),
normalizedResponse: text("normalizedResponse"),
// True if any of the request/response (raw or normalized) fields
// were cut short at AI_SESSION_LOG_MAX_BODY_CHARS before storage.
truncated: boolean("truncated").notNull().default(false),
statusCode: integer("statusCode"),
createdAt: bigint("createdAt", { mode: "number" }).notNull() // epoch seconds
},
(t) => [
index("idx_ai_session_log_org_created").on(t.orgId, t.createdAt),
index("idx_ai_session_log_org_provider_created").on(
t.orgId,
t.providerId,
t.createdAt
),
index("idx_ai_session_log_org_resource_created").on(
t.orgId,
t.resourceId,
t.createdAt
),
index("idx_ai_session_log_org_site_resource_created").on(
t.orgId,
t.siteResourceId,
t.createdAt
),
index("idx_ai_session_log_org_user_created").on(
t.orgId,
t.userId,
t.createdAt
),
index("idx_ai_session_log_org_virtual_api_key_created").on(
t.orgId,
t.virtualApiKeyId,
t.createdAt
),
index("idx_ai_session_log_session").on(t.sessionId)
]
);
export const certificates = pgTable("certificates", {
certId: serial("certId").primaryKey(),
domain: varchar("domain", { length: 255 }).notNull().unique(),
@@ -2129,7 +2072,6 @@ export type AiModel = InferSelectModel<typeof aiModels>;
export type AiBudget = InferSelectModel<typeof aiBudgets>;
export type AiUsageRecord = InferSelectModel<typeof aiUsageRecords>;
export type AiBudgetBreachEvent = InferSelectModel<typeof aiBudgetBreachEvents>;
export type AiSessionLog = InferSelectModel<typeof aiSessionLog>;
export type ResourceAiProvider = InferSelectModel<typeof resourceAiProviders>;
export type SiteResourceAiProvider = InferSelectModel<
typeof siteResourceAiProviders
+89 -1
View File
@@ -9,6 +9,7 @@ import {
uniqueIndex
} from "drizzle-orm/sqlite-core";
import {
aiProviders,
clients,
domains,
exitNodes,
@@ -20,7 +21,8 @@ import {
siteResources,
sites,
targetHealthCheck,
users
users,
virtualApiKeys
} from "./schema";
export const dnsChallenge = sqliteTable("dnsChallenges", {
@@ -609,6 +611,91 @@ export const trialNotifications = sqliteTable("trialNotifications", {
sentAt: integer("sentAt").notNull()
});
// Logs the aggregated prompt + response for a single AI gateway request, for
// session replay. One row per request (not per streaming chunk). `sessionId`
// is a fresh random id per row for now - no cross-request correlation yet,
// but the column exists so a future pass can link multiple rows into a real
// multi-turn session.
export const aiSessionLog = sqliteTable(
"aiSessionLog",
{
id: integer("id").primaryKey({ autoIncrement: true }),
sessionId: text("sessionId").notNull(),
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
providerId: integer("providerId").references(
() => aiProviders.providerId,
{ onDelete: "set null" }
),
capability: text("capability").notNull(),
resourceId: integer("resourceId").references(
() => resources.resourceId,
{ onDelete: "set null" }
),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "set null" }
),
userId: text("userId").references(() => users.userId, {
onDelete: "set null"
}),
virtualApiKeyId: text("virtualApiKeyId").references(
() => virtualApiKeys.virtualApiKeyId,
{ onDelete: "set null" }
),
requestedModel: text("requestedModel"),
isStream: integer("isStream", { mode: "boolean" })
.notNull()
.default(false),
requestBody: text("requestBody"),
responseBody: text("responseBody"),
// Capability-agnostic message transcript (JSON-encoded
// NormalizedAiMessage[] from server/lib/aiMessageNormalization.ts),
// computed at write time so search/display never need per-capability
// parsing logic. Null when normalization couldn't recognize the
// shape - callers fall back to requestBody/responseBody.
normalizedRequest: text("normalizedRequest"),
normalizedResponse: text("normalizedResponse"),
// True if any of the request/response (raw or normalized) fields
// were cut short at AI_SESSION_LOG_MAX_BODY_CHARS before storage.
truncated: integer("truncated", { mode: "boolean" })
.notNull()
.default(false),
statusCode: integer("statusCode"),
createdAt: integer("createdAt").notNull() // epoch seconds
},
(t) => [
index("idx_ai_session_log_org_created").on(t.orgId, t.createdAt),
index("idx_ai_session_log_org_provider_created").on(
t.orgId,
t.providerId,
t.createdAt
),
index("idx_ai_session_log_org_resource_created").on(
t.orgId,
t.resourceId,
t.createdAt
),
index("idx_ai_session_log_org_site_resource_created").on(
t.orgId,
t.siteResourceId,
t.createdAt
),
index("idx_ai_session_log_org_user_created").on(
t.orgId,
t.userId,
t.createdAt
),
index("idx_ai_session_log_org_virtual_api_key_created").on(
t.orgId,
t.virtualApiKeyId,
t.createdAt
),
index("idx_ai_session_log_session").on(t.sessionId)
]
);
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -647,3 +734,4 @@ export type AlertEmailAction = InferSelectModel<typeof alertEmailActions>;
export type AlertEmailRecipient = InferSelectModel<typeof alertEmailRecipients>;
export type AlertWebhookAction = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type AiSessionLog = InferSelectModel<typeof aiSessionLog>;
+340 -353
View File
@@ -99,131 +99,149 @@ export const orgDomains = sqliteTable("orgDomains", {
.references(() => domains.domainId, { onDelete: "cascade" })
});
export const sites = sqliteTable("sites", {
siteId: integer("siteId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
niceId: text("niceId").notNull(),
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
onDelete: "set null"
}),
networkId: integer("networkId").references(() => networks.networkId, {
onDelete: "set null"
}),
name: text("name").notNull(),
pubKey: text("pubKey"),
exitNodeSubnet: text("exitNodeSubnet"),
megabytesIn: integer("bytesIn").default(0),
megabytesOut: integer("bytesOut").default(0),
lastBandwidthUpdate: text("lastBandwidthUpdate"),
type: text("type").notNull(), // "newt" or "wireguard"
online: integer("online", { mode: "boolean" }).notNull().default(false),
lastPing: integer("lastPing"),
export const sites = sqliteTable(
"sites",
{
siteId: integer("siteId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
niceId: text("niceId").notNull(),
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
onDelete: "set null"
}),
networkId: integer("networkId").references(() => networks.networkId, {
onDelete: "set null"
}),
name: text("name").notNull(),
pubKey: text("pubKey"),
exitNodeSubnet: text("exitNodeSubnet"),
megabytesIn: integer("bytesIn").default(0),
megabytesOut: integer("bytesOut").default(0),
lastBandwidthUpdate: text("lastBandwidthUpdate"),
type: text("type").notNull(), // "newt" or "wireguard"
online: integer("online", { mode: "boolean" }).notNull().default(false),
lastPing: integer("lastPing"),
// exit node stuff that is how to connect to the site when it has a wg server
address: text("address"), // this is the address of the wireguard interface in newt
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
lastHolePunch: integer("lastHolePunch"),
listenPort: integer("listenPort"),
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
.default(true),
autoUpdateEnabled: integer("autoUpdateEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdateOverrideOrg: integer("autoUpdateOverrideOrg", {
mode: "boolean"
})
.notNull()
.default(false),
status: text("status").$type<"pending" | "approved">().default("approved")
});
export const resources = sqliteTable("resources", {
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{ onDelete: "set null" }
),
defaultResourcePolicyId: integer("defaultResourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{
onDelete: "restrict"
}
),
resourceGuid: text("resourceGuid", { length: 36 })
.unique()
.notNull()
.$defaultFn(() => randomUUID()),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
// exit node stuff that is how to connect to the site when it has a wg server
address: text("address"), // this is the address of the wireguard interface in newt
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
lastHolePunch: integer("lastHolePunch"),
listenPort: integer("listenPort"),
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
.default(true),
autoUpdateEnabled: integer("autoUpdateEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdateOverrideOrg: integer("autoUpdateOverrideOrg", {
mode: "boolean"
})
.notNull(),
niceId: text("niceId").notNull(),
name: text("name").notNull(),
subdomain: text("subdomain"),
fullDomain: text("fullDomain"),
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
}),
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
blockAccess: integer("blockAccess", { mode: "boolean" })
.notNull()
.default(false),
proxyPort: integer("proxyPort"),
sso: integer("sso", { mode: "boolean" }),
emailWhitelistEnabled: integer("emailWhitelistEnabled", {
mode: "boolean"
}),
applyRules: integer("applyRules", { mode: "boolean" }),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
stickySession: integer("stickySession", { mode: "boolean" })
.notNull()
.default(false),
tlsServerName: text("tlsServerName"),
setHostHeader: text("setHostHeader"),
enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "set null"
}),
headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
.notNull()
.default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
maintenanceModeType: text("maintenanceModeType", {
enum: ["forced", "automatic"]
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath"),
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false),
mode: text("mode")
.default("http")
.$type<"rdp" | "ssh" | "http" | "vnc" | "inference" | "tcp" | "udp">()
.notNull(), // rdp, ssh, http, vnc, inference
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123),
status: text("status").$type<"pending" | "approved">().default("approved")
});
.notNull()
.default(false),
status: text("status")
.$type<"pending" | "approved">()
.default("approved")
},
(table) => [index("idx_sites_orgId").on(table.orgId)]
);
export const resources = sqliteTable(
"resources",
{
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{ onDelete: "set null" }
),
defaultResourcePolicyId: integer("defaultResourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{
onDelete: "restrict"
}
),
resourceGuid: text("resourceGuid", { length: 36 })
.unique()
.notNull()
.$defaultFn(() => randomUUID()),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
niceId: text("niceId").notNull(),
name: text("name").notNull(),
subdomain: text("subdomain"),
fullDomain: text("fullDomain"),
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
}),
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
blockAccess: integer("blockAccess", { mode: "boolean" })
.notNull()
.default(false),
proxyPort: integer("proxyPort"),
sso: integer("sso", { mode: "boolean" }),
emailWhitelistEnabled: integer("emailWhitelistEnabled", {
mode: "boolean"
}),
applyRules: integer("applyRules", { mode: "boolean" }),
enabled: integer("enabled", { mode: "boolean" })
.notNull()
.default(true),
stickySession: integer("stickySession", { mode: "boolean" })
.notNull()
.default(false),
tlsServerName: text("tlsServerName"),
setHostHeader: text("setHostHeader"),
enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "set null"
}),
headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
.notNull()
.default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
maintenanceModeType: text("maintenanceModeType", {
enum: ["forced", "automatic"]
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath"),
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
wildcard: integer("wildcard", { mode: "boolean" })
.notNull()
.default(false),
mode: text("mode")
.default("http")
.$type<
"rdp" | "ssh" | "http" | "vnc" | "inference" | "tcp" | "udp"
>()
.notNull(), // rdp, ssh, http, vnc, inference
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123),
status: text("status")
.$type<"pending" | "approved">()
.default("approved")
},
(table) => [index("idx_resources_orgId").on(table.orgId)]
);
export const resourceAiProviders = sqliteTable(
"resourceAiProviders",
@@ -260,16 +278,20 @@ export const resourceAiModels = sqliteTable(
(t) => [primaryKey({ columns: [t.resourceId, t.modelId] })]
);
export const labels = sqliteTable("labels", {
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
color: text("color").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const labels = sqliteTable(
"labels",
{
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
color: text("color").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
},
(table) => [index("idx_labels_orgId").on(table.orgId)]
);
export const launcherViews = sqliteTable("launcherViews", {
viewId: integer("viewId").primaryKey({ autoIncrement: true }),
@@ -366,35 +388,46 @@ export const clientLabels = sqliteTable(
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
);
export const targets = sqliteTable("targets", {
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId").references(() => resources.resourceId, {
onDelete: "cascade"
}),
providerId: integer("providerId").references(() => aiProviders.providerId, {
onDelete: "cascade"
}),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
ip: text("ip").notNull(),
method: text("method"),
port: integer("port").notNull(),
internalPort: integer("internalPort"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
path: text("path"),
pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix
priority: integer("priority").notNull().default(100),
mode: text("mode")
.$type<"http" | "tcp" | "udp" | "ssh" | "rdp" | "vnc">()
.notNull()
.default("http"),
authToken: text("authToken")
});
export const targets = sqliteTable(
"targets",
{
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId").references(
() => resources.resourceId,
{ onDelete: "cascade" }
),
providerId: integer("providerId").references(
() => aiProviders.providerId,
{ onDelete: "cascade" }
),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
ip: text("ip").notNull(),
method: text("method"),
port: integer("port").notNull(),
internalPort: integer("internalPort"),
enabled: integer("enabled", { mode: "boolean" })
.notNull()
.default(true),
path: text("path"),
pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix
priority: integer("priority").notNull().default(100),
mode: text("mode")
.$type<"http" | "tcp" | "udp" | "ssh" | "rdp" | "vnc">()
.notNull()
.default("http"),
authToken: text("authToken")
},
(table) => [
index("idx_targets_resourceId").on(table.resourceId),
index("idx_targets_siteId").on(table.siteId)
]
);
export const targetHealthCheck = sqliteTable("targetHealthCheck", {
targetHealthCheckId: integer("targetHealthCheckId").primaryKey({
@@ -663,50 +696,67 @@ export const setupTokens = sqliteTable("setupTokens", {
dateUsed: text("dateUsed")
});
export const newts = sqliteTable("newt", {
newtId: text("id").primaryKey(),
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
})
});
export const clients = sqliteTable("clients", {
clientId: integer("clientId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
export const newts = sqliteTable(
"newt",
{
newtId: text("id").primaryKey(),
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
agent: text("agent"), // either newt or cli
agentVersion: text("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
onDelete: "set null"
}),
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
niceId: text("niceId").notNull(),
name: text("name").notNull(),
pubKey: text("pubKey"),
olmId: text("olmId"), // to lock it to a specific olm optionally
subnet: text("subnet").notNull(),
exitNodeSubnet: text("exitNodeSubnet"), // this is the subnet when connecting to an exit node
megabytesIn: integer("bytesIn"),
megabytesOut: integer("bytesOut"),
lastBandwidthUpdate: text("lastBandwidthUpdate"),
lastPing: integer("lastPing"),
type: text("type").notNull(), // "olm"
online: integer("online", { mode: "boolean" }).notNull().default(false),
// endpoint: text("endpoint"),
lastHolePunch: integer("lastHolePunch"),
archived: integer("archived", { mode: "boolean" }).notNull().default(false),
blocked: integer("blocked", { mode: "boolean" }).notNull().default(false),
approvalState: text("approvalState").$type<
"pending" | "approved" | "denied"
>()
});
},
(table) => [index("idx_newts_siteId").on(table.siteId)]
);
export const clients = sqliteTable(
"clients",
{
clientId: integer("clientId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
onDelete: "set null"
}),
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
niceId: text("niceId").notNull(),
name: text("name").notNull(),
pubKey: text("pubKey"),
olmId: text("olmId"), // to lock it to a specific olm optionally
subnet: text("subnet").notNull(),
exitNodeSubnet: text("exitNodeSubnet"), // this is the subnet when connecting to an exit node
megabytesIn: integer("bytesIn"),
megabytesOut: integer("bytesOut"),
lastBandwidthUpdate: text("lastBandwidthUpdate"),
lastPing: integer("lastPing"),
type: text("type").notNull(), // "olm"
online: integer("online", { mode: "boolean" }).notNull().default(false),
// endpoint: text("endpoint"),
lastHolePunch: integer("lastHolePunch"),
archived: integer("archived", { mode: "boolean" })
.notNull()
.default(false),
blocked: integer("blocked", { mode: "boolean" })
.notNull()
.default(false),
approvalState: text("approvalState").$type<
"pending" | "approved" | "denied"
>()
},
(table) => [
index("idx_clients_orgId").on(table.orgId),
index("idx_clients_userId").on(table.userId)
]
);
export const clientSitesAssociationsCache = sqliteTable(
"clientSitesAssociationsCache",
@@ -734,23 +784,29 @@ export const clientSiteResourcesAssociationsCache = sqliteTable(
}
);
export const olms = sqliteTable("olms", {
olmId: text("id").primaryKey(),
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
agent: text("agent"),
name: text("name"),
clientId: integer("clientId").references(() => clients.clientId, {
// we will switch this depending on the current org it wants to connect to
onDelete: "set null"
}),
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
archived: integer("archived", { mode: "boolean" }).notNull().default(false)
});
export const olms = sqliteTable(
"olms",
{
olmId: text("id").primaryKey(),
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
agent: text("agent"),
name: text("name"),
clientId: integer("clientId").references(() => clients.clientId, {
// we will switch this depending on the current org it wants to connect to
onDelete: "set null"
}),
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
archived: integer("archived", { mode: "boolean" })
.notNull()
.default(false)
},
(table) => [index("idx_olms_userId").on(table.userId)]
);
export const currentFingerprint = sqliteTable("currentFingerprint", {
fingerprintId: integer("id").primaryKey({ autoIncrement: true }),
@@ -912,17 +968,21 @@ export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
codeHash: text("codeHash").notNull()
});
export const sessions = sqliteTable("session", {
sessionId: text("id").primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull(),
issuedAt: integer("issuedAt"),
deviceAuthUsed: integer("deviceAuthUsed", { mode: "boolean" })
.notNull()
.default(false)
});
export const sessions = sqliteTable(
"session",
{
sessionId: text("id").primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull(),
issuedAt: integer("issuedAt"),
deviceAuthUsed: integer("deviceAuthUsed", { mode: "boolean" })
.notNull()
.default(false)
},
(table) => [index("idx_sessions_userId").on(table.userId)]
);
export const newtSessions = sqliteTable("newtSession", {
sessionId: text("id").primaryKey(),
@@ -940,21 +1000,30 @@ export const olmSessions = sqliteTable("clientSession", {
expiresAt: integer("expiresAt").notNull()
});
export const userOrgs = sqliteTable("userOrgs", {
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false),
autoProvisioned: integer("autoProvisioned", {
mode: "boolean"
}).default(false),
pamUsername: text("pamUsername") // cleaned username for ssh and such
});
export const userOrgs = sqliteTable(
"userOrgs",
{
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isOwner: integer("isOwner", { mode: "boolean" })
.notNull()
.default(false),
autoProvisioned: integer("autoProvisioned", {
mode: "boolean"
}).default(false),
pamUsername: text("pamUsername") // cleaned username for ssh and such
},
(table) => [
index("idx_userOrgs_userId").on(table.userId),
index("idx_userOrgs_orgId").on(table.orgId)
]
);
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
codeId: integer("id").primaryKey({ autoIncrement: true }),
@@ -982,26 +1051,30 @@ export const actions = sqliteTable("actions", {
description: text("description")
});
export const roles = sqliteTable("roles", {
roleId: integer("roleId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isAdmin: integer("isAdmin", { mode: "boolean" }),
name: text("name").notNull(),
description: text("description"),
requireDeviceApproval: integer("requireDeviceApproval", {
mode: "boolean"
}).default(false),
sshSudoMode: text("sshSudoMode").default("full"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: integer("sshCreateHomeDir", { mode: "boolean" }).default(
true
),
sshUnixGroups: text("sshUnixGroups").default("[]")
});
export const roles = sqliteTable(
"roles",
{
roleId: integer("roleId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isAdmin: integer("isAdmin", { mode: "boolean" }),
name: text("name").notNull(),
description: text("description"),
requireDeviceApproval: integer("requireDeviceApproval", {
mode: "boolean"
}).default(false),
sshSudoMode: text("sshSudoMode").default("full"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: integer("sshCreateHomeDir", {
mode: "boolean"
}).default(true),
sshUnixGroups: text("sshUnixGroups").default("[]")
},
(table) => [index("idx_roles_orgId").on(table.orgId)]
);
export const userOrgRoles = sqliteTable(
"userOrgRoles",
@@ -1928,91 +2001,6 @@ export const aiBudgetBreachEvents = sqliteTable(
]
);
// Logs the aggregated prompt + response for a single AI gateway request, for
// session replay. One row per request (not per streaming chunk). `sessionId`
// is a fresh random id per row for now - no cross-request correlation yet,
// but the column exists so a future pass can link multiple rows into a real
// multi-turn session.
export const aiSessionLog = sqliteTable(
"aiSessionLog",
{
id: integer("id").primaryKey({ autoIncrement: true }),
sessionId: text("sessionId").notNull(),
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
providerId: integer("providerId").references(
() => aiProviders.providerId,
{ onDelete: "set null" }
),
capability: text("capability").notNull(),
resourceId: integer("resourceId").references(
() => resources.resourceId,
{ onDelete: "set null" }
),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "set null" }
),
userId: text("userId").references(() => users.userId, {
onDelete: "set null"
}),
virtualApiKeyId: text("virtualApiKeyId").references(
() => virtualApiKeys.virtualApiKeyId,
{ onDelete: "set null" }
),
requestedModel: text("requestedModel"),
isStream: integer("isStream", { mode: "boolean" })
.notNull()
.default(false),
requestBody: text("requestBody"),
responseBody: text("responseBody"),
// Capability-agnostic message transcript (JSON-encoded
// NormalizedAiMessage[] from server/lib/aiMessageNormalization.ts),
// computed at write time so search/display never need per-capability
// parsing logic. Null when normalization couldn't recognize the
// shape - callers fall back to requestBody/responseBody.
normalizedRequest: text("normalizedRequest"),
normalizedResponse: text("normalizedResponse"),
// True if any of the request/response (raw or normalized) fields
// were cut short at AI_SESSION_LOG_MAX_BODY_CHARS before storage.
truncated: integer("truncated", { mode: "boolean" })
.notNull()
.default(false),
statusCode: integer("statusCode"),
createdAt: integer("createdAt").notNull() // epoch seconds
},
(t) => [
index("idx_ai_session_log_org_created").on(t.orgId, t.createdAt),
index("idx_ai_session_log_org_provider_created").on(
t.orgId,
t.providerId,
t.createdAt
),
index("idx_ai_session_log_org_resource_created").on(
t.orgId,
t.resourceId,
t.createdAt
),
index("idx_ai_session_log_org_site_resource_created").on(
t.orgId,
t.siteResourceId,
t.createdAt
),
index("idx_ai_session_log_org_user_created").on(
t.orgId,
t.userId,
t.createdAt
),
index("idx_ai_session_log_org_virtual_api_key_created").on(
t.orgId,
t.virtualApiKeyId,
t.createdAt
),
index("idx_ai_session_log_session").on(t.sessionId)
]
);
export const certificates = sqliteTable("certificates", {
certId: integer("certId").primaryKey({ autoIncrement: true }),
domain: text("domain").notNull().unique(),
@@ -2123,7 +2111,6 @@ export type AiModel = InferSelectModel<typeof aiModels>;
export type AiBudget = InferSelectModel<typeof aiBudgets>;
export type AiUsageRecord = InferSelectModel<typeof aiUsageRecords>;
export type AiBudgetBreachEvent = InferSelectModel<typeof aiBudgetBreachEvents>;
export type AiSessionLog = InferSelectModel<typeof aiSessionLog>;
export type ResourceAiProvider = InferSelectModel<typeof resourceAiProviders>;
export type SiteResourceAiProvider = InferSelectModel<
typeof siteResourceAiProviders
+8
View File
@@ -0,0 +1,8 @@
export async function startDnsServer() {
// No-op: the authoritative DNS server is only available in builds
// that include the private/enterprise feature set.
}
export async function stopDnsServer() {
// No-op counterpart to startDnsServer.
}
+6
View File
@@ -25,6 +25,8 @@ import { setHostMeta } from "@server/lib/hostMeta";
import { TraefikConfigManager } from "@server/lib/traefik/TraefikConfigManager";
import { initCleanup } from "#dynamic/cleanup";
import { startSchedulers } from "#dynamic/startSchedulers";
import { startDnsServer } from "#dynamic/dns";
import { startCertificateManager } from "#dynamic/certificates";
import license from "#dynamic/license/license";
import { fetchServerIp } from "@server/lib/serverIpService";
import { initAiModelCatalog } from "@server/lib/aiModelCatalog";
@@ -45,6 +47,10 @@ async function startServers() {
startSchedulers();
await startDnsServer();
await startCertificateManager();
// Start all servers
const apiServer = createApiServer();
const internalServer = createInternalServer();
+1 -1
View File
@@ -16,7 +16,7 @@ import { registry, openApiTags } from "./openApi";
import fs from "fs";
import path from "path";
import { APP_PATH } from "./lib/consts";
import yaml from "js-yaml";
import * as yaml from "js-yaml";
import { z } from "zod";
const dev = process.env.ENVIRONMENT !== "prod";
+31 -19
View File
@@ -121,25 +121,37 @@ export async function applyBlueprint({
(hc) => hc.targetId === target.targetId
);
if (["http", "tcp", "udp"].includes(target.mode)) {
await addProxyTargets(
site.newt.newtId,
[target],
matchingHealthcheck
? [matchingHealthcheck]
: [],
result.proxyResource.mode === "udp"
? "udp"
: "tcp",
site.newt.version
);
} else if (
["ssh", "rdp", "vnc"].includes(target.mode)
) {
await sendBrowserGatewayTargets(
site.newt.newtId,
[target],
site.newt.version
// The DB writes for all resources have already committed
// by this point, so a push failure for one target (e.g.
// a newt rejecting a malformed health check) must not
// abort pushing the rest, and must not mark the whole
// blueprint as failed when the config was actually
// persisted successfully.
try {
if (["http", "tcp", "udp"].includes(target.mode)) {
await addProxyTargets(
site.newt.newtId,
[target],
matchingHealthcheck
? [matchingHealthcheck]
: [],
result.proxyResource.mode === "udp"
? "udp"
: "tcp",
site.newt.version
);
} else if (
["ssh", "rdp", "vnc"].includes(target.mode)
) {
await sendBrowserGatewayTargets(
site.newt.newtId,
[target],
site.newt.version
);
}
} catch (e) {
logger.error(
`Failed to push target ${target.targetId} to newt on site ${site.sites.siteId}. Error: ${e}`
);
}
}
+41
View File
@@ -0,0 +1,41 @@
import { and, asc, eq, or } from "drizzle-orm";
import { Transaction, User, userOrgs, users } from "@server/db";
export async function findOrgUsersByIdentifier(
trx: Transaction,
orgId: string,
identifier: string
): Promise<User[]> {
const matches = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, identifier), eq(users.email, identifier)),
eq(userOrgs.orgId, orgId)
)
)
.orderBy(asc(users.dateCreated), asc(users.userId));
return matches.map((match) => match.user);
}
export async function resolveOrgUserIds(
trx: Transaction,
orgId: string,
identifiers: string[]
): Promise<string[]> {
const userIds = new Set<string>();
for (const identifier of identifiers) {
const matchedUsers = await findOrgUsersByIdentifier(
trx,
orgId,
identifier
);
for (const user of matchedUsers) {
userIds.add(user.userId);
}
}
return [...userIds];
}
+32 -45
View File
@@ -11,15 +11,14 @@ import {
siteNetworks,
siteResources,
Transaction,
userOrgs,
users,
userSiteResources,
networks
} from "@server/db";
import { sites } from "@server/db";
import { eq, and, ne, inArray, or, isNotNull } from "drizzle-orm";
import { eq, and, ne, inArray, isNotNull } from "drizzle-orm";
import { Config } from "./types";
import { getOrCreateLabelIds, syncSiteResourceLabels } from "./labels";
import { resolveOrgUserIds } from "./findOrgUser";
import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { getNextAvailableAliasAddress } from "../ip";
@@ -389,28 +388,22 @@ export async function updatePrivateResources(
.where(eq(userSiteResources.siteResourceId, siteResourceId));
if (resourceData.users.length > 0) {
// get userIds from username
const usersToUpdate = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(
inArray(users.username, resourceData.users),
inArray(users.email, resourceData.users)
),
eq(userOrgs.orgId, orgId)
)
);
const userIds = await resolveOrgUserIds(
trx,
orgId,
resourceData.users
);
const userIds = usersToUpdate.map((user) => user.user.userId);
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({ userId, siteResourceId }))
);
if (userIds.length > 0) {
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({
userId,
siteResourceId
}))
);
}
}
// Get all admin role IDs for this org to exclude from deletion
@@ -721,28 +714,22 @@ export async function updatePrivateResources(
}
if (resourceData.users.length > 0) {
// get userIds from username
const usersToUpdate = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(
inArray(users.username, resourceData.users),
inArray(users.email, resourceData.users)
),
eq(userOrgs.orgId, orgId)
)
);
const userIds = await resolveOrgUserIds(
trx,
orgId,
resourceData.users
);
const userIds = usersToUpdate.map((user) => user.user.userId);
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({ userId, siteResourceId }))
);
if (userIds.length > 0) {
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({
userId,
siteResourceId
}))
);
}
}
if (resourceData.machines.length > 0) {
+34 -41
View File
@@ -46,11 +46,12 @@ import { encrypt } from "@server/lib/crypto";
import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { pickPort } from "@server/routers/target/helpers";
import { and, asc, eq, isNotNull, ne, or } from "drizzle-orm";
import { and, asc, eq, isNotNull, ne } from "drizzle-orm";
import { tierMatrix } from "../billing/tierMatrix";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { Config, isTargetsOnlyResource, TargetData } from "./types";
import { getOrCreateLabelIds, syncResourceLabels } from "./labels";
import { findOrgUsersByIdentifier } from "./findOrgUser";
import { LimitId } from "../billing";
import { usageService } from "../billing/usageService";
import { syncInferenceAiConfig } from "./aiProviders";
@@ -1563,31 +1564,27 @@ async function syncUserResources(
.where(eq(userResources.resourceId, resourceId));
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
const matchedUsers = await findOrgUsersByIdentifier(
trx,
orgId,
username
);
if (!user) {
if (matchedUsers.length === 0) {
throw new Error(`User not found: ${username} in org ${orgId}`);
}
const existingUserResource = existingUserResources.find(
(rr) => rr.userId === user.user.userId
);
for (const user of matchedUsers) {
const existingUserResource = existingUserResources.find(
(rr) => rr.userId === user.userId
);
if (!existingUserResource) {
await trx.insert(userResources).values({
userId: user.user.userId,
resourceId: resourceId
});
if (!existingUserResource) {
await trx.insert(userResources).values({
userId: user.userId,
resourceId: resourceId
});
}
}
}
@@ -1955,31 +1952,27 @@ async function syncUserPolicies(
.where(eq(userPolicies.resourcePolicyId, policyId));
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
const matchedUsers = await findOrgUsersByIdentifier(
trx,
orgId,
username
);
if (!user) {
if (matchedUsers.length === 0) {
throw new Error(`User not found: ${username} in org ${orgId}`);
}
const existingUserPolicy = existingUserPoliciesList.find(
(up) => up.userId === user.user.userId
);
for (const user of matchedUsers) {
const existingUserPolicy = existingUserPoliciesList.find(
(up) => up.userId === user.userId
);
if (!existingUserPolicy) {
await trx.insert(userPolicies).values({
userId: user.user.userId,
resourcePolicyId: policyId
});
if (!existingUserPolicy) {
await trx.insert(userPolicies).values({
userId: user.userId,
resourcePolicyId: policyId
});
}
}
}
+30 -37
View File
@@ -13,7 +13,7 @@ import {
userPolicies,
users
} from "@server/db";
import { eq, and, or } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { Config, ResourcePolicyData } from "./types";
import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names";
@@ -22,6 +22,7 @@ import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix";
import { findOrgUsersByIdentifier } from "./findOrgUser";
export type ResourcePoliciesResults = {
resourcePolicyId: number;
@@ -466,34 +467,30 @@ async function syncUserPolicies(
.where(eq(userPolicies.resourcePolicyId, policyId));
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
const matchedUsers = await findOrgUsersByIdentifier(
trx,
orgId,
username
);
if (!user) {
if (matchedUsers.length === 0) {
logger.warn(
`User '${username}' not found in org '${orgId}', skipping`
);
continue;
}
const alreadyExists = existingUserPolicies.some(
(up) => up.userId === user.user.userId
);
for (const user of matchedUsers) {
const alreadyExists = existingUserPolicies.some(
(up) => up.userId === user.userId
);
if (!alreadyExists) {
await trx.insert(userPolicies).values({
userId: user.user.userId,
resourcePolicyId: policyId
});
if (!alreadyExists) {
await trx.insert(userPolicies).values({
userId: user.userId,
resourcePolicyId: policyId
});
}
}
}
@@ -536,29 +533,25 @@ async function addUserPolicies(
trx: Transaction
) {
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
const matchedUsers = await findOrgUsersByIdentifier(
trx,
orgId,
username
);
if (!user) {
if (matchedUsers.length === 0) {
logger.warn(
`User '${username}' not found in org '${orgId}', skipping`
);
continue;
}
await trx.insert(userPolicies).values({
userId: user.user.userId,
resourcePolicyId: policyId
});
for (const user of matchedUsers) {
await trx.insert(userPolicies).values({
userId: user.userId,
resourcePolicyId: policyId
});
}
}
}
+29 -3
View File
@@ -29,8 +29,34 @@ export const SiteSchema = z.object({
"docker-socket-enabled": z.boolean().optional().default(true)
});
// A malformed hostname (e.g. stray whitespace) is silently accepted here but
// fails to parse as a URL when newt builds the health check request, which
// takes the target out of the routing pool and breaks the resource entirely
// (see #3677). Validate eagerly so blueprints reject it up front instead.
const healthCheckHostnameSchema = z
.string()
.trim()
.min(1)
.refine((val) => !/\s/.test(val), {
message: "Hostname must not contain whitespace"
})
.refine(
(val) => {
if (z.union([z.ipv4(), z.ipv6()]).safeParse(val).success) {
return true;
}
const hostnameRegex =
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
return hostnameRegex.test(val);
},
{
message:
"Hostname must be a valid IP address or hostname (no spaces or invalid characters)"
}
);
export const TargetHealthCheckSchema = z.object({
hostname: z.string(),
hostname: healthCheckHostnameSchema,
port: z.int().min(1).max(65535),
enabled: z.boolean().optional().default(true),
path: z.string().optional().default("/"),
@@ -101,7 +127,7 @@ export const AuthSchema = z.object({
export const RuleSchema = z
.object({
action: z.enum(["allow", "deny", "pass"]),
match: z.enum(["cidr", "path", "ip", "country", "asn", "region"]),
match: z.enum(["cidr", "path", "ip", "country", "country_is_not", "asn", "region"]),
value: z.coerce.string(),
priority: z.int().optional(),
enabled: z.boolean().optional().default(true)
@@ -136,7 +162,7 @@ export const RuleSchema = z
)
.refine(
(rule) => {
if (rule.match === "country") {
if (rule.match === "country" || rule.match === "country_is_not") {
if (!hasMaxmindCountryDb) {
return false;
}
+2 -8
View File
@@ -1,13 +1,7 @@
import NodeCache from "node-cache";
import logger from "@server/logger";
import { createLocalCache } from "@server/lib/createLocalCache";
// Create local cache with maxKeys limit to prevent memory leaks
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
export const localCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
export const localCache = createLocalCache();
// Log cache statistics periodically for monitoring
// setInterval(() => {
+5
View File
@@ -112,6 +112,11 @@ export class Config {
? "true"
: "false";
process.env.FLAGS_DISABLE_VIRTUAL_API_KEYS_UI = parsedConfig.flags
?.disable_virtual_api_keys_ui
? "true"
: "false";
this.rawConfig = parsedConfig;
}
+1 -1
View File
@@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process
export const APP_VERSION = "1.22.0";
export const APP_VERSION = "1.23.0";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);
+82
View File
@@ -0,0 +1,82 @@
import { LRUCache } from "lru-cache";
const DEFAULT_MAX_KEYS = 10000;
const DEFAULT_TTL_SECONDS = 3600;
export type LocalCache = {
get<T>(key: string): T | undefined;
set(key: string, value: unknown, ttlSeconds?: number): boolean;
del(key: string | string[]): number;
has(key: string): boolean;
keys(): string[];
flushAll(): void;
getStats(): { keys: number };
getTtl(key: string): number | undefined;
};
export function createLocalCache(
max = DEFAULT_MAX_KEYS,
ttlSeconds = DEFAULT_TTL_SECONDS
): LocalCache {
const lru = new LRUCache<string, {}>({
max,
ttl: ttlSeconds * 1000,
updateAgeOnGet: false
});
return {
get<T>(key: string): T | undefined {
return lru.get(key) as T | undefined;
},
set(key: string, value: unknown, ttlSeconds?: number): boolean {
const stored = value as {};
if (ttlSeconds === undefined) {
lru.set(key, stored);
} else if (ttlSeconds === 0) {
lru.set(key, stored, { ttl: 0 });
} else {
lru.set(key, stored, { ttl: ttlSeconds * 1000 });
}
return true;
},
del(key: string | string[]): number {
const keys = Array.isArray(key) ? key : [key];
let deleted = 0;
for (const k of keys) {
if (lru.delete(k)) {
deleted++;
}
}
return deleted;
},
has(key: string): boolean {
return lru.has(key);
},
keys(): string[] {
return [...lru.keys()];
},
flushAll(): void {
lru.clear();
},
getStats(): { keys: number } {
return { keys: lru.size };
},
getTtl(key: string): number | undefined {
if (!lru.has(key)) {
return undefined;
}
const remaining = lru.getRemainingTTL(key);
if (!Number.isFinite(remaining)) {
return 0;
}
return Date.now() + remaining;
}
};
}
+1 -1
View File
@@ -71,7 +71,7 @@ export async function withRetry<T>(
const jitter = Math.random() * baseDelay;
const delay = baseDelay + jitter;
logger.warn(
`Transient DB error in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`,
`Transient DB issue in ${context}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`,
{ code: error?.code ?? error?.cause?.code }
);
await new Promise((resolve) => setTimeout(resolve, delay));
+3
View File
@@ -0,0 +1,3 @@
export function createCname(domainId: string, baseDomain: string) {}
export function createNs() {}
+10 -15
View File
@@ -31,7 +31,6 @@ export async function validateAndConstructDomain(
subdomain?: string | null
): Promise<DomainValidationResult> {
try {
// Query domain with organization access check
const [domainRes] = await db
.select()
.from(domains)
@@ -42,6 +41,10 @@ export async function validateAndConstructDomain(
eq(orgDomains.orgId, orgId),
eq(orgDomains.domainId, domainId)
)
)
.leftJoin(
domainNamespaces,
eq(domainNamespaces.domainId, domainId)
);
// Check if domain exists
@@ -52,7 +55,7 @@ export async function validateAndConstructDomain(
};
}
if (!domainRes.orgDomains) {
if (!domainRes.orgDomains && !domainRes.domainNamespaces) {
return {
success: false,
error: `Organization does not have access to domain with ID ${domainId}`
@@ -83,19 +86,11 @@ export async function validateAndConstructDomain(
}
// Wildcard subdomains are not allowed on namespace (provided/free) domains
if (isWildcard) {
const [namespaceDomain] = await db
.select()
.from(domainNamespaces)
.where(eq(domainNamespaces.domainId, domainId))
.limit(1);
if (namespaceDomain) {
return {
success: false,
error: "Wildcard subdomains are not supported for provided or free domains. Use a specific subdomain instead."
};
}
if (isWildcard && domainRes.domainNamespaces) {
return {
success: false,
error: "Wildcard subdomains are not supported for provided or free domains. Use a specific subdomain instead."
};
}
if (
+14
View File
@@ -0,0 +1,14 @@
// Tracks, per process lifetime, whether a given exit node has ever checked in
// (called /gerbil/get-config) since this Pangolin instance started. This lets
// callers distinguish "gerbil hasn't come up yet" (expected briefly after a
// restart, since gerbil depends on pangolin's container starting first) from
// "gerbil was reachable and now isn't" (a real problem worth an error log).
const checkedInExitNodeIds = new Set<number>();
export function markExitNodeCheckedIn(exitNodeId: number): void {
checkedInExitNodeIds.add(exitNodeId);
}
export function hasExitNodeCheckedIn(exitNodeId: number): boolean {
return checkedInExitNodeIds.has(exitNodeId);
}
+13 -6
View File
@@ -1,6 +1,7 @@
import axios from "axios";
import logger from "@server/logger";
import { ExitNode } from "@server/db";
import { hasExitNodeCheckedIn } from "./exitNodeCheckIn";
interface ExitNodeRequest {
remoteType?: string;
@@ -72,13 +73,19 @@ export async function sendToExitNode(
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error(
`Error making ${method} request (can Pangolin see Gerbil HTTP API?) for exit node at ${exitNode.reachableAt} (status: ${error.response?.status}): ${error.message}`
);
const message = axios.isAxiosError(error)
? `Error making ${method} request (can Pangolin see Gerbil HTTP API?) for exit node at ${exitNode.reachableAt} (status: ${error.response?.status}): ${error.message}`
: `Error making ${method} request for exit node at ${exitNode.reachableAt}: ${error}`;
// The exit node (gerbil) may still be starting up and not yet
// reachable. Until it has checked in at least once, log this at a
// lower level since it's expected; once it has checked in, a
// connection failure is a real problem.
if (hasExitNodeCheckedIn(exitNode.exitNodeId)) {
logger.error(message);
} else {
logger.error(
`Error making ${method} request for exit node at ${exitNode.reachableAt}: ${error}`
logger.warn(
`${message} (exit node has not checked in yet since startup, this is expected briefly)`
);
}
throw error;
+1
View File
@@ -1,5 +1,6 @@
export * from "./exitNodes";
export * from "./exitNodeComms";
export * from "./exitNodeCheckIn";
export * from "./subnet";
export * from "./getCurrentExitNodeId";
export * from "./calculateExitNodeWeight";
+4 -20
View File
@@ -1,5 +1,5 @@
import fs from "fs";
import yaml from "js-yaml";
import * as yaml from "js-yaml";
import { configFilePath1, configFilePath2 } from "./consts";
import { z } from "zod";
import stoi from "./stoi";
@@ -348,8 +348,8 @@ export const configSchema = z
.optional()
.pipe(z.string())
.transform((url) => url.toLowerCase()),
subnet_group: z.string().optional().default("100.89.137.0/20"),
block_size: z.number().positive().gt(0).optional().default(24),
subnet_group: z.string().optional().default("100.89.137.0/18"),
block_size: z.number().positive().gt(0).optional().default(22),
site_block_size: z
.number()
.positive()
@@ -442,6 +442,7 @@ export const configSchema = z
disable_config_managed_domains: z.boolean().optional(),
disable_product_help_banners: z.boolean().optional(),
disable_enterprise_features: z.boolean().optional(),
disable_virtual_api_keys_ui: z.boolean().optional(),
enable_acme_cert_sync: z.boolean().optional().default(true),
disable_private_http_placeholder: z
.boolean()
@@ -492,23 +493,6 @@ export const configSchema = z
.prefault({})
})
.optional()
.prefault({}),
dns: z
.object({
nameservers: z
.array(z.string().optional().optional())
.optional()
.default([
"ns1.pangolin.net",
"ns2.pangolin.net",
"ns3.pangolin.net"
]),
cname_extension: z
.string()
.optional()
.default("cname.pangolin.net")
})
.optional()
.prefault({})
})
.refine(
+72
View File
@@ -0,0 +1,72 @@
import logger from "@server/logger";
export async function withRetry<T>(
fn: () => Promise<T>,
options: {
retries?: number;
baseDelayMs?: number;
label?: string;
// Called with each caught error to decide whether it's worth
// retrying. Defaults to retrying everything (existing behavior) -
// pass this to exclude errors that are known to be permanent (e.g.
// an upstream rate limit or validation rejection) rather than
// transient, so they fail fast instead of wasting retry attempts.
shouldRetry?: (error: unknown) => boolean;
} = {}
): Promise<T> {
const {
retries = 3,
baseDelayMs = 250,
label = "operation",
shouldRetry = () => true
} = options;
let attempt = 0;
while (true) {
try {
return await fn();
} catch (error) {
attempt++;
if (attempt > retries || !shouldRetry(error)) {
throw error;
}
// Exponential backoff with jitter so retries don't all land at once.
const delay =
baseDelayMs * 2 ** (attempt - 1) * (0.5 + Math.random());
logger.warn(
`${label} failed (attempt ${attempt}/${retries + 1}), retrying in ${delay.toFixed(0)}ms`,
error
);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
// Bounds an operation that has no timeout of its own (e.g. acme-client's
// axios instance never sets one, so a stalled TCP connection to the ACME
// server hangs forever instead of erroring). Without this, a single hung
// call can leave its caller's promise permanently unsettled - fatal for
// code that gates future work on that promise resolving, like the
// scheduler's runExclusive() waiting on a batch's Promise.all.
export async function withTimeout<T>(
promise: Promise<T>,
ms: number,
label = "operation"
): Promise<T> {
let timer: NodeJS.Timeout;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(
() => reject(new Error(`${label} timed out after ${ms}ms`)),
ms
);
});
try {
return await Promise.race([promise, timeout]);
} finally {
clearTimeout(timer!);
}
}
+50 -1
View File
@@ -3,6 +3,8 @@ import config from "./config";
import { getHostMeta } from "./hostMeta";
import logger from "@server/logger";
import {
aiProviders,
aiUsageRecords,
alertRules,
apiKeys,
blueprints,
@@ -11,7 +13,16 @@ import {
siteResources
} from "@server/db";
import { sites, users, orgs, resources, clients, idp } from "@server/db";
import { eq, count, notInArray, and, isNotNull, isNull } from "drizzle-orm";
import {
eq,
count,
countDistinct,
notInArray,
and,
isNotNull,
isNull,
gte
} from "drizzle-orm";
import { APP_VERSION } from "./consts";
import crypto from "crypto";
import { UserType } from "@server/types/UserTypes";
@@ -172,6 +183,25 @@ class TelemetryClient {
.select({ count: count() })
.from(blueprints);
const [aiProvidersCount] = await db
.select({ count: count() })
.from(aiProviders);
const [orgsWithAiProviders] = await db
.select({ count: countDistinct(aiProviders.orgId) })
.from(aiProviders);
const usageWindowStart =
Math.floor(Date.now() / 1000) -
this.collectionIntervalDays * 24 * 60 * 60;
const [aiUsageRecordsRecent] = await db
.select({ count: count() })
.from(aiUsageRecords)
.where(gte(aiUsageRecords.createdAt, usageWindowStart));
const [orgsWithRecentAiUsage] = await db
.select({ count: countDistinct(aiUsageRecords.orgId) })
.from(aiUsageRecords)
.where(gte(aiUsageRecords.createdAt, usageWindowStart));
const supporterKey = config.getSupporterData();
const allPrivateResources = await db.select().from(siteResources);
@@ -182,6 +212,7 @@ class TelemetryClient {
let numPrivResourceCidr = 0;
let numPrivResourceHttp = 0;
let numPrivResourceSsh = 0;
let numPrivResourceInference = 0;
for (const res of allPrivateResources) {
if (res.mode === "host") {
numPrivResourceHosts += 1;
@@ -191,6 +222,8 @@ class TelemetryClient {
numPrivResourceHttp += 1;
} else if (res.mode === "ssh") {
numPrivResourceSsh += 1;
} else if (res.mode === "inference") {
numPrivResourceInference += 1;
}
if (res.alias) {
@@ -211,6 +244,11 @@ class TelemetryClient {
numPrivateResourceCidr: numPrivResourceCidr,
numPrivateResourceHttp: numPrivResourceHttp,
numPrivateResourceSsh: numPrivResourceSsh,
numPrivateResourceInference: numPrivResourceInference,
numAiProviders: aiProvidersCount.count,
numOrgsWithAiProviders: orgsWithAiProviders.count,
numAiUsageRecordsRecent: aiUsageRecordsRecent.count,
numOrgsWithRecentAiUsage: orgsWithRecentAiUsage.count,
numAlertRules: numAlertRules.count,
numUserDevices: userDevicesCount.count,
numMachineClients: machineClients.count,
@@ -323,6 +361,17 @@ class TelemetryClient {
num_resources_non_http: stats.resources.filter(
(r) => r.mode !== "http"
).length,
num_resources_ai_gateway: stats.resources.filter(
(r) => r.mode === "inference"
).length,
num_private_resources_ai_gateway:
stats.numPrivateResourceInference,
num_ai_providers: stats.numAiProviders,
num_orgs_with_ai_providers: stats.numOrgsWithAiProviders,
num_ai_usage_records_recent:
stats.numAiUsageRecordsRecent,
num_orgs_with_recent_ai_usage:
stats.numOrgsWithRecentAiUsage,
num_newt_sites: stats.sites.filter((s) => s.type === "newt")
.length,
num_local_sites: stats.sites.filter(
+39 -21
View File
@@ -6,7 +6,10 @@ import * as yaml from "js-yaml";
import axios from "axios";
import { db, exitNodes } from "@server/db";
import { eq } from "drizzle-orm";
import { getCurrentExitNodeId } from "@server/lib/exitNodes";
import {
getCurrentExitNodeId,
hasExitNodeCheckedIn
} from "@server/lib/exitNodes";
import { getTraefikConfig } from "#dynamic/lib/traefik";
import { getValidCertificatesForDomains } from "@server/lib/certificates";
import { sendToExitNode } from "#dynamic/lib/exitNodes";
@@ -341,10 +344,6 @@ export class TraefikConfigManager {
const { domains, traefikConfig } = getTraefikConfig;
// Add static domains from config
// const staticDomains = [config.getRawConfig().app.dashboard_url];
// staticDomains.forEach((domain) => domains.add(domain));
// Log if domains changed
if (
this.lastActiveDomains.size !== domains.size ||
@@ -358,7 +357,7 @@ export class TraefikConfigManager {
this.lastActiveDomains = new Set(domains);
}
if (process.env.USE_PANGOLIN_DNS === "true" && build != "oss") {
if (process.env.CERT_MODE === "pangolin" && build != "oss") {
// Scan current local certificate state
this.lastLocalCertificateState =
await this.scanLocalCertificateState();
@@ -439,13 +438,13 @@ export class TraefikConfigManager {
// Always ensure all existing certificates (including wildcards) are in the config
await this.updateDynamicConfigFromLocalCerts(domains);
} else {
const timeSinceLastFetch = this.lastCertificateFetch
? Math.round(
(Date.now() -
this.lastCertificateFetch.getTime()) /
(1000 * 60)
)
: 0;
// const timeSinceLastFetch = this.lastCertificateFetch
// ? Math.round(
// (Date.now() -
// this.lastCertificateFetch.getTime()) /
// (1000 * 60)
// )
// : 0;
// logger.debug(
// `Skipping certificate fetch - no changes detected and within 24-hour window (last fetch: ${timeSinceLastFetch} minutes ago)`
@@ -466,32 +465,51 @@ export class TraefikConfigManager {
await this.writeTraefikDynamicConfig(traefikConfig);
// Send domains to SNI proxy
let exitNodeForSni: typeof exitNodes.$inferSelect | undefined;
try {
let exitNode;
if (config.getRawConfig().gerbil.exit_node_name) {
const exitNodeName =
config.getRawConfig().gerbil.exit_node_name!;
[exitNode] = await db
[exitNodeForSni] = await db
.select()
.from(exitNodes)
.where(eq(exitNodes.name, exitNodeName))
.limit(1);
} else {
[exitNode] = await db.select().from(exitNodes).limit(1);
[exitNodeForSni] = await db
.select()
.from(exitNodes)
.limit(1);
}
if (exitNode) {
await sendToExitNode(exitNode, {
if (exitNodeForSni) {
await sendToExitNode(exitNodeForSni, {
localPath: "/update-local-snis",
method: "POST",
data: { fullDomains: Array.from(domains) }
data: {
fullDomains: [
...Array.from(domains),
...config.getRawConfig().traefik.static_domains
]
}
});
} else {
logger.error(
logger.warn(
"No exit node found. Has gerbil registered yet?"
);
}
} catch (err) {
logger.error("Failed to post domains to SNI proxy:", err);
// sendToExitNode already logs the underlying connection
// error at the appropriate level (warn before the exit node
// has checked in since startup, error after), so avoid
// double-logging it as an error here.
if (
exitNodeForSni &&
!hasExitNodeCheckedIn(exitNodeForSni.exitNodeId)
) {
logger.warn("Failed to post domains to SNI proxy:", err);
} else {
logger.error("Failed to post domains to SNI proxy:", err);
}
}
// Update active domains tracking
+6 -12
View File
@@ -1,7 +1,5 @@
/**
* Build the Host()/HostRegexp() Traefik rule for a resource's domain.
* Wildcard resources match any single subdomain via HostRegexp.
*/
// Build the Host()/HostRegexp() Traefik rule for a resource's domain.
// Wildcard resources match any single subdomain via HostRegexp.
export function buildHostRule(
fullDomain: string,
wildcard?: boolean | null
@@ -14,10 +12,8 @@ export function buildHostRule(
return `Host(\`${fullDomain}\`)`;
}
/**
* Append a path-matching clause to a Traefik rule based on the resource's
* configured path and pathMatchType.
*/
// Append a path-matching clause to a Traefik rule based on the resource's
// configured path and pathMatchType.
export function appendPathMatch(
rule: string,
path: string | null | undefined,
@@ -40,10 +36,8 @@ export function appendPathMatch(
return rule;
}
/**
* Compute the router priority for a resource, favoring an explicit override
* and otherwise deriving it from the path match specificity.
*/
// Compute the router priority for a resource, favoring an explicit override
// and otherwise deriving it from the path match specificity.
export function computeRoutePriority(
priority: number | null | undefined,
path: string | null | undefined,
@@ -68,6 +68,11 @@ export async function verifyApiKeyAccessTokenAccess(
);
}
if (apiKey.isRoot) {
// Root keys can access any access token in any org
return next();
}
if (!resource.orgId) {
return next(
createHttpError(
+17
View File
@@ -0,0 +1,17 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export {
startCertificateManager,
stopCertificateManager
} from "./lib/certificates";
+4
View File
@@ -20,6 +20,8 @@ import { flushSiteBandwidthToDb } from "@server/routers/gerbil/receiveBandwidth"
import { stopPingAccumulator } from "@server/routers/newt/pingAccumulator";
import { shutdownUsageRecorder } from "@server/lib/aiBudgetEnforcement";
import { shutdownAiSessionLogger } from "@server/routers/aiGateway/logAiSession";
import { stopDnsServer } from "./dns";
import { stopCertificateManager } from "./certificates";
async function cleanup() {
await stopPingAccumulator();
@@ -31,6 +33,8 @@ async function cleanup() {
await rateLimitService.cleanup();
await wsCleanup();
await logStreamingManager.shutdown();
await stopDnsServer();
await stopCertificateManager();
process.exit(0);
}
+37
View File
@@ -0,0 +1,37 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { AuthoritativeDNSServer } from "#private/lib/dns";
import { privateConfig } from "#private/lib/config";
let dnsServer: AuthoritativeDNSServer | undefined;
export async function startDnsServer() {
const dnsConfig = privateConfig.getRawPrivateConfig().dns;
if (!dnsConfig || !dnsConfig.enabled) {
return;
}
dnsServer = new AuthoritativeDNSServer(dnsConfig.listen_port);
await dnsServer.start();
}
export async function stopDnsServer() {
if (!dnsServer) {
return;
}
await dnsServer.stop();
dnsServer = undefined;
}
+15 -25
View File
@@ -11,17 +11,11 @@
* This file is not licensed under the AGPLv3.
*/
import NodeCache from "node-cache";
import logger from "@server/logger";
import { redisManager, regionalRedisManager } from "@server/private/lib/redis";
import { createLocalCache } from "@server/lib/createLocalCache";
import { redisManager, regionalRedisManager } from "#private/lib/redis";
// Create local cache with maxKeys limit to prevent memory leaks
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
export const localCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
export const localCache = createLocalCache();
// Log cache statistics periodically for monitoring
// setInterval(() => {
@@ -97,11 +91,11 @@ class AdaptiveCache {
const value = await redisManager.get(key);
if (value !== null) {
logger.debug(`Cache hit in Redis: ${key}`);
// logger.debug(`Cache hit in Redis: ${key}`);
return JSON.parse(value) as T;
}
logger.debug(`Cache miss in Redis: ${key}`);
// logger.debug(`Cache miss in Redis: ${key}`);
return undefined;
} catch (error) {
logger.error(`Redis get error for key ${key}:`, error);
@@ -134,7 +128,7 @@ class AdaptiveCache {
const success = await redisManager.del(k);
if (success) {
deletedCount++;
logger.debug(`Deleted key from Redis: ${k}`);
// logger.debug(`Deleted key from Redis: ${k}`);
}
}
@@ -161,7 +155,7 @@ class AdaptiveCache {
const success = localCache.del(k);
if (success > 0) {
deletedCount++;
logger.debug(`Deleted key from local cache: ${k}`);
// logger.debug(`Deleted key from local cache: ${k}`);
}
}
@@ -229,7 +223,7 @@ class AdaptiveCache {
}
localCache.flushAll();
logger.debug("Flushed local cache");
// logger.debug("Flushed local cache");
}
/**
@@ -301,15 +295,11 @@ export default cache;
/**
* Regional adaptive cache backed by the in-cluster Redis instance.
* Falls back to a local NodeCache when the regional Redis is unavailable.
* Falls back to a local LRU cache when the regional Redis is unavailable.
* Use this for data that is regional in nature (e.g. status history) so
* reads are served from the same cluster the user is hitting.
*/
const regionalLocalCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
const regionalLocalCache = createLocalCache();
class RegionalAdaptiveCache {
private useRedis(): boolean {
@@ -332,7 +322,7 @@ class RegionalAdaptiveCache {
redisTtl
);
if (success) {
logger.debug(`[regional] Set key in Redis: ${key}`);
// logger.debug(`[regional] Set key in Redis: ${key}`);
return true;
}
} catch (error) {
@@ -353,10 +343,10 @@ class RegionalAdaptiveCache {
try {
const value = await regionalRedisManager.get(key);
if (value !== null) {
logger.debug(`[regional] Cache hit in Redis: ${key}`);
// logger.debug(`[regional] Cache hit in Redis: ${key}`);
return JSON.parse(value) as T;
}
logger.debug(`[regional] Cache miss in Redis: ${key}`);
// logger.debug(`[regional] Cache miss in Redis: ${key}`);
return undefined;
} catch (error) {
logger.error(
@@ -385,7 +375,7 @@ class RegionalAdaptiveCache {
const success = await regionalRedisManager.del(k);
if (success) {
deletedCount++;
logger.debug(`[regional] Deleted key from Redis: ${k}`);
// logger.debug(`[regional] Deleted key from Redis: ${k}`);
}
}
if (deletedCount === keys.length) return deletedCount;
@@ -400,7 +390,7 @@ class RegionalAdaptiveCache {
const count = regionalLocalCache.del(k);
if (count > 0) {
deletedCount++;
logger.debug(`[regional] Deleted key from local cache: ${k}`);
// logger.debug(`[regional] Deleted key from local cache: ${k}`);
}
}
return deletedCount;
@@ -0,0 +1,298 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import * as acme from "acme-client";
import * as fs from "fs";
import { eq } from "drizzle-orm/sql";
import { privateConfig as config } from "#private/lib/config";
import { DnsChallenge, db, dnsChallenge } from "@server/db";
import { withRetry } from "@server/lib/retry";
import logger from "@server/logger";
import { acmeRateLimiter } from "./acmeRateLimiter";
// acme-client's own retry/backoff logging (429 retries, 5xx retries, each
// status-poll tick in waitForValidStatus) is a no-op by default - it only
// activates via DEBUG=acme-client or this call, neither of which was wired
// up. Without it, a cert silently retrying a Let's Encrypt rate limit for
// several minutes is indistinguishable in our logs from one that's actually
// hung, since our own logging only wraps the call, not what happens inside
// it. Must run before any AcmeClient method is called.
acme.setLogger((msg: string) => logger.info(`[acme-client] ${msg}`));
// acme-client's axios retry wrapper treats any response-less request error
// (timeout, connection reset, DNS blip reaching the ACME server) as
// retryable, but once its internal retries are exhausted it falls through to
// `validateStatus(response)` with `response` still undefined, throwing this
// uninformative TypeError instead of the real network error.
// https://github.com/publishlab/node-acme-client/blob/master/src/axios.js
function isUnresponsiveAcmeError(error: unknown): boolean {
return (
error instanceof TypeError &&
error.message ===
"Cannot read properties of undefined (reading 'config')"
);
}
function normalizeAcmeError(error: unknown): Error {
if (isUnresponsiveAcmeError(error)) {
return new Error(
"ACME server did not respond after repeated attempts (network error reaching the ACME endpoint)",
{ cause: error }
);
}
return error instanceof Error ? error : new Error(String(error));
}
export class AcmeClientManager {
private client: acme.Client | null = null;
private accountKey: string | null = null;
async initialize() {
try {
this.accountKey = await this.loadAccountKey();
this.client = new acme.Client({
directoryUrl: config.getRawConfig().acme!.acme_directory_url,
accountKey: this.accountKey
});
// Try to create account or get existing one
await this.client.createAccount({
termsOfServiceAgreed: true,
contact: [`mailto:${config.getRawConfig().acme!.contact_email}`]
});
logger.info("ACME client initialized successfully");
} catch (error) {
logger.error("Failed to initialize ACME client:", error);
throw error;
}
}
private async loadAccountKey(): Promise<string> {
const keyPath = config.getRawConfig().acme!.acme_account_key_path;
if (fs.existsSync(keyPath)) {
logger.info("Loading existing account key");
return fs.readFileSync(keyPath, "utf8");
} else {
logger.info("Generating new account key");
const privateKey = await acme.crypto.createPrivateKey();
const privateKeyString = privateKey.toString();
fs.writeFileSync(keyPath, privateKeyString);
return privateKeyString;
}
}
getClient(): acme.Client {
if (!this.client) {
throw new Error("ACME client not initialized");
}
return this.client;
}
async createOrder(domain: string, wildcard: boolean = false): Promise<any> {
const client = this.getClient();
const identifiers = wildcard
? [
{ type: "dns", value: domain },
{ type: "dns", value: `*.${domain}` }
]
: [{ type: "dns", value: domain }];
await acmeRateLimiter.acquire();
const order = await client.createOrder({
identifiers
});
if (wildcard) {
logger.info(`Created wildcard order for domain: ${domain}`);
} else {
logger.info(`Created order for domain: ${domain}`);
}
return order;
}
async getAuthorizations(order: any): Promise<any[]> {
const client = this.getClient();
await acmeRateLimiter.acquire();
return client.getAuthorizations(order);
}
async handleDnsChallenge(
dnsChallenges: {
authz: any;
challenge: any;
}[]
): Promise<void> {
const client = this.getClient();
let challengeDomains: DnsChallenge[] = [];
for (const { authz, challenge } of dnsChallenges) {
const keyAuthorization =
await client.getChallengeKeyAuthorization(challenge);
// Extract the domain from authorization
const domain = authz.identifier.value;
// Store challenge in database for DNS server to pick up
challengeDomains = await withRetry(
() =>
db
.insert(dnsChallenge)
.values({
domain: domain,
token: challenge.token,
keyAuthorization,
createdAt: Math.floor(Date.now() / 1000),
expiresAt: Math.floor(
(Date.now() +
config.getRawConfig().acme!
.challenge_ttl_ms) /
1000
)
})
.returning(),
{ label: `insert dnsChallenge for domain ${domain}` }
);
logger.info(
`DNS challenge stored for domain: ${domain} as token ${challenge.token} and keyAuthorization`
);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
const failedDomains: string[] = [];
for (const { authz, challenge } of dnsChallenges) {
const domain = authz.identifier.value;
const challengeDomain = `_acme-challenge.${domain}`;
try {
// The ACME server occasionally has a transient network blip
// mid-sequence; retry the whole verify/complete/wait sequence
// rather than just the DNS challenge propagation wait, since
// these calls are safe to repeat against the ACME server.
await withRetry(
async () => {
// Verify challenge
await acmeRateLimiter.acquire();
await client.verifyChallenge(authz, challenge);
// Complete challenge
logger.info(
`Completing challenge for domain: ${challengeDomain}`
);
await acmeRateLimiter.acquire();
await client.completeChallenge(challenge);
// Wait for validation
logger.info(
`Waiting for challenge to be validated for domain: ${challengeDomain}...`
);
await acmeRateLimiter.acquire();
await client.waitForValidStatus(challenge);
},
{
retries: 2,
baseDelayMs: 5000,
label: `ACME challenge completion for domain ${domain}`,
// Only retry the known network-blip crash - a
// genuine validation failure (e.g. challenge marked
// "invalid" because the DNS record wasn't found) is
// permanent and should fail immediately instead of
// burning Let's Encrypt's per-hostname failed-
// validation rate limit on retries that can't help.
shouldRetry: isUnresponsiveAcmeError
}
);
logger.info(`Challenge completed for domain: ${domain}`);
} catch (error) {
logger.error(
`Failed to complete challenge for domain ${domain}:`,
normalizeAcmeError(error)
);
failedDomains.push(domain);
}
}
for (const challengeDomain of challengeDomains) {
await this.removeDnsChallenge(challengeDomain.dnsChallengeId);
logger.info(
`Removed DNS challenge for domain: ${challengeDomain.domain}`
);
}
// A failed dns-01 challenge leaves the order stuck in "pending" -
// finalizing it would just fail with a confusing ACME error, so
// stop here and let the caller mark the certificate as failed.
if (failedDomains.length > 0) {
throw new Error(
`DNS-01 challenge validation failed for domain(s): ${failedDomains.join(", ")}`
);
}
}
async removeDnsChallenge(dnsChallengeId: number): Promise<void> {
try {
await withRetry(
() =>
db
.delete(dnsChallenge)
.where(eq(dnsChallenge.dnsChallengeId, dnsChallengeId)),
{ label: `delete dnsChallenge ${dnsChallengeId}` }
);
} catch (error) {
logger.error(
`Failed to clean up DNS challenge for id ${dnsChallengeId}:`,
error
);
}
}
async finalizeCertificate(
order: any,
domain: string,
wildcard: boolean = false
): Promise<{ certificate: string; privateKey: string }> {
const client = this.getClient();
const altNames = wildcard ? [`*.${domain}`, domain] : [domain];
// Create CSR
const [privateKey, csr] = await acme.crypto.createCsr({
altNames
});
// Finalize order
await acmeRateLimiter.acquire();
const finalizedOrder = await client.finalizeOrder(order, csr);
// Get certificate
await acmeRateLimiter.acquire();
const certificate = await client.getCertificate(finalizedOrder);
logger.info(`Certificate obtained for domain: ${domain}`);
return {
certificate: certificate.toString(),
privateKey: privateKey.toString()
};
}
}
export const acmeClientManager = new AcmeClientManager();
@@ -0,0 +1,71 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { privateConfig as config } from "#private/lib/config";
import logger from "@server/logger";
import { redis } from "../redis";
// Caps outgoing ACME API calls to a fixed budget per wall-clock second,
// shared across all pops workers via Redis (mirrors the lockManager pattern
// in @lib/lock) - a per-process limiter wouldn't be enough since multiple
// workers issue certificates against the same Let's Encrypt account.
const ACQUIRE_SCRIPT = `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('PEXPIRE', key, 2000)
end
if current > limit then
return 0
else
return 1
end
`;
class AcmeRateLimiter {
async acquire(): Promise<void> {
const limit =
config.getRawConfig().acme?.acme_requests_per_second ?? 15;
for (;;) {
const bucket = Math.floor(Date.now() / 1000);
const key = `acme_rate_limit:${bucket}`;
let allowed: number;
try {
allowed = (await redis.eval(
ACQUIRE_SCRIPT,
1,
key,
limit.toString()
)) as number;
} catch (error) {
logger.error(
"ACME rate limiter check failed, proceeding without throttling:",
error
);
return;
}
if (allowed === 1) {
return;
}
// Budget for this second is spent - wait for the next window.
const waitMs = 1000 - (Date.now() % 1000) + 10;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
}
export const acmeRateLimiter = new AcmeRateLimiter();
@@ -0,0 +1,511 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { acmeClientManager } from "./acme-client";
import { dnsValidator } from "./dns-validator";
import { getTableColumns } from "drizzle-orm";
import { eq, and, or, isNull, lt, asc } from "drizzle-orm/sql";
import { config } from "@server/lib/config";
import { db, certificates, domains, Certificate } from "@server/db";
import { encrypt } from "@server/lib/crypto";
import { withTimeout, withRetry } from "@server/lib/retry";
import logger from "@server/logger";
import { lockManager } from "../lock";
import { pushCertUpdateToAffectedNewts } from "@server/lib/acmeCertSync";
import crypto from "crypto";
// Number of on-demand DNS validation attempts made right before a
// certificate is (re)issued, to avoid burning Let's Encrypt rate limits on
// domains whose DNS has drifted since they were last verified.
const PRE_CERT_DNS_VALIDATION_ATTEMPTS = 3;
// Hard ceiling on a single certificate's issuance/renewal flow. acme-client's
// axios instance never sets a request timeout, so a stalled connection to
// the ACME server hangs forever instead of erroring - and since
// processPendingCertificates/processRenewalCandidates gate the *next* batch
// on Promise.all(...) over the current one, one hung certificate would
// otherwise stall every other domain permanently. Sized generously above the
// legitimate worst case (acme-client's own bounded backoff is ~3.6min per
// status-polling loop, and a wildcard cert's two identifiers plus order
// finalization can chain a few of those) so this only fires on a genuine hang.
const CERTIFICATE_ISSUANCE_TIMEOUT_MS = 20 * 60 * 1000;
// "requested" is set the instant a cert starts processing and is never
// queried anywhere else - processPendingCertificates only selects "pending"
// and processRenewalCandidates only selects "valid". So if the *process*
// dies mid-flight (OOM, node eviction, a rolling deploy) rather than just
// hanging, the row is orphaned in "requested" permanently with nothing to
// ever pick it back up, no matter how good the in-process timeouts are.
// Threshold is set comfortably above CERTIFICATE_ISSUANCE_TIMEOUT_MS plus the
// scheduler's own outer backstop so this never reclaims a cert that's still
// genuinely being worked on.
const STUCK_CERTIFICATE_THRESHOLD_MS = 40 * 60 * 1000;
export class CertificateService {
// Runs at the top of every processPendingCertificates tick so an
// interrupted worker's leftovers always get put back in the queue
// instead of sitting invisible to every query forever.
private async reclaimStuckCertificates(): Promise<void> {
const staleBefore =
Math.floor(Date.now() / 1000) -
Math.floor(STUCK_CERTIFICATE_THRESHOLD_MS / 1000);
const reclaimed = await db
.update(certificates)
.set({
status: "pending",
errorMessage:
'Reclaimed after being stuck in "requested" state - the worker processing it likely restarted or crashed',
updatedAt: Math.floor(Date.now() / 1000)
})
.where(
and(
eq(certificates.status, "requested"),
lt(certificates.updatedAt, staleBefore)
)
)
.returning({ domain: certificates.domain });
if (reclaimed.length > 0) {
logger.warn(
`Reclaimed ${reclaimed.length} certificate(s) stuck in "requested" state: ${reclaimed
.map((c) => c.domain)
.join(", ")}`
);
}
}
async processPendingCertificates(): Promise<void> {
logger.debug("Checking for pending certificates...");
await this.reclaimStuckCertificates();
const pendingCerts = await db
.select(getTableColumns(certificates))
.from(certificates)
.leftJoin(domains, eq(certificates.domainId, domains.domainId))
.where(
and(
eq(certificates.status, "pending"),
or(
// Certs with no linked domain row (e.g. legacy certs
// imported from acme.json) aren't gated on domain
// verification since there's nothing to check.
isNull(certificates.domainId),
and(
eq(domains.verified, true),
eq(domains.failed, false)
)
)
)
)
.limit(10);
if (pendingCerts.length === 0) {
logger.debug("No pending certificates found");
return;
}
logger.info(`Found ${pendingCerts.length} pending certificates`);
// Process the batch concurrently so one domain stuck retrying a slow
// DNS-01 challenge (the ACME client's waitForValidStatus can spend
// minutes on a bad domain) doesn't stall the rest of the batch.
// processSingleCertificate catches its own errors and each cert uses
// an independent per-domain lock, so this is safe to parallelize.
await Promise.all(
pendingCerts.map((cert) => this.processSingleCertificate(cert))
);
}
async processRenewalCandidates(): Promise<void> {
logger.debug("Checking for certificates needing renewal...");
const now = Math.floor(Date.now() / 1000);
const renewalCandidates = await db
.select(getTableColumns(certificates))
.from(certificates)
.leftJoin(domains, eq(certificates.domainId, domains.domainId))
.where(
and(
eq(certificates.status, "valid"),
lt(certificates.expiresAt, now + 15 * 24 * 60 * 60), // 15 days from now
or(
// Certs with no linked domain row (e.g. legacy certs
// imported from acme.json) aren't gated on domain
// verification since there's nothing to check.
isNull(certificates.domainId),
and(
eq(domains.verified, true),
eq(domains.failed, false)
)
)
)
)
// Most urgent first, so already-expired certs aren't starved
// behind the limit by certs that still have weeks of runway.
.orderBy(asc(certificates.expiresAt))
.limit(50);
if (renewalCandidates.length === 0) {
logger.debug("No certificates need renewal");
return;
}
logger.info(
`Found ${renewalCandidates.length} certificates needing renewal`
);
for (const cert of renewalCandidates) {
if (cert.expiresAt !== null && cert.expiresAt < now) {
logger.warn(
`Certificate for ${cert.domain} is marked "valid" but already expired at ${new Date(cert.expiresAt * 1000).toISOString()} (bad state) - renewing immediately`
);
}
}
// Process the batch concurrently - see processPendingCertificates for why.
await Promise.all(
renewalCandidates.map((cert) => this.renewCertificate(cert))
);
}
private async processSingleCertificate(cert: Certificate): Promise<void> {
const lockKey = `cert:${cert.domain}`;
const lockToken = await lockManager.acquireLock(lockKey);
if (!lockToken) {
logger.debug(
`Could not acquire lock for certificate: ${cert.domain}`
);
return;
}
try {
logger.info(`Processing certificate for domain: ${cert.domain}`);
// Update status to processing
await db
.update(certificates)
.set({
status: "requested",
updatedAt: Math.floor(Date.now() / 1000)
})
.where(eq(certificates.certId, cert.certId));
//
await withTimeout(
this.obtainCertificate(cert),
CERTIFICATE_ISSUANCE_TIMEOUT_MS,
`certificate issuance for ${cert.domain}`
);
} catch (error) {
logger.error(
`Failed to process certificate for ${cert.domain}:`,
error
);
await db
.update(certificates)
.set({
status: "failed",
errorMessage:
error instanceof Error
? error.message
: "Unknown error",
updatedAt: Math.floor(Date.now() / 1000)
})
.where(eq(certificates.certId, cert.certId));
} finally {
await lockManager.releaseLock(lockKey, lockToken);
}
}
private async renewCertificate(cert: Certificate): Promise<void> {
const lockKey = `cert:${cert.domain}`;
const lockToken = await lockManager.acquireLock(lockKey);
if (!lockToken) {
logger.debug(
`Could not acquire lock for certificate renewal: ${cert.domain}`
);
return;
}
try {
logger.info(`Renewing certificate for domain: ${cert.domain}`);
// Update last renewal attempt
await db
.update(certificates)
.set({
lastRenewalAttempt: Math.floor(Date.now() / 1000),
updatedAt: Math.floor(Date.now() / 1000)
})
.where(eq(certificates.certId, cert.certId));
await withTimeout(
this.obtainCertificate(cert),
CERTIFICATE_ISSUANCE_TIMEOUT_MS,
`certificate renewal for ${cert.domain}`
);
} catch (error) {
logger.error(
`Failed to renew certificate for ${cert.domain}:`,
error
);
await db
.update(certificates)
.set({
status: "failed",
errorMessage:
error instanceof Error
? error.message
: "Unknown error",
lastRenewalAttempt: Math.floor(Date.now() / 1000),
updatedAt: Math.floor(Date.now() / 1000)
})
.where(eq(certificates.certId, cert.certId));
} finally {
await lockManager.releaseLock(lockKey, lockToken);
}
}
// Re-checks the domain's DNS records right before we spend a Let's
// Encrypt order on it, so drift that happened after the domain was
// originally verified doesn't burn ACME rate limits. Certs with no
// linked domain row (e.g. legacy/manually-managed certs) skip this and
// proceed as before, since there are no tracked DNS records to check.
private async verifyDomainBeforeIssuance(cert: Certificate): Promise<void> {
if (!cert.domainId) {
return;
}
const [domain] = await db
.select()
.from(domains)
.where(eq(domains.domainId, cert.domainId))
.limit(1);
if (!domain) {
return;
}
for (
let attempt = 1;
attempt <= PRE_CERT_DNS_VALIDATION_ATTEMPTS;
attempt++
) {
// Offset `tries` so each attempt round-robins to a different
// privateConfigured DNS resolver instead of re-querying the same one.
const probe = { ...domain, tries: domain.tries + attempt - 1 };
if (
await dnsValidator.validateDomain(probe, {
forceRecheck: true
})
) {
await db
.update(domains)
.set({ verified: true, failed: false, errorMessage: null })
.where(eq(domains.domainId, domain.domainId));
return;
}
logger.warn(
`Pre-certificate DNS check ${attempt}/${PRE_CERT_DNS_VALIDATION_ATTEMPTS} failed for domain ${domain.baseDomain} (cert: ${cert.domain})`
);
}
const errorMessage = `Domain failed DNS validation ${PRE_CERT_DNS_VALIDATION_ATTEMPTS} times before certificate issuance`;
await db
.update(domains)
.set({ verified: false, failed: true, errorMessage })
.where(eq(domains.domainId, domain.domainId));
throw new Error(errorMessage);
}
private async obtainCertificate(cert: Certificate): Promise<void> {
await this.verifyDomainBeforeIssuance(cert);
// Create order
const order = await acmeClientManager.createOrder(
cert.domain,
cert.wildcard || false
);
// Update with order ID
await withRetry(
() =>
db
.update(certificates)
.set({
orderId: order.url,
updatedAt: Math.floor(Date.now() / 1000)
})
.where(eq(certificates.certId, cert.certId)),
{ label: `update orderId for certificate ${cert.domain}` }
);
// Get authorizations
const authorizations = await acmeClientManager.getAuthorizations(order);
// Aggregate all DNS-01 challenges
const dnsChallenges = authorizations.map((authz: any) => {
const dnsChallenge = authz.challenges.find(
(c: any) => c.type === "dns-01"
);
if (!dnsChallenge) {
throw new Error(
`No DNS-01 challenge found for ${authz.identifier.value}`
);
}
return {
authz,
challenge: dnsChallenge
};
});
// Send all DNS-01 challenges in one request to handleDnsChallenge
await acmeClientManager.handleDnsChallenge(dnsChallenges);
// Finalize certificate
const { certificate, privateKey } =
await acmeClientManager.finalizeCertificate(
order,
cert.domain,
cert.wildcard || false
);
const encryptionKey = config.getRawConfig().server.secret;
if (!encryptionKey) {
throw new Error("Encryption key not provided");
}
// Encrypt certificate and private key
const encryptedCert = encrypt(certificate, encryptionKey);
const encryptedKey = encrypt(privateKey, encryptionKey);
// Parse certificate to get expiration date
const expiresAt = this.extractExpirationDate(certificate);
// Update database record. This persists the certificate we just
// obtained from the ACME server, so it's retried aggressively -
// losing this write means re-issuing the cert from scratch.
await withRetry(
() =>
db
.update(certificates)
.set({
status: "valid",
expiresAt: Math.floor(expiresAt.getTime() / 1000),
renewalCount: (cert.renewalCount || 0) + 1,
errorMessage: null,
updatedAt: Math.floor(Date.now() / 1000),
certFile: encryptedCert,
keyFile: encryptedKey
})
.where(eq(certificates.certId, cert.certId)),
{
retries: 5,
label: `persist issued certificate for ${cert.domain}`
}
);
logger.info(
`Certificate successfully obtained/renewed for domain: ${cert.domain}`
);
await pushCertUpdateToAffectedNewts(
cert.domain,
cert.domainId ?? null,
certificate,
privateKey
);
}
private extractExpirationDate(certificate: string): Date {
try {
// Extract the certificate block
const pem = certificate
.replace(/-----BEGIN CERTIFICATE-----/g, "")
.replace(/-----END CERTIFICATE-----/g, "")
.replace(/\s+/g, "");
const der = Buffer.from(pem, "base64");
// Use Node.js crypto to parse the certificate
const x509 = new crypto.X509Certificate(der);
return new Date(x509.validTo);
} catch (error) {
logger.warn(
"Failed to parse certificate expiration date, using default",
error
);
// Default to 90 days from now (Let's Encrypt default)
return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);
}
}
async addCertificateRequest(domain: string): Promise<void> {
try {
await db.insert(certificates).values({
domain,
status: "pending",
createdAt: Math.floor(Date.now() / 1000),
updatedAt: Math.floor(Date.now() / 1000)
});
logger.info(`Certificate request added for domain: ${domain}`);
} catch (error) {
if (error instanceof Error && error.message.includes("unique")) {
logger.warn(
`Certificate request already exists for domain: ${domain}`
);
} else {
throw error;
}
}
}
async getCertificateStatus(domain: string) {
const cert = await db
.select()
.from(certificates)
.where(eq(certificates.domain, domain))
.limit(1);
return cert[0] || null;
}
async cleanupExpiredChallenges(): Promise<void> {
try {
const result = await db
.delete(certificates)
.where(
lt(certificates.expiresAt, Math.floor(Date.now() / 1000))
)
.returning();
if (result.length > 0) {
logger.info(
`Cleaned up ${result.length} expired DNS challenges`
);
}
} catch (error) {
logger.error("Failed to cleanup expired challenges:", error);
}
}
}
export const certificateService = new CertificateService();
@@ -0,0 +1,334 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { eq, and, lt } from "drizzle-orm";
import * as dns from "dns/promises";
import { privateConfig as config } from "#private/lib/config";
import { db, domains, DnsRecord, dnsRecords, Domain } from "@server/db";
import logger from "@server/logger";
import { lockManager } from "../lock";
export const DNS_VALIDATOR_MAX_TRIES = 300;
export class DNSValidator {
private static readonly MAX_TRIES = DNS_VALIDATOR_MAX_TRIES;
constructor() {}
async validateAll(): Promise<void> {
// Get all domains that are not yet verified and haven't exceeded max tries
const unverifiedDomains: Domain[] = await db
.select()
.from(domains)
.where(
and(
eq(domains.verified, false),
lt(domains.tries, DNSValidator.MAX_TRIES)
)
);
if (unverifiedDomains.length === 0) {
logger.debug("No unverified domains found for DNS validation");
return;
}
logger.info(`Validating ${unverifiedDomains.length} DNS records`);
for (const domain of unverifiedDomains) {
const lockKey = `dns:${domain.baseDomain}`;
const lockToken = await lockManager.acquireLock(lockKey);
if (!lockToken) {
logger.debug(
`Could not acquire lock for DNS validation: ${domain.baseDomain}`
);
continue;
}
try {
const isValid = await this.validateDomain(domain);
if (isValid) {
await db
.update(domains)
.set({
verified: true,
failed: false,
tries: 0,
errorMessage: null
})
.where(eq(domains.domainId, domain.domainId));
logger.info(
`Domain ${domain.baseDomain} validated successfully`
);
} else {
const newTries = domain.tries + 1;
const shouldMarkAsFailed =
newTries >= DNSValidator.MAX_TRIES;
await db
.update(domains)
.set({
tries: newTries,
failed: shouldMarkAsFailed
})
.where(eq(domains.domainId, domain.domainId));
if (shouldMarkAsFailed) {
logger.warn(
`Domain ${domain.baseDomain} exceeded maximum tries (${DNSValidator.MAX_TRIES}), marking as failed`
);
} else {
logger.debug(
`Domain ${domain.baseDomain} did not validate (attempt ${newTries}/${DNSValidator.MAX_TRIES})`
);
}
}
} catch (err) {
logger.warn(
`Error validating domain ${domain.baseDomain}:`,
err
);
// Increment tries even on error
const newTries = domain.tries + 1;
const shouldMarkAsFailed = newTries >= DNSValidator.MAX_TRIES;
await db
.update(domains)
.set({
tries: newTries,
failed: shouldMarkAsFailed
})
.where(eq(domains.domainId, domain.domainId));
} finally {
await lockManager.releaseLock(lockKey, lockToken);
}
}
}
async validateDomain(
domain: Domain,
opts: { forceRecheck?: boolean } = {}
): Promise<boolean> {
const { forceRecheck = false } = opts;
const resolver = new dns.Resolver();
const servers = config.getRawConfig().acme?.dns_resolvers;
if (!servers || servers.length === 0) {
throw new Error("No DNS resolvers configured");
}
const dnsServer = servers[domain.tries % servers.length]!;
resolver.setServers([dnsServer]);
logger.debug(
`Using DNS server ${dnsServer} for domain ${domain.baseDomain} (try ${domain.tries})`
);
// Get all DNS records for this domain
const records: DnsRecord[] = await db
.select()
.from(dnsRecords)
.where(eq(dnsRecords.domainId, domain.domainId));
if (records.length === 0) {
logger.warn(`No DNS records found for domain ${domain.baseDomain}`);
return false;
}
if (!forceRecheck && records.every((r) => r.verified)) {
logger.info(
`All DNS records already verified for domain ${domain.baseDomain}`
);
return true;
}
logger.info(
`Validating ${records.length} DNS records for domain ${domain.baseDomain}`
);
// Collect the full set of expected NS values for this domain so we can
// detect extra records that are present in DNS but not in our DB.
const expectedNsValues = new Set<string>(
records.filter((r) => r.recordType === "NS").map((r) => r.value)
);
// Cache resolved NS records across iterations — there will be 3 NS
// records in the DB and we don't need to hit the upstream server 3 times.
let previousNs: string[] | null = null;
for (const record of records) {
// Skip already verified records, unless a live recheck was requested
if (record.verified && !forceRecheck) {
continue;
}
let isValid = false;
try {
if (record.recordType === "NS") {
let nsRecords: string[] | null = previousNs;
if (!nsRecords) {
nsRecords = await resolver.resolveNs(
record.baseDomain || domain.baseDomain
);
}
logger.info(
`NS records for ${
record.baseDomain || domain.baseDomain
}:`,
nsRecords
);
// Check if this expected NS value is present in the live records.
// A stale/legacy expected value (e.g. left over from a
// nameserver rebrand) is also accepted as long as the live
// records resolve to some other known-valid nameserver —
// the specific literal hostname stored per-domain isn't
// meaningful once it's a recognized alias.
isValid = nsRecords.some((ns) => ns === record.value);
previousNs = nsRecords;
} else if (record.recordType === "CNAME") {
const cnameRecords = await resolver.resolveCname(
record.baseDomain || domain.baseDomain
);
logger.info(
`CNAME records for ${
record.baseDomain || domain.baseDomain
}:`,
cnameRecords
);
// Check if the CNAME record matches the expected value
isValid =
cnameRecords.length === 1 &&
cnameRecords[0] === record.value;
} else if (record.recordType === "TXT") {
const txtRecords = await resolver.resolveTxt(
record.baseDomain || domain.baseDomain
);
logger.info(
`TXT records for ${
record.baseDomain || domain.baseDomain
}:`,
txtRecords
);
// TXT records come as an array of arrays, flatten and check
const flatTxtRecords = txtRecords.flat();
isValid = flatTxtRecords.includes(record.value);
} else if (record.recordType === "A") {
const aRecords = await resolver.resolve4(
record.baseDomain || domain.baseDomain
);
logger.info(
`A records for ${
record.baseDomain || domain.baseDomain
}:`,
aRecords
);
// Check if the A record matches the expected value
isValid = aRecords.includes(record.value);
} else {
logger.warn(
`Unsupported record type: ${record.recordType}`
);
continue;
}
} catch (error) {
isValid = false;
logger.debug(
`Did not resolve ${record.recordType} record for ${
record.baseDomain || domain.baseDomain
}:`,
error
);
}
// Update the individual record verification status. Runs for
// both a mismatched value and a failed/thrown DNS lookup, so a
// previously-verified record that stops resolving (e.g. NXDOMAIN
// after NS delegation is dropped) gets downgraded instead of
// leaving stale `verified: true` state behind.
if (isValid) {
await db
.update(dnsRecords)
.set({ verified: true })
.where(eq(dnsRecords.id, record.id));
logger.info(
`DNS record ${record.id} (${record.recordType}) for ${
record.baseDomain || domain.baseDomain
} verified successfully`
);
} else {
if (record.verified) {
await db
.update(dnsRecords)
.set({ verified: false })
.where(eq(dnsRecords.id, record.id));
}
logger.debug(
`DNS record ${record.id} (${record.recordType}) for ${
record.baseDomain || domain.baseDomain
} does not match expected value: ${record.value}`
);
}
}
// --- Extra NS record check ---
// If we resolved NS records during this pass, verify that the live DNS
// has no nameservers beyond the ones we expect. Individual records may
// already be marked verified above, but we must block full domain
// verification until the extra records are removed.
if (previousNs !== null && expectedNsValues.size > 0) {
const extraNsRecords = previousNs.filter(
(ns) => !expectedNsValues.has(ns)
);
if (extraNsRecords.length > 0) {
const errorMessage = `Extra NS records found that are not expected: ${extraNsRecords.join(", ")}. Remove these nameservers to complete domain verification.`;
await db
.update(domains)
.set({ errorMessage })
.where(eq(domains.domainId, domain.domainId));
logger.warn(
`Domain ${domain.baseDomain} has extra NS records that prevent verification: ${extraNsRecords.join(", ")}`
);
return false;
}
// No extras — clear any stale error that was previously written
await db
.update(domains)
.set({ errorMessage: null })
.where(eq(domains.domainId, domain.domainId));
}
// Check if all records are now verified
const updatedRecords: DnsRecord[] = await db
.select()
.from(dnsRecords)
.where(eq(dnsRecords.domainId, domain.domainId));
const allRecordsVerified = updatedRecords.every((r) => r.verified);
logger.info(
`Domain ${domain.baseDomain}: ${
updatedRecords.filter((r) => r.verified).length
}/${updatedRecords.length} records verified`
);
return allRecordsVerified;
}
}
export const dnsValidator = new DNSValidator();
@@ -0,0 +1,233 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { eq, and, or, isNull, lt } from "drizzle-orm";
import * as dns from "dns/promises";
import { DNS_VALIDATOR_MAX_TRIES } from "./dns-validator";
import { db, domains, DnsRecord, dnsRecords, Domain } from "@server/db";
import logger from "@server/logger";
import { lockManager } from "../lock";
import { privateConfig as config } from "#private/lib/config";
// Module-level counter so successive domains in a batch round-robin across servers.
let serverIndex = 0;
export class DomainReverifier {
async reverifyAll(): Promise<void> {
const certConfig = config.getRawConfig().acme;
if (!certConfig) {
logger.debug(
"No certificate config — skipping domain reverification"
);
return;
}
const windowMs = certConfig.domain_reverification_window_ms;
const batchSize = certConfig.domain_reverification_batch_size;
const windowSecs = Math.floor(windowMs / 1000);
const cutoff = Math.floor(Date.now() / 1000) - windowSecs;
const domainsToCheck: Domain[] = await db
.select()
.from(domains)
.where(
and(
eq(domains.verified, true),
or(
isNull(domains.lastCheckedAt),
lt(domains.lastCheckedAt, cutoff)
)
)
)
.limit(batchSize);
if (domainsToCheck.length === 0) {
logger.debug("No verified domains due for reverification");
return;
}
logger.info(`Reverifying ${domainsToCheck.length} domains`);
for (const domain of domainsToCheck) {
const lockKey = `dns-reverify:${domain.baseDomain}`;
const lockToken = await lockManager.acquireLock(lockKey);
if (!lockToken) {
logger.debug(
`Could not acquire lock for domain reverification: ${domain.baseDomain}`
);
continue;
}
try {
await this.reverifyDomain(domain, certConfig.dns_resolvers);
} catch (err) {
logger.warn(
`Unexpected error reverifying domain ${domain.baseDomain}:`,
err
);
// Still stamp lastCheckedAt so we don't hammer a broken domain every run.
await db
.update(domains)
.set({ lastCheckedAt: Math.floor(Date.now() / 1000) })
.where(eq(domains.domainId, domain.domainId));
} finally {
await lockManager.releaseLock(lockKey, lockToken);
}
}
}
private async reverifyDomain(
domain: Domain,
servers: string[]
): Promise<void> {
if (!servers || servers.length === 0) {
throw new Error("No DNS resolvers configured");
}
// Round-robin across servers; advance the global counter so the next
// domain in the same batch gets a different server.
const dnsServer = servers[serverIndex % servers.length]!;
serverIndex++;
const resolver = new dns.Resolver();
resolver.setServers([dnsServer]);
logger.debug(
`Reverifying domain ${domain.baseDomain} using DNS server ${dnsServer}`
);
const records: DnsRecord[] = await db
.select()
.from(dnsRecords)
.where(eq(dnsRecords.domainId, domain.domainId));
if (records.length === 0) {
logger.warn(
`No DNS records found for domain ${domain.baseDomain} during reverification — marking failed`
);
await this.markFailed(
domain.domainId,
"No DNS records found during periodic reverification"
);
return;
}
const expectedNsValues = new Set<string>(
records.filter((r) => r.recordType === "NS").map((r) => r.value)
);
let allValid = true;
let errorMessage: string | null = null;
let resolvedNs: string[] | null = null;
for (const record of records) {
let isValid = false;
try {
if (record.recordType === "NS") {
if (!resolvedNs) {
resolvedNs = await resolver.resolveNs(
record.baseDomain || domain.baseDomain
);
}
isValid = resolvedNs.some((ns) => ns === record.value);
} else if (record.recordType === "CNAME") {
const cnameRecords = await resolver.resolveCname(
record.baseDomain || domain.baseDomain
);
isValid =
cnameRecords.length === 1 &&
cnameRecords[0] === record.value;
} else if (record.recordType === "TXT") {
const txtRecords = await resolver.resolveTxt(
record.baseDomain || domain.baseDomain
);
isValid = txtRecords.flat().includes(record.value);
} else if (record.recordType === "A") {
const aRecords = await resolver.resolve4(
record.baseDomain || domain.baseDomain
);
isValid = aRecords.includes(record.value);
} else {
logger.warn(
`Unsupported record type ${record.recordType} during reverification of ${domain.baseDomain}`
);
continue;
}
} catch (err) {
logger.debug(
`DNS lookup failed for ${record.recordType} record on ${record.baseDomain || domain.baseDomain}:`,
err
);
isValid = false;
}
if (!isValid) {
allValid = false;
errorMessage = `${record.recordType} record for ${record.baseDomain || domain.baseDomain} no longer resolves to expected value "${record.value}"`;
break;
}
}
// Check for extra NS records beyond what we expect.
if (allValid && resolvedNs !== null && expectedNsValues.size > 0) {
const extraNs = resolvedNs.filter(
(ns) => !expectedNsValues.has(ns)
);
if (extraNs.length > 0) {
allValid = false;
errorMessage = `Extra NS records found: ${extraNs.join(", ")}. Remove these nameservers.`;
}
}
const now = Math.floor(Date.now() / 1000);
if (allValid) {
await db
.update(domains)
.set({ lastCheckedAt: now, errorMessage: null })
.where(eq(domains.domainId, domain.domainId));
logger.debug(
`Domain ${domain.baseDomain} passed periodic reverification`
);
} else {
await this.markFailed(domain.domainId, errorMessage);
logger.warn(
`Domain ${domain.baseDomain} failed periodic reverification: ${errorMessage}`
);
}
}
private async markFailed(
domainId: string,
errorMessage: string | null
): Promise<void> {
await db
.update(domains)
.set({
verified: false,
failed: true,
// Three below MAX_TRIES: keeps the domain out of the DNS
// validator's immediate retry loop, while still leaving it
// eligible (tries < MAX_TRIES) for a few more validation
// passes instead of being excluded forever once tries hits
// MAX_TRIES.
tries: DNS_VALIDATOR_MAX_TRIES - 3,
lastCheckedAt: Math.floor(Date.now() / 1000),
errorMessage
})
.where(eq(domains.domainId, domainId));
}
}
export const domainReverifier = new DomainReverifier();
+45
View File
@@ -0,0 +1,45 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import logger from "@server/logger";
import { privateConfig } from "#private/lib/config";
import { acmeClientManager } from "./acme-client";
import { jobScheduler } from "./scheduler";
export async function startCertificateManager() {
const acmeConfig = privateConfig.getRawPrivateConfig().acme;
if (
acmeConfig &&
acmeConfig.cert_mode === "pangolin" &&
acmeConfig.enable_acme_client
) {
logger.info("Starting certificate management server...");
// Initialize ACME client
await acmeClientManager.initialize();
// Start certificate issuance/renewal jobs
await jobScheduler.start();
}
if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
// DNS record validation/reverification doesn't require certs, so it
// runs whenever Pangolin is acting as the authoritative DNS server,
// independent of the cert manager above.
await jobScheduler.startDnsJobs();
}
}
export async function stopCertificateManager() {
await jobScheduler.stop();
}
@@ -0,0 +1,197 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { withTimeout } from "@server/lib/retry";
import logger from "@server/logger";
import { certificateService } from "./certificate-service";
import { privateConfig as config } from "#private/lib/config";
import { dnsValidator } from "./dns-validator";
import { domainReverifier } from "./domain-reverifier";
import license from "#private/license/license";
// Backstop for runExclusive: no single job's own internal timeouts (e.g.
// certificate-service's per-cert issuance timeout) are relied on here. This
// is the last line of defense - if *anything* inside a job hangs with no
// error (a stalled Redis/DB call, a future code path that forgets to bound
// itself, etc.), state.active must still reset so the next tick can run.
// Without it, one hung run permanently skips every future tick for that job,
// since runExclusive only clears state.active after the job promise settles.
const RUN_EXCLUSIVE_TIMEOUT_MS = 30 * 60 * 1000;
export class JobScheduler {
private certIntervals: NodeJS.Timeout[] = [];
private dnsIntervals: NodeJS.Timeout[] = [];
private certRunning = false;
private dnsRunning = false;
// Guards against a slow batch (e.g. 10 certs whose DNS challenges take a
// while) still being processed when the next interval tick fires -
// without this, overlapping ticks would each pull their own batch of up
// to 10 pending/renewal certs and process them concurrently instead of
// waiting for the prior batch to finish.
private runExclusive(
job: () => Promise<void>,
state: { active: boolean },
label: string
): () => Promise<void> {
return async () => {
if (!(await license.isUnlocked())) {
logger.debug(
`Skipping ${label} tick - license is not subscribed`
);
return;
}
if (state.active) {
logger.debug(
`Skipping ${label} tick - previous run still in progress`
);
return;
}
state.active = true;
try {
await withTimeout(job(), RUN_EXCLUSIVE_TIMEOUT_MS, label);
} catch (error) {
logger.error(`Error in ${label}:`, error);
} finally {
state.active = false;
}
};
}
// Certificate issuance/renewal - requires an ACME client, so this is
// only started when Pangolin is actually managing certs.
async start(): Promise<void> {
if (this.certRunning) {
logger.warn("Certificate job scheduler is already running");
return;
}
this.certRunning = true;
logger.info("Starting certificate job scheduler");
const newCertState = { active: false };
const renewalState = { active: false };
const runNewCertCheck = this.runExclusive(
() => certificateService.processPendingCertificates(),
newCertState,
"processing pending certificates"
);
const runRenewalCheck = this.runExclusive(
() => certificateService.processRenewalCandidates(),
renewalState,
"processing renewal candidates"
);
// Schedule new certificate processing
const newCertInterval = setInterval(
runNewCertCheck,
config.getRawConfig().acme!.new_cert_check_interval_ms
);
// Schedule renewal processing (every 24 hours)
const renewalInterval = setInterval(
runRenewalCheck,
config.getRawConfig().acme!.renewal_check_interval_ms
);
this.certIntervals.push(newCertInterval, renewalInterval);
// Run initial checks
setTimeout(async () => {
try {
await runNewCertCheck();
// await runRenewalCheck();
} catch (error) {
logger.error("Error in initial certificate processing:", error);
}
}, 1000); // Wait 1 second after startup
logger.info("Certificate job scheduler started successfully");
}
// DNS record validation/reverification - doesn't touch certs at all, so
// this runs independently whenever Pangolin is acting as the
// authoritative DNS server, regardless of cert_mode.
async startDnsJobs(): Promise<void> {
if (this.dnsRunning) {
logger.warn("DNS validation job scheduler is already running");
return;
}
this.dnsRunning = true;
logger.info("Starting DNS validation job scheduler");
const dnsValidationState = { active: false };
const reverifyState = { active: false };
const runDnsValidation = this.runExclusive(
() => dnsValidator.validateAll(),
dnsValidationState,
"validating DNS records"
);
const runReverify = this.runExclusive(
() => domainReverifier.reverifyAll(),
reverifyState,
"reverifying domains"
);
// Schedule DNS validation
const dnsValidationInterval = setInterval(
runDnsValidation,
config.getRawConfig().acme?.dns_check_interval_ms ?? 60000
);
// Schedule periodic reverification of already-verified domains
const reverifyInterval = setInterval(
runReverify,
config.getRawConfig().acme?.domain_reverification_interval_ms ??
3600000
);
this.dnsIntervals.push(dnsValidationInterval, reverifyInterval);
// Run an initial validation pass shortly after startup
setTimeout(async () => {
try {
await runDnsValidation();
} catch (error) {
logger.error("Error in initial DNS validation:", error);
}
}, 1000);
logger.info("DNS validation job scheduler started successfully");
}
async stop(): Promise<void> {
if (this.certRunning) {
logger.info("Stopping certificate job scheduler");
this.certRunning = false;
this.certIntervals.forEach((interval) => clearInterval(interval));
this.certIntervals = [];
}
if (this.dnsRunning) {
logger.info("Stopping DNS validation job scheduler");
this.dnsRunning = false;
this.dnsIntervals.forEach((interval) => clearInterval(interval));
this.dnsIntervals = [];
}
}
isRunning(): boolean {
return this.certRunning || this.dnsRunning;
}
}
export const jobScheduler = new JobScheduler();
+8
View File
@@ -146,12 +146,20 @@ export class PrivateConfig {
process.env.USE_PANGOLIN_DNS =
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
}
if (this.rawPrivateConfig.acme?.cert_mode) {
process.env.CERT_MODE = this.rawPrivateConfig.acme.cert_mode;
}
}
public getRawPrivateConfig() {
return this.rawPrivateConfig;
}
public getRawConfig() {
return this.getRawPrivateConfig();
}
// `flags.enable_acme_cert_sync`, `flags.disable_private_http_placeholder`,
// and `acme` used to live in the private config file. They now live in
// the public config file. If an operator still has them set in the
+47
View File
@@ -0,0 +1,47 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { build } from "@server/build";
import privateConfig from "#private/lib/config";
export function createCname(domainId: string, baseDomain: string) {
if (!privateConfig.getRawPrivateConfig().dns?.cname_extension) {
throw new Error("CNAME extension not configured");
}
let cnameRecords = [
{
value: `${domainId}.${privateConfig.getRawPrivateConfig().dns?.cname_extension}`,
baseDomain: baseDomain
},
{
value: `_acme-challenge.${domainId}.${privateConfig.getRawPrivateConfig().dns?.cname_extension}`,
baseDomain: `_acme-challenge.${baseDomain}`
}
];
return cnameRecords;
}
export function createNs() {
if (!privateConfig.getRawPrivateConfig().dns?.nameserver_name) {
throw new Error("Nameservers not configured");
}
const nsRecords = [
privateConfig.getRawPrivateConfig().dns?.nameserver_name,
...(privateConfig.getRawPrivateConfig().dns?.alternate_nameservers ||
[])
] as string[];
return nsRecords;
}
+14
View File
@@ -0,0 +1,14 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./server";
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -18,6 +18,7 @@ import { eq } from "drizzle-orm";
import { sendToClient } from "#private/routers/ws";
import privateConfig from "#private/lib/config";
import config from "@server/lib/config";
import { hasExitNodeCheckedIn } from "@server/lib/exitNodes";
interface ExitNodeRequest {
remoteType?: string;
@@ -138,13 +139,19 @@ export async function sendToExitNode(
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error(
`Error making ${method} request (can Pangolin see Gerbil HTTP API?) for exit node at ${hostname} (status: ${error.response?.status}): ${error.message}`
);
const message = axios.isAxiosError(error)
? `Error making ${method} request (can Pangolin see Gerbil HTTP API?) for exit node at ${hostname} (status: ${error.response?.status}): ${error.message}`
: `Error making ${method} request for exit node at ${hostname}: ${error}`;
// The exit node (gerbil) may still be starting up and not yet
// reachable. Until it has checked in at least once, log this at a
// lower level since it's expected; once it has checked in, a
// connection failure is a real problem.
if (hasExitNodeCheckedIn(exitNode.exitNodeId)) {
logger.error(message);
} else {
logger.error(
`Error making ${method} request for exit node at ${hostname}: ${error}`
logger.warn(
`${message} (exit node has not checked in yet since startup, this is expected briefly)`
);
}
}
+141 -6
View File
@@ -12,7 +12,7 @@
*/
import fs from "fs";
import yaml from "js-yaml";
import * as yaml from "js-yaml";
import { privateConfigFilePath1 } from "@server/lib/consts";
import { z } from "zod";
import { colorsSchema } from "@server/lib/colorsSchema";
@@ -95,6 +95,70 @@ export const privateConfigSchema = z
.optional()
})
.optional(),
dns: z
.object({
enabled: z.boolean().optional().default(false),
listen_port: z.number().int().positive().optional().default(53),
nameserver_name: z.string(),
cname_extension: z.string(),
site_extension: z.string().optional(),
cname_alternate_extensions: z
.array(z.string())
.optional()
.default([]),
alternate_nameservers: z
.array(z.string())
.optional()
.default([]),
rate_limit: z
.object({
enabled: z.boolean().optional().default(true),
window_ms: z
.number()
.int()
.min(1000)
.max(600000)
.optional()
.default(60000),
max_requests: z
.number()
.int()
.min(50)
.max(100000)
.optional()
.default(1200),
max_requests_per_query_type: z
.number()
.int()
.min(10)
.max(50000)
.optional()
.default(600)
})
.default({
enabled: true,
window_ms: 60000,
max_requests: 1200,
max_requests_per_query_type: 600
}),
static_records: z
.array(
z.object({
domain: z.string(),
type: z.enum(["TXT", "CNAME", "A", "NS"]),
value: z.string(),
ttl: z
.number()
.int()
.positive()
.optional()
.default(300)
})
)
.optional()
.default([])
})
.optional(),
gerbil: z
.object({
local_exit_node_reachable_at: z
@@ -125,15 +189,86 @@ export const privateConfigSchema = z
})
.optional()
.prefault({}),
// @deprecated Moved to the public config file as `acme`
// (server/lib/readConfigFile.ts). Kept here only so existing private
// config files keep parsing; any value set here is migrated into the
// public config at startup by PrivateConfig (server/private/lib/config.ts).
acme: z
.object({
cert_mode: z
.enum(["traefik", "pangolin"])
.optional()
.default("traefik"),
enable_acme_client: z.boolean().optional().default(false),
// @deprecated Moved to the public config file
// (server/lib/readConfigFile.ts). Kept here only so existing private
// config files keep parsing; any value set here is migrated into the
// public config at startup by PrivateConfig (server/private/lib/config.ts).
acme_json_path: z.string().optional(),
// @deprecated Moved to the public config file
// (server/lib/readConfigFile.ts). Kept here only so existing private
// config files keep parsing; any value set here is migrated into the
// public config at startup by PrivateConfig (server/private/lib/config.ts).
acme_http_endpoint: z.string().optional(),
sync_interval_ms: z.number().optional()
// @deprecated Moved to the public config file
// (server/lib/readConfigFile.ts). Kept here only so existing private
// config files keep parsing; any value set here is migrated into the
// public config at startup by PrivateConfig (server/private/lib/config.ts).
sync_interval_ms: z.number().optional(),
acme_directory_url: z
.string()
.url()
.default("https://acme-v02.api.letsencrypt.org/directory"),
contact_email: z.string().email().optional(),
acme_account_key_path: z
.string()
.default("./config/account.key"),
challenge_ttl_ms: z.number().int().positive().default(300000),
renewal_check_interval_ms: z
.number()
.int()
.positive()
.default(3600000),
new_cert_check_interval_ms: z
.number()
.int()
.positive()
.default(5000),
// Kept safely under Let's Encrypt's ~20 req/s limit since this
// budget is shared across all pops workers and only covers the
// request-issuing calls we make directly (not every request
// acme-client makes internally, e.g. while polling for
// challenge/order status).
acme_requests_per_second: z
.number()
.int()
.positive()
.default(15),
dns_check_interval_ms: z
.number()
.int()
.positive()
.default(60000),
domain_reverification_interval_ms: z
.number()
.int()
.positive()
.default(3600000), // 1 hour — how often to run the reverification pass
domain_reverification_window_ms: z
.number()
.int()
.positive()
.default(259200000), // 72 hours — how old checkedAt must be before rechecking
domain_reverification_batch_size: z
.number()
.int()
.positive()
.default(20), // max domains to recheck per pass
dns_resolvers: z
.array(z.string())
.optional()
.default([
"8.8.8.8",
"1.1.1.1",
"9.9.9.9",
"208.67.222.222"
])
})
.optional(),
branding: z
+8 -6
View File
@@ -158,7 +158,7 @@ class RedisManager {
this.writeClient = new Redis({
...masterConfig,
enableReadyCheck: false,
maxRetriesPerRequest: 3,
maxRetriesPerRequest: 50,
keepAlive: 30000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
@@ -169,7 +169,7 @@ class RedisManager {
this.readClient = new Redis({
...replicaConfig!,
enableReadyCheck: false,
maxRetriesPerRequest: 3,
maxRetriesPerRequest: 50,
keepAlive: 30000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
@@ -186,7 +186,7 @@ class RedisManager {
this.publisher = new Redis({
...masterConfig,
enableReadyCheck: false,
maxRetriesPerRequest: 3,
maxRetriesPerRequest: 50,
keepAlive: 30000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
@@ -196,7 +196,7 @@ class RedisManager {
this.subscriber = new Redis({
...(this.hasReplicas ? replicaConfig! : masterConfig),
enableReadyCheck: false,
maxRetriesPerRequest: 3,
maxRetriesPerRequest: 50,
keepAlive: 30000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
@@ -901,7 +901,9 @@ class RegionalRedisManager {
// if the configured host doesn't match that pattern (e.g. local dev),
// in which case callers should fall back to the primary for reads.
private getReplicaHost(primaryHost: string): string | null {
const match = primaryHost.match(/^redis\.([^.]+)\.svc\.cluster\.local$/);
const match = primaryHost.match(
/^redis\.([^.]+)\.svc\.cluster\.local$/
);
if (!match) return null;
const namespace = match[1];
return `redis-1.redis-headless.${namespace}.svc.cluster.local`;
@@ -912,7 +914,7 @@ class RegionalRedisManager {
const baseOpts = {
...cfg,
enableReadyCheck: false,
maxRetriesPerRequest: 3,
maxRetriesPerRequest: 50,
keepAlive: 10000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
+18 -8
View File
@@ -396,7 +396,7 @@ export async function getTraefikConfig(
);
let validCerts: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
// create a list of all domains to get certs for
const domains = new Set<string>();
for (const resource of resourcesMap.values()) {
@@ -522,7 +522,10 @@ export async function getTraefikConfig(
);
let tls = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
tls = buildWildcardTls({
fullDomain,
hasSubdomain: !!resource.subdomain,
@@ -789,7 +792,8 @@ export async function getTraefikConfig(
preferWildcardCert
}) => {
if (
!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
return buildWildcardTls({
fullDomain,
@@ -832,7 +836,8 @@ export async function getTraefikConfig(
redirectHttpsMiddlewareName,
resolveTls: (fullDomain) => {
if (
!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global
@@ -924,7 +929,10 @@ export async function getTraefikConfig(
const rule = buildHostRule(fullDomain, ir.wildcard);
let tls: any = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
tls = buildWildcardTls({
fullDomain,
hasSubdomain: !!ir.subdomain,
@@ -1005,7 +1013,8 @@ export async function getTraefikConfig(
let tls: any = {};
if (
!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global
@@ -1080,7 +1089,7 @@ export async function getTraefikConfig(
.where(eq(exitNodes.exitNodeId, exitNodeId));
let validCertsLoginPages: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
// create a list of all domains to get certs for
const domains = new Set<string>();
for (const lp of exitNodeLoginPages) {
@@ -1126,7 +1135,8 @@ export async function getTraefikConfig(
const tls = {};
if (
!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
// TODO: we need to add the wildcard logic here too
} else {

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