Compare commits

...

365 Commits

Author SHA1 Message Date
Owen
de8262d7b9 Batch deletes 2025-11-15 11:51:52 -05:00
Owen
8ad7bcc0d6 Adjust rate limiting position 2025-11-14 11:33:52 -05:00
Owen
e62806d6fb Clean up old timestamps 2025-11-14 11:33:51 -05:00
Owen Schwartz
aabe39137b Merge pull request #1856 from LaurenceJJones/fix-remove-return-before-showing-token
fix: Remove return in installer which prevents showing token
2025-11-14 10:23:21 -05:00
miloschwartz
ca66637270 remove from address in saas suppport email 2025-11-13 17:37:27 -05:00
Laurence Jones
fbce392137 Remove unnecessary return after success message
Remove redundant return statement after success message.
2025-11-13 12:52:21 +00:00
Owen
d38b321f85 Add missing header 2025-11-08 16:47:03 -08:00
Owen Schwartz
b0ff50a76f Merge pull request #1834 from fosrl/dev
Small Bug Fixes
2025-11-08 16:35:50 -08:00
Owen
37acdc2796 Revert transaction 2025-11-08 16:33:48 -08:00
Owen Schwartz
f3d31cb6de Merge pull request #1833 from fosrl/crowdin_dev
New Crowdin updates
2025-11-08 16:23:11 -08:00
Owen Schwartz
a336955066 New translations en-us.json (Norwegian Bokmal) 2025-11-08 16:22:42 -08:00
Owen Schwartz
a229fc1c61 New translations en-us.json (Chinese Simplified) 2025-11-08 16:22:40 -08:00
Owen Schwartz
7995fd364e New translations en-us.json (Turkish) 2025-11-08 16:22:39 -08:00
Owen Schwartz
5e0d822d45 New translations en-us.json (Russian) 2025-11-08 16:22:38 -08:00
Owen Schwartz
4fddaa8f11 New translations en-us.json (Portuguese) 2025-11-08 16:22:36 -08:00
Owen Schwartz
4a87cecf89 New translations en-us.json (Polish) 2025-11-08 16:22:35 -08:00
Owen Schwartz
ac5ee5c7ca New translations en-us.json (Dutch) 2025-11-08 16:22:34 -08:00
Owen Schwartz
8a8c357563 New translations en-us.json (Korean) 2025-11-08 16:22:32 -08:00
Owen Schwartz
263fd80c18 New translations en-us.json (Italian) 2025-11-08 16:22:31 -08:00
Owen Schwartz
7bdf05bdf5 New translations en-us.json (German) 2025-11-08 16:22:30 -08:00
Owen Schwartz
d00f12967d New translations en-us.json (Czech) 2025-11-08 16:22:28 -08:00
Owen Schwartz
d9991a18e2 New translations en-us.json (Bulgarian) 2025-11-08 16:22:27 -08:00
Owen Schwartz
a51c21cdd2 New translations en-us.json (Spanish) 2025-11-08 16:22:26 -08:00
Owen Schwartz
265cab5b64 New translations en-us.json (French) 2025-11-08 16:22:24 -08:00
Owen
da15e5e77b Remove software-properties-common
Fixes #1828
2025-11-08 16:13:42 -08:00
Owen
a717ca2675 Only uppercase the value if its a country
Fixes #1813
2025-11-08 15:42:46 -08:00
Owen
564b290244 Fix #1830 2025-11-08 14:24:28 -08:00
Owen
84d78df67e Merge branch 'main' into dev 2025-11-08 14:20:40 -08:00
Owen
107053a98f Merge branch 'main' of github.com:fosrl/pangolin 2025-11-08 14:20:35 -08:00
Owen Schwartz
6422a78e6f Merge pull request #1830 from hetlelid/patch-2
Update resourceRawSettingsDescription with details
2025-11-08 14:20:21 -08:00
miloschwartz
5f11630e27 minor adjustments to blueprints screens 2025-11-08 14:15:47 -08:00
Owen
a776b2ea94 Fix: qiery perferWildcardCert from db
Fixes #1816
Fixes #1829
2025-11-08 14:14:17 -08:00
miloschwartz
b83ec1b503 remove target unique check 2025-11-08 13:57:00 -08:00
Owen
83bd5957cd Dont allow editing a config managed domain
Ref #1816
2025-11-08 12:18:36 -08:00
hetlelid
66c14c2d09 Update resourceRawSettingsDescription with details
Expanded the description for resourceRawSettings to include mapping details and a documentation link.
2025-11-08 13:24:51 +01:00
Owen Schwartz
51db267a4a Merge pull request #1779 from fosrl/dependabot/npm_and_yarn/eslint-config-next-16.0.1
Bump eslint-config-next from 15.5.6 to 16.0.1
2025-11-07 12:15:19 -08:00
dependabot[bot]
669817818a Bump eslint-config-next from 15.5.6 to 16.0.1
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.5.6 to 16.0.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v16.0.1/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 20:07:29 +00:00
Owen Schwartz
b84453bfbe Merge pull request #1825 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-282bba5f0a
Bump the dev-patch-updates group across 1 directory with 5 updates
2025-11-07 12:06:08 -08:00
Owen Schwartz
15d561f59f Merge pull request #1824 from robtec/patch-1
Fix typo in shareSeeOnce message
2025-11-07 12:05:59 -08:00
dependabot[bot]
749cea5a4d Bump the dev-patch-updates group across 1 directory with 5 updates
Bumps the dev-patch-updates group with 4 updates in the / directory: [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx), [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss), [esbuild](https://github.com/evanw/esbuild) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@dotenvx/dotenvx` from 1.51.0 to 1.51.1
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v1.51.0...v1.51.1)

Updates `@tailwindcss/postcss` from 4.1.16 to 4.1.17
- [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.1.17/packages/@tailwindcss-postcss)

Updates `esbuild` from 0.25.11 to 0.25.12
- [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.25.11...v0.25.12)

Updates `tailwindcss` from 4.1.16 to 4.1.17
- [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.1.17/packages/tailwindcss)

Updates `typescript-eslint` from 8.46.2 to 8.46.3
- [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.46.3/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.51.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: esbuild
  dependency-version: 0.25.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: typescript-eslint
  dependency-version: 8.46.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 01:23:55 +00:00
miloschwartz
2a7529c39e don't delete user 2025-11-06 16:48:53 -08:00
miloschwartz
fce887436d fix bug causing auto provision to override manually created users 2025-11-06 15:46:54 -08:00
Rob
3489107a49 Fix typo in shareSeeOnce message 2025-11-06 23:09:52 +00:00
Owen Schwartz
296b220bf3 Merge pull request #1819 from Pallavikumarimdb/fix/resourceTable-typeError
Fix/Revert column from Resource table to fix type error and match overall styling
2025-11-06 12:03:15 -08:00
Pallavi Kumari
0a9f37c44d revert column from resource table 2025-11-06 22:57:03 +05:30
Owen Schwartz
ef5d72663f Merge pull request #1328 from Pallavikumarimdb/enhancement-#906/dashboard-enhancements
Enhancement #906/Resources Dashboard: Targets Column, Customizable Columns & Status Indicators
2025-11-05 11:41:43 -08:00
Owen
6ddfc9b8fe Revert columns 2025-11-05 11:41:07 -08:00
Owen
301654b63e Fix styling 2025-11-05 11:38:14 -08:00
Owen Schwartz
3b12a77cf0 Merge pull request #1809 from clemone210/patch-2
Update German translations for client and blueprint terms
2025-11-04 10:34:26 -08:00
Timo
7cd31313d8 Update German translations for client and blueprint terms
"Kunden" is generally used for "Customers", so in this case I suggest to stick with Client, as this is a widely used term in german tech sector. The same for "Bauplan" or "Blaupause". "Bauplan" is a "Construction plan" for building houses. "Blaupause" is pretty much the right translation for blueprints, but I would stick with Blueprint here as well.
2025-11-04 07:40:33 +01:00
Milo Schwartz
9822deb4bf Update README.md 2025-11-03 22:56:57 -05:00
Owen
8942cb7aa7 Update const 2025-11-03 17:38:50 -08:00
Owen
f0f219f293 Merge branch 'main' into dev 2025-11-03 17:38:43 -08:00
Owen
6174599754 Allow >30 days on oss 2025-11-03 09:54:41 -08:00
Owen Schwartz
8ba04aeb74 Merge pull request #1802 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-700e856888
Bump the prod-minor-updates group across 1 directory with 9 updates
2025-11-03 09:49:01 -08:00
Owen
43590896e9 Add fosrl 2025-11-02 18:56:46 -08:00
Owen Schwartz
3547c4832b Revert "Refactor CI/CD workflow for improved release process" 2025-11-02 18:56:46 -08:00
Marc Schäfer
1cd098252e Refactor CI/CD workflow for improved release process
Updated CI/CD workflow to include new permissions, job definitions, and steps for version validation, tagging, and artifact management.
2025-11-02 18:56:46 -08:00
Owen
4adbc31dae Fix blueprints not applying
Fixes #1795
2025-11-02 18:56:46 -08:00
Owen
99031feb35 Fix camel case in health checks 2025-11-02 18:56:46 -08:00
Owen
d363b06d0e Fix rewritePath
Closes #1528
2025-11-02 18:56:46 -08:00
Owen
2af100cc86 Warning -> debug 2025-11-02 18:56:46 -08:00
dependabot[bot]
3e90211108 Bump the prod-minor-updates group across 1 directory with 9 updates
Bumps the prod-minor-updates group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.908.0` | `3.922.0` |
| [axios](https://github.com/axios/axios) | `1.12.2` | `1.13.1` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.39.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.1.0` | `8.2.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.545.0` | `0.552.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.3.12` | `4.4.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.10.4` | `5.11.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.65.0` | `7.66.0` |
| [resend](https://github.com/resend/resend-node) | `6.1.3` | `6.4.0` |



Updates `@aws-sdk/client-s3` from 3.908.0 to 3.922.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.922.0/clients/client-s3)

Updates `axios` from 1.12.2 to 1.13.1
- [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.12.2...v1.13.1)

Updates `eslint` from 9.37.0 to 9.39.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.39.0)

Updates `express-rate-limit` from 8.1.0 to 8.2.1
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.1.0...v8.2.1)

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

Updates `next-intl` from 4.3.12 to 4.4.0
- [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.3.12...v4.4.0)

Updates `posthog-node` from 5.10.4 to 5.11.0
- [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.11.0/packages/node)

Updates `react-hook-form` from 7.65.0 to 7.66.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.65.0...v7.66.0)

Updates `resend` from 6.1.3 to 6.4.0
- [Release notes](https://github.com/resend/resend-node/releases)
- [Commits](https://github.com/resend/resend-node/compare/v6.1.3...v6.4.0)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.922.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: axios
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: eslint
  dependency-version: 9.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: express-rate-limit
  dependency-version: 8.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: lucide-react
  dependency-version: 0.552.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.66.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: resend
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 01:34:13 +00:00
Owen
6dd161fe17 Add fosrl 2025-11-02 15:35:02 -08:00
Owen Schwartz
558bd040c6 Merge pull request #1801 from fosrl/revert-1792-main
Revert "Refactor CI/CD workflow for improved release process"
2025-11-02 15:22:12 -08:00
Owen Schwartz
f2c48975f6 Revert "Refactor CI/CD workflow for improved release process" 2025-11-02 15:22:03 -08:00
Owen Schwartz
fc43a56bb3 Merge pull request #1792 from marcschaeferger/main
Refactor CI/CD workflow for improved release process
2025-11-02 15:00:09 -08:00
Owen
ca7f557a3c Fix blueprints not applying
Fixes #1795
2025-11-02 14:56:19 -08:00
Owen
7477713eef Fix camel case in health checks 2025-11-02 14:17:38 -08:00
Owen
c16e762fa4 Fix rewritePath
Closes #1528
2025-11-02 14:05:41 -08:00
Owen Schwartz
41592133a6 Merge pull request #1788 from Pallavikumarimdb/fix/deleting-and-adding-back-a-target
Add transaction while deleting targets
2025-11-02 13:51:08 -08:00
Pallavi Kumari
54f7525f1b add status column in resource table 2025-11-02 13:55:17 +05:30
Pallavi Kumari
ad6bb3da9f fix type error 2025-11-02 13:55:17 +05:30
Pallavi Kumari
49bc2dc5da fix duplicate 2025-11-02 13:55:16 +05:30
Pallavi
cdf77087cd get niceid 2025-11-02 13:55:16 +05:30
Pallavi
8e5dde887c list targes in frontend 2025-11-02 13:55:16 +05:30
Pallavi
f21188000e remove status check and add column filtering on all of the tables 2025-11-02 13:55:16 +05:30
Pallavi
1b3eb32bf4 Show targets and status icons in the dashboard 2025-11-02 13:55:16 +05:30
Marc Schäfer
eec3f183e6 Refactor CI/CD workflow for improved release process
Updated CI/CD workflow to include new permissions, job definitions, and steps for version validation, tagging, and artifact management.
2025-11-02 00:44:03 +01:00
Owen
31b66cd911 Warning -> debug 2025-11-01 10:46:09 -07:00
Pallavi Kumari
ad425e8d9e add transaction while deleting targets 2025-11-01 11:58:09 +05:30
miloschwartz
da0196a308 no reset password for external users 2025-10-30 22:24:07 -07:00
miloschwartz
e585972b7b remove useSubscriptionStatusContext from HorizontalTabs 2025-10-30 21:31:48 -07:00
miloschwartz
cc62cd4add remove sqlite driver logger 2025-10-30 21:23:05 -07:00
Owen
25225a452c Return instead of throwing error 2025-10-30 21:18:26 -07:00
Owen
678644c7fb Fix empty blueprint 2025-10-30 21:09:20 -07:00
Owen
32f20ed984 Bugfixes for remote nodes 2025-10-30 21:01:45 -07:00
Owen
4eb5bf08d5 UI fixes 2025-10-30 17:44:22 -07:00
Owen
35c93f38e0 Fix small ui issues 2025-10-30 17:32:03 -07:00
Owen
f60c2f4fb9 Make refresh work 2025-10-30 17:25:49 -07:00
Owen
b2cf152b9e Add copy to clip 2025-10-30 16:17:20 -07:00
Owen
444928dffd Add wildcard 2025-10-30 15:27:24 -07:00
Owen
4d7e2d5840 Minor fixes to rc 2025-10-30 11:42:31 -07:00
Owen Schwartz
318046ce1d Merge pull request #1780 from fosrl/crowdin_dev
New Crowdin updates
2025-10-29 21:17:18 -07:00
Owen Schwartz
808ad1e272 New translations en-us.json (Norwegian Bokmal) 2025-10-29 21:16:51 -07:00
Owen Schwartz
05a1195661 New translations en-us.json (Chinese Simplified) 2025-10-29 21:16:49 -07:00
Owen Schwartz
c46322c6a6 New translations en-us.json (Turkish) 2025-10-29 21:16:48 -07:00
Owen Schwartz
80d5efc41f New translations en-us.json (Russian) 2025-10-29 21:16:47 -07:00
Owen Schwartz
0409ab7dc1 New translations en-us.json (Portuguese) 2025-10-29 21:16:46 -07:00
Owen Schwartz
63f079ec76 New translations en-us.json (Polish) 2025-10-29 21:16:45 -07:00
Owen Schwartz
5988f1e8da New translations en-us.json (Dutch) 2025-10-29 21:16:43 -07:00
Owen Schwartz
ed0c0edeba New translations en-us.json (Korean) 2025-10-29 21:16:42 -07:00
Owen Schwartz
34b4841f4d New translations en-us.json (Italian) 2025-10-29 21:16:40 -07:00
Owen Schwartz
ff47c5a8ad New translations en-us.json (German) 2025-10-29 21:16:39 -07:00
Owen Schwartz
9430a53c0c New translations en-us.json (Czech) 2025-10-29 21:16:38 -07:00
Owen Schwartz
03334e3f0f New translations en-us.json (Bulgarian) 2025-10-29 21:16:37 -07:00
Owen Schwartz
6f2ecf9d0d New translations en-us.json (Spanish) 2025-10-29 21:16:35 -07:00
Owen Schwartz
6f803c3b4b New translations en-us.json (French) 2025-10-29 21:16:34 -07:00
Owen
15d400c842 Fix migration and install faker 2025-10-29 21:12:12 -07:00
Owen Schwartz
3ddf150661 New translations en-us.json (Norwegian Bokmal) 2025-10-29 21:00:51 -07:00
Owen Schwartz
5b519afee4 New translations en-us.json (Chinese Simplified) 2025-10-29 21:00:50 -07:00
Owen Schwartz
15ea9f3dcc New translations en-us.json (Turkish) 2025-10-29 21:00:48 -07:00
Owen Schwartz
d5e2536f8d New translations en-us.json (Russian) 2025-10-29 21:00:47 -07:00
Owen Schwartz
d7e9083e06 New translations en-us.json (Portuguese) 2025-10-29 21:00:45 -07:00
Owen Schwartz
e0cc338c3a New translations en-us.json (Polish) 2025-10-29 21:00:44 -07:00
Owen Schwartz
624c5741e2 New translations en-us.json (Dutch) 2025-10-29 21:00:43 -07:00
Owen Schwartz
558507dd71 New translations en-us.json (Korean) 2025-10-29 21:00:41 -07:00
Owen Schwartz
565340bd53 New translations en-us.json (Italian) 2025-10-29 21:00:40 -07:00
Owen Schwartz
756745487a New translations en-us.json (German) 2025-10-29 21:00:39 -07:00
Owen Schwartz
d2ece4d370 New translations en-us.json (Czech) 2025-10-29 21:00:37 -07:00
Owen Schwartz
d5f5d1da1e New translations en-us.json (Bulgarian) 2025-10-29 21:00:35 -07:00
Owen Schwartz
dfaf1a72cc New translations en-us.json (Spanish) 2025-10-29 21:00:34 -07:00
Owen Schwartz
ff8e5b871c New translations en-us.json (French) 2025-10-29 21:00:33 -07:00
Owen
927dda4e53 Add blueprints to migrations 2025-10-29 20:50:36 -07:00
Owen
0e51bac307 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2025-10-29 20:46:13 -07:00
Owen Schwartz
7a50af14f3 Merge pull request #1733 from Fredkiss3/feat-blueprint-ui-on-dashboard
feat: blueprint ui on dashboard
2025-10-29 20:45:31 -07:00
Owen
396477c2e2 Update makefiles 2025-10-29 20:42:56 -07:00
Fred KISSIE
8765874d9a ♻️ include Blueprint applied with errors: in blueprint message when it fails 2025-10-30 02:33:45 +01:00
Fred KISSIE
49dffe086d ♻️ show warning toast and do not throw error in case of UI source 2025-10-30 02:18:48 +01:00
Owen Schwartz
77ddadcded Merge pull request #1777 from fosrl/crowdin_dev
New Crowdin updates
2025-10-29 17:51:22 -07:00
Owen Schwartz
05b297ddec New translations en-us.json (Norwegian Bokmal) 2025-10-29 17:49:16 -07:00
Owen Schwartz
feb0de9a08 New translations en-us.json (Chinese Simplified) 2025-10-29 17:49:14 -07:00
Owen Schwartz
f4f2361d22 New translations en-us.json (Turkish) 2025-10-29 17:49:13 -07:00
Owen Schwartz
cae6a9f51c New translations en-us.json (Russian) 2025-10-29 17:49:12 -07:00
Owen Schwartz
2872f5c018 New translations en-us.json (Portuguese) 2025-10-29 17:49:10 -07:00
Owen Schwartz
0512c21ad7 New translations en-us.json (Polish) 2025-10-29 17:49:09 -07:00
Owen Schwartz
922a69feed New translations en-us.json (Dutch) 2025-10-29 17:49:08 -07:00
Owen Schwartz
24192c79d4 New translations en-us.json (Korean) 2025-10-29 17:49:07 -07:00
Owen Schwartz
17c22a635f New translations en-us.json (Italian) 2025-10-29 17:49:05 -07:00
Owen Schwartz
bcbcf417b5 New translations en-us.json (German) 2025-10-29 17:49:04 -07:00
Owen Schwartz
acf7596368 New translations en-us.json (Czech) 2025-10-29 17:49:03 -07:00
Owen Schwartz
34c7d925ca New translations en-us.json (Bulgarian) 2025-10-29 17:49:01 -07:00
Owen Schwartz
c10730ebb9 New translations en-us.json (Spanish) 2025-10-29 17:49:00 -07:00
Owen Schwartz
e50743b922 New translations en-us.json (French) 2025-10-29 17:48:59 -07:00
Owen
75b0745e42 Add proxy procotol to private config 2025-10-29 17:42:21 -07:00
Owen
ebd99f95a3 Also order by id 2025-10-29 17:37:29 -07:00
Owen
0e649883cb More bugfixes 2025-10-29 17:21:32 -07:00
Fred KISSIE
3d376c8d14 ♻️ change default blueprint table ordering to createdAt: desc 2025-10-30 00:55:12 +01:00
Fred KISSIE
adedb0e391 💬 Show Success: Blueprint applied successfully 2025-10-30 00:54:15 +01:00
Fred KISSIE
521935786c 💄 remove rounded-sm 2025-10-30 00:34:14 +01:00
Fred KISSIE
885b9d186b ♻️ remove blueprint name form description 2025-10-30 00:32:55 +01:00
Fred KISSIE
356f023539 💬 fix capitalization 2025-10-30 00:32:08 +01:00
Fred KISSIE
de8d3f45da 💬 uppercase blueprint in create blueprint 2025-10-30 00:30:50 +01:00
Fred KISSIE
72c9956190 💄 Standardize go back to blueprints 2025-10-30 00:27:27 +01:00
Owen
6dc4cbe448 Check country code 2025-10-29 16:24:35 -07:00
Fred KISSIE
77364488c2 💄 show action on the right of the column 2025-10-30 00:21:59 +01:00
Fred KISSIE
5a61040027 💄 remove form description and format w/ prettier 2025-10-30 00:21:39 +01:00
Owen
c6f7be40df Sort by descending 2025-10-29 16:16:25 -07:00
Fred KISSIE
c36fb63f8c 🔨 add drizzle in docker-compose DEV for viewing the postgres db in local development 2025-10-30 00:10:49 +01:00
Owen
48aebea6cf Show error 2025-10-29 15:23:53 -07:00
Owen
55082d2ef8 Rename file 2025-10-29 14:49:53 -07:00
Owen
cc03b97234 Merge branch 'dev' into feat-blueprint-ui-on-dashboard 2025-10-29 14:46:34 -07:00
Owen
5542873368 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2025-10-29 14:43:32 -07:00
Owen
1db5d76ef1 Merge branch 'main' into dev 2025-10-29 14:43:18 -07:00
Owen
ca6c45087b Fix the ordering of deleting targets 2025-10-29 14:40:09 -07:00
Owen Schwartz
3333eb95f9 Merge pull request #1773 from Pallavikumarimdb/fix/long-copy-box
Fix text overflow in CopyToClipboard by setting a max width
2025-10-29 14:11:27 -07:00
Pallavi Kumari
d681725fc3 update max width of CopyToClipboard 2025-10-30 00:59:08 +05:30
Owen
f5eadc9e1e Various fixes 2025-10-29 12:16:28 -07:00
miloschwartz
219e213c1e change logs to debug 2025-10-29 11:39:45 -07:00
miloschwartz
af654e663b add missing translation key 2025-10-29 11:34:13 -07:00
Fred KISSIE
39b3b4ef9d 🐛 add missing orgId to blueprints table 2025-10-29 14:39:42 +01:00
Owen Schwartz
6c62a0900f Merge pull request #1763 from fosrl/dependabot/npm_and_yarn/prod-patch-updates-56e321e524
Bump posthog-node from 5.10.3 to 5.10.4 in the prod-patch-updates group
2025-10-28 21:15:31 -07:00
Owen Schwartz
ddd772eb43 Merge pull request #1769 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-a759a3dffa
Bump the dev-patch-updates group across 1 directory with 4 updates
2025-10-28 21:15:24 -07:00
Fred KISSIE
69458ab649 🔇 remove console.log 2025-10-29 04:25:37 +01:00
Fred KISSIE
c7df70143e ♻️ log only in DEV 2025-10-29 03:50:36 +01:00
Fred KISSIE
a81ea7cc8f 🐛 fix merge errors 2025-10-29 03:34:44 +01:00
Fred KISSIE
02330a0756 Merge branch 'dev' into feat-blueprint-ui-on-dashboard 2025-10-29 03:31:51 +01:00
Fred KISSIE
db49b599b5 add faker dependency 2025-10-29 03:09:16 +01:00
Fred KISSIE
bb0bfd440a ♻️ refactor 2025-10-29 03:09:02 +01:00
Fred KISSIE
10ce732b8d 🚚 rename integration API applyBlueprint to apply JSON blueprint and the UI applyBlueprint to apply YAML blueprint 2025-10-29 03:08:48 +01:00
Fred KISSIE
4c567cf2d7 ♻️ refactor docker and websocket blueprint to call the new applyBlueprint function 2025-10-29 03:07:55 +01:00
Fred KISSIE
2783d2989d ♻️ refactor 2025-10-29 03:06:42 +01:00
Fred KISSIE
c3d6510231 💬 update the text in the blueprint details page to say parsed contents 2025-10-29 03:06:28 +01:00
Fred KISSIE
3bb948991f ♻️ applyBlueprint core logic now saves the blueprint in the DB 2025-10-29 03:01:25 +01:00
dependabot[bot]
4b9ce22f06 Bump the dev-patch-updates group across 1 directory with 4 updates
Bumps the dev-patch-updates group with 4 updates in the / directory: [@types/express](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) and [drizzle-kit](https://github.com/drizzle-team/drizzle-orm).


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

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

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

Updates `drizzle-kit` from 0.31.5 to 0.31.6
- [Release notes](https://github.com/drizzle-team/drizzle-orm/releases)
- [Commits](https://github.com/drizzle-team/drizzle-orm/compare/drizzle-kit@0.31.5...drizzle-kit@0.31.6)

---
updated-dependencies:
- dependency-name: "@types/express"
  dependency-version: 5.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/node"
  dependency-version: 24.9.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/pg"
  dependency-version: 8.15.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: drizzle-kit
  dependency-version: 0.31.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-29 01:22:53 +00:00
miloschwartz
772bda69f9 check for user email in support request 2025-10-27 21:56:22 -07:00
miloschwartz
8b4722b1c9 add support message button in saas 2025-10-27 21:55:34 -07:00
Owen
9e5c9d9c34 Check role access when inviting users 2025-10-27 20:51:16 -07:00
dependabot[bot]
ee533df38f Bump posthog-node from 5.10.3 to 5.10.4 in the prod-patch-updates group
Bumps the prod-patch-updates group with 1 update: [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node).


Updates `posthog-node` from 5.10.3 to 5.10.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.10.4/packages/node)

---
updated-dependencies:
- dependency-name: posthog-node
  dependency-version: 5.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 01:31:57 +00:00
Owen
52dc8e011c Merge branch 'dev' of github.com:fosrl/pangolin into dev 2025-10-27 17:55:10 -07:00
Owen
bd5cc790d6 Fixing various things 2025-10-27 17:52:39 -07:00
Milo Schwartz
7d6d5a7787 Update README.md 2025-10-27 20:15:44 -04:00
Milo Schwartz
ba6e7dd06a Update README.md 2025-10-27 20:08:14 -04:00
miloschwartz
6270fb3237 consolidate install commands 2025-10-27 16:58:11 -07:00
miloschwartz
16ec50a6ee add alaytics to saas 2025-10-27 16:43:52 -07:00
miloschwartz
3d2021c8a1 use select component 2025-10-27 16:38:04 -07:00
Owen
15d63ddffa Various fixes for rc 2025-10-27 16:33:21 -07:00
Fred KISSIE
7ce6fadb3d blueprint details page 2025-10-28 00:14:27 +01:00
Owen
6b18a24f9b @server/private -> #dynamic 2025-10-27 13:46:54 -07:00
Owen
a38cb961c7 Create missing stubs 2025-10-27 13:45:24 -07:00
Owen
3c5fe21078 Add missing header 2025-10-27 11:54:56 -07:00
Owen
b44305694f Add postgres migration 2025-10-27 11:52:45 -07:00
Owen
be217e2b6f Create 1.12.0 sqlite migration 2025-10-27 11:47:14 -07:00
Owen
6ce04c2aa1 Change migration to 1.12.0 2025-10-27 11:34:46 -07:00
Owen
85e4b649db Update cicd: allow to run on rc 2025-10-27 11:14:56 -07:00
Owen
73a3335148 Update cicd: login to ghcr 2025-10-27 11:13:05 -07:00
Owen
32845c5a3d Fix const issue 2025-10-27 11:03:16 -07:00
Owen Schwartz
05a878ac34 Merge pull request #1759 from fosrl/crowdin_dev
New Crowdin updates
2025-10-27 10:55:59 -07:00
Owen Schwartz
847d015243 New translations en-us.json (Spanish) 2025-10-27 10:55:27 -07:00
Owen Schwartz
51cde2681c New translations en-us.json (Norwegian Bokmal) 2025-10-27 10:55:26 -07:00
Owen Schwartz
9c0606942c New translations en-us.json (Chinese Simplified) 2025-10-27 10:55:24 -07:00
Owen Schwartz
646d476bdb New translations en-us.json (Turkish) 2025-10-27 10:55:23 -07:00
Owen Schwartz
31261681a0 New translations en-us.json (Russian) 2025-10-27 10:55:21 -07:00
Owen Schwartz
f6fae820c4 New translations en-us.json (Portuguese) 2025-10-27 10:55:20 -07:00
Owen Schwartz
b3cbf925aa New translations en-us.json (Polish) 2025-10-27 10:55:19 -07:00
Owen Schwartz
aa1ae3ee42 New translations en-us.json (Dutch) 2025-10-27 10:55:17 -07:00
Owen Schwartz
80f6c8b74e New translations en-us.json (Korean) 2025-10-27 10:55:16 -07:00
Owen Schwartz
79d8e8d59d New translations en-us.json (Italian) 2025-10-27 10:55:15 -07:00
Owen Schwartz
9193375586 New translations en-us.json (German) 2025-10-27 10:55:13 -07:00
Owen Schwartz
240bcb8759 New translations en-us.json (Czech) 2025-10-27 10:55:12 -07:00
Owen Schwartz
a5dcafb84c New translations en-us.json (Bulgarian) 2025-10-27 10:55:11 -07:00
Owen Schwartz
192207a857 New translations en-us.json (French) 2025-10-27 10:55:09 -07:00
Owen Schwartz
d18fafb0ef Merge pull request #1757 from fosrl/user-compliance
Enforce org user compliance
2025-10-27 10:44:13 -07:00
Owen
380c86898c Fix lint 2025-10-27 10:43:44 -07:00
Owen
b59a6b82ef Merge branch 'dev' into user-compliance 2025-10-27 10:37:53 -07:00
Owen Schwartz
77ba568c36 Merge pull request #1755 from fosrl/audit-logs
Request, action, and access logs
2025-10-27 10:10:57 -07:00
Owen
a0f05cc77b Resolve export of logActionAudit 2025-10-27 10:09:06 -07:00
Owen
80f43a9774 Fix lint 2025-10-27 10:05:31 -07:00
Owen
c04d9eda6b Merge branch 'dev' into audit-logs 2025-10-27 10:02:32 -07:00
Owen Schwartz
cabf3e9695 Merge pull request #1749 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-b7f346f221
Bump the dev-patch-updates group with 7 updates
2025-10-27 09:57:14 -07:00
Owen Schwartz
ff7b4386d6 Merge pull request #1750 from fosrl/dependabot/npm_and_yarn/prod-patch-updates-c81bf49cf4
Bump the prod-patch-updates group with 5 updates
2025-10-27 09:57:03 -07:00
Owen Schwartz
4dbbe159ee Merge pull request #1751 from fosrl/dependabot/github_actions/actions/upload-artifact-5.0.0
Bump actions/upload-artifact from 4.6.2 to 5.0.0
2025-10-27 09:56:52 -07:00
miloschwartz
eeab92719a add smaller time values and update translations 2025-10-27 09:52:25 -07:00
miloschwartz
43e6b7de07 remove delete on cascade for skipToIdp on resource closes #1654 2025-10-27 09:46:26 -07:00
miloschwartz
4cfd1b1ff5 always check resource session length 2025-10-27 09:45:12 -07:00
dependabot[bot]
09ba018493 Bump actions/upload-artifact from 4.6.2 to 5.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](ea165f8d65...330a01c490)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 01:41:35 +00:00
dependabot[bot]
7acf7dd0eb Bump the prod-patch-updates group with 5 updates
Bumps the prod-patch-updates group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [canvas-confetti](https://github.com/catdad/canvas-confetti) | `1.9.3` | `1.9.4` |
| [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.44.6` | `0.44.7` |
| [ioredis](https://github.com/luin/ioredis) | `5.8.1` | `5.8.2` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `7.0.9` | `7.0.10` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.10.0` | `5.10.3` |


Updates `canvas-confetti` from 1.9.3 to 1.9.4
- [Release notes](https://github.com/catdad/canvas-confetti/releases)
- [Commits](https://github.com/catdad/canvas-confetti/compare/1.9.3...1.9.4)

Updates `drizzle-orm` from 0.44.6 to 0.44.7
- [Release notes](https://github.com/drizzle-team/drizzle-orm/releases)
- [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.44.6...0.44.7)

Updates `ioredis` from 5.8.1 to 5.8.2
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.8.1...v5.8.2)

Updates `nodemailer` from 7.0.9 to 7.0.10
- [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/v7.0.9...v7.0.10)

Updates `posthog-node` from 5.10.0 to 5.10.3
- [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.10.3/packages/node)

---
updated-dependencies:
- dependency-name: canvas-confetti
  dependency-version: 1.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: drizzle-orm
  dependency-version: 0.44.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: ioredis
  dependency-version: 5.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: nodemailer
  dependency-version: 7.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: posthog-node
  dependency-version: 5.10.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 01:36:44 +00:00
Owen
592d085de6 Lock down days 2025-10-26 18:36:09 -07:00
dependabot[bot]
2cf2c64651 Bump the dev-patch-updates group with 7 updates
Bumps the dev-patch-updates group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [@react-email/preview-server](https://github.com/resend/react-email/tree/HEAD/packages/preview-server) | `4.3.1` | `4.3.2` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.1.15` | `4.1.16` |
| [@types/cookie-parser](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/cookie-parser) | `1.4.9` | `1.4.10` |
| [@types/express](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express) | `5.0.3` | `5.0.4` |
| [@types/yargs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/yargs) | `17.0.33` | `17.0.34` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `4.3.1` | `4.3.2` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.15` | `4.1.16` |


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

Updates `@tailwindcss/postcss` from 4.1.15 to 4.1.16
- [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.1.16/packages/@tailwindcss-postcss)

Updates `@types/cookie-parser` from 1.4.9 to 1.4.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/cookie-parser)

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

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

Updates `react-email` from 4.3.1 to 4.3.2
- [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@4.3.2/packages/react-email)

Updates `tailwindcss` from 4.1.15 to 4.1.16
- [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.1.16/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: "@react-email/preview-server"
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.1.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/cookie-parser"
  dependency-version: 1.4.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/express"
  dependency-version: 5.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/yargs"
  dependency-version: 17.0.34
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react-email
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.1.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 01:27:47 +00:00
Owen
560974f7d2 Merge branch 'feat/add-proxy-protocol-support' into dev 2025-10-26 18:16:38 -07:00
Owen
85270f497a Restrict raw resources and use st from config 2025-10-26 18:15:39 -07:00
miloschwartz
9fbea4a380 move enterprise/subscription required alert to component 2025-10-26 17:12:47 -07:00
miloschwartz
cbf9c5361e redirect to org login via query param 2025-10-26 17:08:35 -07:00
miloschwartz
44316731c0 enforce resource session length 2025-10-26 16:52:15 -07:00
Owen Schwartz
60513af8ed Merge pull request #1716 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-5d11ea411f
Bump the dev-patch-updates group with 3 updates
2025-10-26 16:31:27 -07:00
Owen Schwartz
24cfe02979 Merge pull request #1717 from fosrl/dependabot/npm_and_yarn/dev-minor-updates-648ae407da
Bump @types/node from 24.8.1 to 24.9.1 in the dev-minor-updates group
2025-10-26 16:31:18 -07:00
Owen
8f3324560a Install maxmind by default 2025-10-26 16:04:19 -07:00
Owen
2041edcf30 Allow protocols on the same port
Fixes #1745
2025-10-26 15:57:12 -07:00
miloschwartz
1227b3c11a use alert instead of badge for unlock status 2025-10-25 17:21:21 -07:00
miloschwartz
8973726f63 add org policy check to verify session 2025-10-25 17:15:37 -07:00
Owen
5559fef1bc Merge branch 'dev' of github.com:fosrl/pangolin into dev 2025-10-25 16:27:12 -07:00
Owen
9cb3c3821a Merge branch 'Pallavikumarimdb-feat/cert-resolver-through-UI' into dev 2025-10-25 16:25:51 -07:00
Owen
c85e367ded Merge branch 'feat/cert-resolver-through-UI' of github.com:Pallavikumarimdb/pangolin into Pallavikumarimdb-feat/cert-resolver-through-UI 2025-10-25 16:25:42 -07:00
Owen Schwartz
5e20487216 Merge pull request #1732 from Pallavikumarimdb/feat/show-update-for-client-olm
Client olm version show in the table with an update prompt
2025-10-25 16:23:51 -07:00
Owen Schwartz
bc6b9eb905 Merge pull request #1736 from Lokowitz/fix-geoip-blueprint
fix blueprint country issue - fix #1705 - option 2
2025-10-25 16:21:49 -07:00
Owen
5940bbd498 Uppercase 2025-10-25 16:20:50 -07:00
Owen
f4a0f6a2e6 Update ui 2025-10-25 16:17:45 -07:00
Milo Schwartz
0df7d45678 Update README.md 2025-10-25 13:48:14 -04:00
Fred KISSIE
a05ee2483b 💄 adjust form style for createblueprintform 2025-10-25 03:22:51 +02:00
Fred KISSIE
f5dbc18c05 create and apply blueprint 2025-10-25 03:06:54 +02:00
Fred KISSIE
dd052fa1af 💄 Gave a relooking to the blueprint table 2025-10-25 03:06:45 +02:00
Fred KISSIE
2cc4ad9c30 💄 fix header & cell misalignment in table 2025-10-25 03:05:47 +02:00
Fred KISSIE
4dd741cc3f 🔊 log all SQL queries 2025-10-25 02:55:19 +02:00
miloschwartz
9ce81b34c9 add confirm dialog to update security settings 2025-10-24 17:30:39 -07:00
miloschwartz
460df46abc update translation and send password reset email 2025-10-24 17:18:34 -07:00
miloschwartz
1e70e4289b add password expiry enforcement 2025-10-24 17:11:39 -07:00
Owen
5fa0ac5927 Add hybrid request logs function 2025-10-24 17:05:05 -07:00
Owen
4b40e7b8d6 Restrict features 2025-10-24 16:29:37 -07:00
Fred KISSIE
29cd035a05 🚧 add & validate blueprint yaml 2025-10-25 01:25:19 +02:00
miloschwartz
39d6b93d42 enforce max session length 2025-10-24 16:14:21 -07:00
miloschwartz
629f17294a 2fa policy check working 2025-10-24 14:31:50 -07:00
Owen
10a5af67aa Merge branch 'dev' into audit-logs 2025-10-24 11:15:39 -07:00
Owen
b542d82553 Consolidate into central cache 2025-10-24 11:14:07 -07:00
Owen
2a644c3f88 Working on settings 2025-10-24 10:51:32 -07:00
Owen
f6de61968d Merge branch 'dev' into audit-logs 2025-10-24 10:31:54 -07:00
Owen
68f0c4df3a Working on licencing 2025-10-24 10:11:28 -07:00
Pallavi Kumari
0743daf56a add en-US for proxy protocol 2025-10-24 16:30:34 +05:30
Pallavi Kumari
58b6ab2601 Implement Proxy Protocol handling in Traefik config generator 2025-10-24 15:56:46 +05:30
Fred KISSIE
038f8829c2 🚧 create blueprint form 2025-10-24 04:17:13 +02:00
miloschwartz
ddcf77a62d add basic org policy check in middleware 2025-10-23 18:15:00 -07:00
Owen
adefbdbeb3 Fix various ui bugs 2025-10-23 17:36:24 -07:00
Owen
921285e5b1 Filtering on all tables 2025-10-23 15:33:29 -07:00
Owen
264bf46798 Filtering working on both access and request 2025-10-23 14:34:56 -07:00
miloschwartz
5a7b5d65a4 remove org settings json 2025-10-23 14:22:50 -07:00
Fred KISSIE
23b13f0a0e 💄 add toploader navigation 2025-10-23 23:10:28 +02:00
Fred KISSIE
90ddffce0e 🚧 create blueprint page 2025-10-23 22:27:14 +02:00
Fred KISSIE
e30fde5237 💄 blueprint data table 2025-10-23 22:14:09 +02:00
Pallavi Kumari
ac683c3ff7 add pg schema for proxy protocol 2025-10-23 23:24:42 +05:30
Pallavi Kumari
b5a931c96e UI and backend update to add proxy protocol support 2025-10-23 23:07:26 +05:30
Lokowitz
5b61742075 change geoip to country 2025-10-23 13:27:34 +00:00
Lokowitz
4e4a38f7e9 move to match type country instead of geoip 2025-10-23 13:19:27 +00:00
miloschwartz
c1bb029a1c simplify telemetry collection 2025-10-22 21:41:36 -07:00
Owen
eae2c37388 Add expandable columns 2025-10-22 18:21:54 -07:00
dependabot[bot]
7193fea068 Bump the dev-patch-updates group with 3 updates
Bumps the dev-patch-updates group with 3 updates: [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss), [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@tailwindcss/postcss` from 4.1.14 to 4.1.15
- [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.1.15/packages/@tailwindcss-postcss)

Updates `tailwindcss` from 4.1.14 to 4.1.15
- [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.1.15/packages/tailwindcss)

Updates `typescript-eslint` from 8.46.1 to 8.46.2
- [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.46.2/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.1.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.1.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: typescript-eslint
  dependency-version: 8.46.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-23 01:20:34 +00:00
dependabot[bot]
9b85deebf8 Bump @types/node from 24.8.1 to 24.9.1 in the dev-minor-updates group
Bumps the dev-minor-updates group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-23 01:20:13 +00:00
Owen
0211f75cb6 Access logs working 2025-10-22 17:42:27 -07:00
Fred KISSIE
fa6b7ca3ed 🚧 (WIP) blueprints table 2025-10-23 00:33:49 +02:00
Fred KISSIE
007d03e7f6 ♻️ refactor 2025-10-23 00:27:07 +02:00
Fred KISSIE
a534301eb7 ♻️ make source not null 2025-10-23 00:26:41 +02:00
miloschwartz
1baa987016 update resend ids 2025-10-22 15:14:57 -07:00
Fred KISSIE
a5b48ab392 🚧 blueprints page 2025-10-23 00:13:31 +02:00
Owen
7f981f05fb Show resource link in table for requests 2025-10-22 14:58:18 -07:00
Fred KISSIE
259cea1c42 add API endpoint for listing blueprints 2025-10-22 23:49:43 +02:00
Fred KISSIE
9024b2a974 🗃️ finish db schemas for blueprints 2025-10-22 23:49:13 +02:00
Owen
f2c31d3ca6 Add actor data to request 2025-10-22 14:27:21 -07:00
miloschwartz
6f8b5dd909 change get to post for whitelist 2025-10-22 14:02:43 -07:00
Fred KISSIE
6521b66b7c 🍱 add jsonschema for blueprint yaml validation 2025-10-22 21:58:19 +02:00
Fred KISSIE
202d2075a6 🚧 add blueprint to the sidebar and scaffold page 2025-10-22 21:56:26 +02:00
Fred KISSIE
e575fae73b 🚧 SQLite database schema with modes (is it okay ?) 2025-10-22 21:56:10 +02:00
Fred KISSIE
d84ee3d03d 🌐 add blueprint section title in the sidebar in messages (en-US for now) 2025-10-22 21:55:41 +02:00
Fred KISSIE
ba745588e9 🎨 format with prettier 2025-10-22 21:55:09 +02:00
Pallavi Kumari
84731bdc19 client olm version show in the table 2025-10-23 00:55:48 +05:30
Owen
f748c5dbe4 Basic request log working 2025-10-22 12:23:48 -07:00
Owen
fdd4d5244f Temp dont ignore org 2025-10-22 10:59:35 -07:00
Owen
9301477262 Merge branch 'dev' into audit-logs 2025-10-22 10:34:31 -07:00
Owen
9a787e6ef8 Merge branch 'main' into dev 2025-10-22 10:34:21 -07:00
Owen Schwartz
5b8cdf7884 Merge pull request #1730 from Pallavikumarimdb/fix/shareable-link-resource-URI
Update shareable link resource URI to use NiceId instead of resourceId
2025-10-22 10:11:10 -07:00
Fred KISSIE
5fd104bb30 🗃️ add bluePrintRuns model 2025-10-22 14:02:37 +02:00
Pallavi Kumari
9ba42a8fa3 add niceid to CreateShareLinkForm 2025-10-22 16:18:19 +05:30
Pallavi Kumari
fe8fd2e3a8 change shareable link resource URI from resource Id to NiceId 2025-10-22 15:53:29 +05:30
Owen
9ebce35d2b Dont do local sites undefined in cloud 2025-10-21 22:02:09 -07:00
Owen
654145be84 Clean up imports and ordering 2025-10-21 21:58:09 -07:00
Owen
3662d42374 Add resource id and cc 2025-10-21 21:42:53 -07:00
Owen
d392fb371e Add logging for all auth 2025-10-21 21:22:56 -07:00
Owen
1142d6ac48 Date picker working 2025-10-21 20:15:43 -07:00
Owen
bdc3b2425b Basic table working 2025-10-21 17:35:13 -07:00
Owen
9a64f45815 Basic log table there 2025-10-21 15:26:03 -07:00
Fred KISSIE
3633e02ff7 🔨 run next server with turbopack (easy win) 2025-10-22 00:17:42 +02:00
Owen Schwartz
2c502ec764 Merge pull request #1728 from jonasmerkel/main
Update German translations for client terminology
2025-10-21 14:29:23 -07:00
Jonas
b17d7f0e27 Update German translations for client terminology 2025-10-21 23:26:27 +02:00
Owen
65364d6b0f Merge branch 'dev' into audit-logs 2025-10-21 11:31:33 -07:00
Pallavi Kumari
6b0dd00aa5 show IP of the server inside DNS records 2025-10-21 20:43:42 +05:30
Pallavi Kumari
461866836e Remove the popup after creating domain and redirect to domain details page 2025-10-21 17:41:14 +05:30
Pallavi Kumari
3ae42f054f show the wildcard record info 2025-10-21 17:07:34 +05:30
Pallavi Kumari
5a571f19e1 add each form control it's own form field/item/control 2025-10-21 16:10:23 +05:30
Owen
70aeaf7b5d Change badges and button size 2025-10-21 15:37:03 +05:30
Pallavi Kumari
7a6838f5a5 fix lint 2025-10-21 15:37:03 +05:30
Pallavi Kumari
07f5e8f215 add update domain Settings for wildcard 2025-10-21 15:37:03 +05:30
Pallavi Kumari
2b05bc1f5f ui and layout fix 2025-10-21 15:37:03 +05:30
Pallavi Kumari
edf64ae7b5 fix invalid "default" 2025-10-21 15:37:03 +05:30
Pallavi Kumari
7370448be9 pg schema 2025-10-21 15:37:02 +05:30
Pallavi Kumari
51af293d66 add doc link button and fix continuous polling 2025-10-21 15:37:02 +05:30
Pallavi Kumari
d37e28215e add restart button 2025-10-21 15:37:02 +05:30
Pallavi Kumari
2c01849f2e fix import 2025-10-21 15:37:02 +05:30
Pallavi Kumari
c29ba9bb5f add DNS Records table 2025-10-21 15:37:02 +05:30
Pallavi Kumari
8fdf120ec2 backend setup to store and get DNS Records 2025-10-21 15:37:02 +05:30
Pallavi Kumari
a9b9161c40 template for Domain Settings 2025-10-21 15:37:02 +05:30
Pallavi Kumari
43f907ebec remove import 2025-10-21 15:37:02 +05:30
Pallavi Kumari
ae670e1eb5 initial setup for viewing domain details 2025-10-21 15:37:02 +05:30
Pallavi Kumari
f102718901 add edit button to domain table 2025-10-21 15:37:02 +05:30
Pallavi Kumari
9d452efc7d fix treafik config mismatch 2025-10-21 15:37:02 +05:30
Pallavi Kumari
156fe529b5 fix code conflicts and match dev change 2025-10-21 15:37:02 +05:30
Owen
df24525105 Fix type issues 2025-10-21 15:37:02 +05:30
Owen
d938345deb Copy in config to db, remove 2nd column, + prefer 2025-10-21 15:37:02 +05:30
Pallavi Kumari
d6681733dd remove custom cery type form config file 2025-10-21 15:37:02 +05:30
Pallavi Kumari
2f1aec02f0 traefik config update for custom Cert Resolver 2025-10-21 15:37:01 +05:30
Pallavi Kumari
d30e0a3c51 schema add 2025-10-21 15:37:01 +05:30
Pallavi Kumari
3f3e9cf1bb add cert resolver 2025-10-21 15:37:01 +05:30
Owen
f3149e46cd Starting to create frontend 2025-10-20 20:40:04 -07:00
Owen
58443ef53f Reorder log middleware 2025-10-19 22:25:00 -07:00
Owen
1ee52ad86b Add headers 2025-10-19 21:59:51 -07:00
Owen
bc941239ec Fix the indexes 2025-10-19 21:59:41 -07:00
Owen
9a52d5387d Merge branch 'dev' into audit-logs 2025-10-19 21:54:26 -07:00
Owen
1f50bc3752 Add logActionAudit and query endpoint 2025-10-19 21:53:00 -07:00
Owen
dce84b9b09 Add action audit middleware and tables 2025-10-19 17:58:52 -07:00
218 changed files with 18499 additions and 2887 deletions

View File

@@ -17,6 +17,7 @@ on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.rc.[0-9]+"
concurrency:
group: ${{ github.ref }}
@@ -30,7 +31,7 @@ jobs:
timeout-minutes: 120
env:
# Target images
DOCKERHUB_IMAGE: docker.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ github.event.repository.name }}
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
steps:
@@ -98,7 +99,7 @@ jobs:
make go-build-release
- name: Upload artifacts from /install/bin
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: install-bin
path: install/bin/
@@ -110,13 +111,6 @@ jobs:
echo "Built & pushed to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
shell: bash
- name: Login in to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install skopeo + jq
# skopeo: copy/inspect images between registries
# jq: JSON parsing tool used to extract digest values
@@ -126,6 +120,11 @@ jobs:
skopeo --version
shell: bash
- name: Login to GHCR
run: |
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
shell: bash
- name: Copy tag from Docker Hub to GHCR
# Mirror the already-built image (all architectures) to GHCR so we can sign it
run: |

View File

@@ -44,6 +44,36 @@ build-release:
--tag fosrl/pangolin:ee-postgresql-$(tag) \
--push .
build-rc:
@if [ -z "$(tag)" ]; then \
echo "Error: tag is required. Usage: make build-release tag=<tag>"; \
exit 1; \
fi
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:$(tag) \
--push .
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:postgresql-$(tag) \
--push .
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-$(tag) \
--push .
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-postgresql-$(tag) \
--push .
build-arm:
docker buildx build --platform linux/arm64 -t fosrl/pangolin:latest .

View File

@@ -45,7 +45,10 @@ Pangolin is a self-hosted tunneled reverse proxy server with identity and contex
## Installation
Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
- Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
<img src="public/screenshots/hero.png" />
## Deployment Options
@@ -86,3 +89,7 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
## Contributions
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
---
WireGuard® is a registered trademark of Jason A. Donenfeld.

View File

@@ -90,7 +90,8 @@ export const setAdminCredentials: CommandModule<{}, SetAdminCredentialsArgs> = {
passwordHash,
dateCreated: moment().toISOString(),
serverAdmin: true,
emailVerified: true
emailVerified: true,
lastPasswordChange: new Date().getTime()
});
console.log("Server admin created");

View File

@@ -0,0 +1,15 @@
services:
drizzle-gateway:
image: ghcr.io/drizzle-team/gateway:latest
ports:
- "4984:4983"
depends_on:
- db
environment:
- STORE_PATH=/app
- DATABASE_URL=postgresql://postgres:password@db:5432/postgres
volumes:
- drizzle-gateway-data:/app
volumes:
drizzle-gateway-data:

View File

@@ -11,7 +11,7 @@ services:
- ./config/postgres:/var/lib/postgresql/data
ports:
- "5432:5432" # Map host port 5432 to container port 5432
restart: no
restart: no
redis:
image: redis:latest # Use the latest Redis image

View File

@@ -18,7 +18,11 @@ put-back:
mv main.go.bak main.go
dev-update-versions:
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name') && \
if [ -z "$(tag)" ]; then \
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name'); \
else \
PANGOLIN_VERSION=$(tag); \
fi && \
GERBIL_VERSION=$$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') && \
BADGER_VERSION=$$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') && \
echo "Latest versions - Pangolin: $$PANGOLIN_VERSION, Gerbil: $$GERBIL_VERSION, Badger: $$BADGER_VERSION" && \

View File

@@ -14,7 +14,6 @@ app:
domains:
domain1:
base_domain: "{{.BaseDomain}}"
cert_resolver: "letsencrypt"
server:
secret: "{{.Secret}}"

View File

@@ -51,3 +51,12 @@ http:
loadBalancer:
servers:
- url: "http://pangolin:3000" # API/WebSocket server
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2

View File

@@ -73,7 +73,7 @@ func installDocker() error {
case strings.Contains(osRelease, "ID=ubuntu"):
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
apt-get update &&
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
apt-get install -y apt-transport-https ca-certificates curl &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
apt-get update &&
@@ -82,7 +82,7 @@ func installDocker() error {
case strings.Contains(osRelease, "ID=debian"):
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
apt-get update &&
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
apt-get install -y apt-transport-https ca-certificates curl &&
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
apt-get update &&

View File

@@ -238,7 +238,6 @@ func main() {
}
fmt.Println("CrowdSec installed successfully!")
return
}
}
}
@@ -378,7 +377,7 @@ func collectUserInput(reader *bufio.Reader) Config {
fmt.Println("\n=== Advanced Configuration ===")
config.EnableIPv6 = readBool(reader, "Is your server IPv6 capable?", true)
config.EnableGeoblocking = readBool(reader, "Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", false)
config.EnableGeoblocking = readBool(reader, "Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
if config.DashboardDomain == "" {
fmt.Println("Error: Dashboard Domain name is required")

View File

@@ -131,7 +131,7 @@
"expireIn": "Изтече",
"neverExpire": "Никога не изтича",
"shareExpireDescription": "Времето на изтичане е колко дълго връзката ще бъде използваема и ще предоставя достъп до ресурса. След това време, връзката няма да работи и потребителите, които са я използвали, ще загубят достъп до ресурса.",
"shareSeeOnce": "Ще можете да видите тази връзка само веднъж. Уверете се да я копирате.",
"shareSeeOnce": "Ще можете да видите този линк само веднъж. Уверете се, че го копирате.",
"shareAccessHint": "Всеки с тази връзка може да има достъп до ресурса. Споделяйте я с внимание.",
"shareTokenUsage": "Вижте използването на токена за достъп",
"createLink": "Създаване на връзка",
@@ -179,7 +179,7 @@
"baseDomain": "Базов домейн",
"subdomnainDescription": "Субдомейнът, в който ще бъде достъпен вашият ресурс.",
"resourceRawSettings": "TCP/UDP настройки",
"resourceRawSettingsDescription": "Конфигурирайте как вашият ресурс ще бъде достъпен през TCP/UDP",
"resourceRawSettingsDescription": "Настройте как ресурсът ви ще бъде достъпен през TCP/UDP. Свързвате ресурса към порт на хост сървъра Pangolin, за да го достъпвате от server-public-ip:mapped-port.",
"protocol": "Протокол",
"protocolSelect": "Изберете протокол",
"resourcePortNumber": "Номер на порт",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Проверете имейла си за код за нулиране.",
"passwordNew": "Нова парола",
"passwordNewConfirm": "Потвърдете новата парола",
"changePassword": "Промяна на парола",
"changePasswordDescription": "Актуализиране на паролата на акаунта",
"oldPassword": "Текуща парола",
"newPassword": "Нова парола",
"confirmNewPassword": "Потвърдете новата парола",
"changePasswordError": "Неуспешна промяна на парола",
"changePasswordErrorDescription": "Възникна грешка при промяна на вашата парола",
"changePasswordSuccess": "Паролата беше успешно променена",
"changePasswordSuccessDescription": "Вашата парола беше успешно актуализирана",
"passwordExpiryRequired": "Изисква се изтичане на срока на годност на паролата",
"passwordExpiryDescription": "Тази организация изисква да сменяте паролата си на всеки {maxDays} дни.",
"changePasswordNow": "Сменете паролата сега",
"pincodeAuth": "Код на удостоверителя",
"pincodeSubmit2": "Изпрати код",
"passwordResetSubmit": "Заявка за нулиране",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Лиценз",
"sidebarClients": "Клиенти",
"sidebarDomains": "Домейни",
"sidebarBluePrints": "Чертежи",
"blueprints": "Чертежи",
"blueprintsDescription": "Прилагайте декларативни конфигурации и преглеждайте предишни изпълнения",
"blueprintAdd": "Добави Чертеж",
"blueprintGoBack": "Виж всички Чертежи",
"blueprintCreate": "Създай Чертеж",
"blueprintCreateDescription2": "Следвайте стъпките по-долу, за да създадете и приложите нов чертеж",
"blueprintDetails": "Детайли на чертежа",
"blueprintDetailsDescription": "Вижте резултата от приложените чертежи и всички възникнали грешки",
"blueprintInfo": "Информация за Чертежа",
"message": "Съобщение",
"blueprintContentsDescription": "Дефинирайте YAML съдържанието, описващо вашата инфраструктура",
"blueprintErrorCreateDescription": "Възникна грешка при прилагането на чертежа",
"blueprintErrorCreate": "Грешка при създаването на чертеж",
"searchBlueprintProgress": "Търси чертежи...",
"appliedAt": "Приложено във",
"source": "Източник",
"contents": "Съдържание",
"parsedContents": "Парсирано съдържание (само за четене)",
"enableDockerSocket": "Активиране на Docker Чернова",
"enableDockerSocketDescription": "Активиране на Docker Socket маркировка за изтегляне на етикети на чернова. Пътят на гнездото трябва да бъде предоставен на Newt.",
"enableDockerSocketLink": "Научете повече",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Възникна проблем при използването на ключа за сигурност. Моля, опитайте отново.",
"twoFactorRequired": "Двуфакторното удостоверяване е необходимо за регистрация на ключ за защита.",
"twoFactor": "Двуфакторно удостоверяване",
"twoFactorAuthentication": "Двуфакторно удостоверяване",
"twoFactorDescription": "Тази организация изисква двуфакторно удостоверяване.",
"enableTwoFactor": "Активиране на двуфакторно удостоверяване",
"organizationSecurityPolicy": "Политика за сигурност на организацията",
"organizationSecurityPolicyDescription": "Тази организация има изисквания за сигурност, които трябва да бъдат изпълнени, за да имате достъп до нея.",
"securityRequirements": "Изисквания за сигурност",
"allRequirementsMet": "Всички изисквания са изпълнени",
"completeRequirementsToContinue": "Завършете изискванията по-долу, за да продължите достъпа до тази организация",
"youCanNowAccessOrganization": "Вече можете да получите достъп до тази организация",
"reauthenticationRequired": "Продължителност на сесията",
"reauthenticationDescription": "Тази организация изисква да влизате на всеки {maxDays} дни.",
"reauthenticationDescriptionHours": "Тази организация изисква да влизате на всеки {maxHours} часа.",
"reauthenticateNow": "Влезте отново",
"adminEnabled2FaOnYourAccount": "Вашият администратор е активирал двуфакторно удостоверяване за {email}. Моля, завършете процеса по настройка, за да продължите.",
"securityKeyAdd": "Добавяне на ключ за сигурност",
"securityKeyRegisterTitle": "Регистриране на нов ключ за сигурност",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Редактиране на файл: docker-compose.yml",
"emailVerificationRequired": "Потвърждението на Email е необходимо. Моля, влезте отново чрез {dashboardUrl}/auth/login, за да завършите тази стъпка. След това, върнете се тук.",
"twoFactorSetupRequired": "Необходима е настройка на двуфакторно удостоверяване. Моля, влезте отново чрез {dashboardUrl}/auth/login, за да завършите тази стъпка. След това, върнете се тук.",
"additionalSecurityRequired": "Необходима е допълнителна сигурност",
"organizationRequiresAdditionalSteps": "Тази организация изисква допълнителни стъпки за сигурност, за да получите достъп до ресурсите.",
"completeTheseSteps": "Завършете тези стъпки",
"enableTwoFactorAuthentication": "Активирайте двуфакторното удостоверяване",
"completeSecuritySteps": "Завършете стъпките за сигурност",
"securitySettings": "Настройки за сигурност",
"securitySettingsDescription": "Конфигурирайте политиките за сигурност на вашата организация",
"requireTwoFactorForAllUsers": "Изисквайте двуфакторно удостоверяване за всички потребители",
"requireTwoFactorDescription": "Когато е активирано, всички вътрешни потребители в организацията трябва да имат активирано двуфакторно удостоверяване, за да имат достъп до организацията.",
"requireTwoFactorDisabledDescription": "Тази функция изисква валиден лиценз (Enterprise) или активен абонамент (SaaS).",
"requireTwoFactorCannotEnableDescription": "Трябва да активирате двуфакторното удостоверяване за вашия акаунт, преди да го наложите за всички потребители.",
"maxSessionLength": "Максимална продължителност на сесията",
"maxSessionLengthDescription": "Задайте максималната продължителност на потребителските сесии. След това време потребителите ще трябва да се удостоверят отново.",
"maxSessionLengthDisabledDescription": "Тази функция изисква валиден лиценз (Enterprise) или активен абонамент (SaaS).",
"selectSessionLength": "Изберете продължителност на сесията",
"unenforced": "Неналожено",
"1Hour": "1 час",
"3Hours": "3 часа",
"6Hours": "6 часа",
"12Hours": "12 часа",
"1DaySession": "1 ден",
"3Days": "3 дни",
"7Days": "7 дни",
"14Days": "14 дни",
"30DaysSession": "30 дни",
"90DaysSession": "90 дни",
"180DaysSession": "180 дни",
"passwordExpiryDays": "Изтичане на парола",
"editPasswordExpiryDescription": "Задайте броя дни, след които потребителите трябва да сменят паролата си.",
"selectPasswordExpiry": "Изберете изтичането на парола",
"30Days": "30 дни",
"1Day": "1 ден",
"60Days": "60 дни",
"90Days": "90 дни",
"180Days": "180 дни",
"1Year": "1 година",
"subscriptionBadge": "Изисква се абонамент",
"securityPolicyChangeWarning": "Предупреждение за промяна на политиката за сигурност",
"securityPolicyChangeDescription": "Ще променяте настройките на политиката за сигурност. След запазване може да се наложи да се удостоверите отново, за да се съобразите с тези актуализации. Всички потребители, които не са съвместими, също ще трябва да се удостоверят отново.",
"securityPolicyChangeConfirmMessage": "Потвърждавам",
"securityPolicyChangeWarningText": "Това ще засегне всички потребители в организацията",
"authPageErrorUpdateMessage": "Възникна грешка при актуализирането на настройките на страницата за удостоверяване",
"authPageErrorUpdate": "Неуспешно актуализиране на страницата за удостоверяване",
"authPageUpdated": "Страницата за удостоверяване е актуализирана успешно",
"healthCheckNotAvailable": "Локална",
"rewritePath": "Пренапиши път",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Това не може да се отмени.",
"toConfirm": "за потвърждение",
"deleteClientQuestion": "Сигурни ли сте, че искате да премахнете клиента от сайта и организацията?",
"clientMessageRemove": "След като клиентът бъде премахнат, той вече няма да може да се свързва с сайта."
}
"clientMessageRemove": "След като клиентът бъде премахнат, той вече няма да може да се свързва с сайта.",
"sidebarLogs": "Логове",
"request": "Изискване",
"logs": "Логове",
"logsSettingsDescription": "Следете логовете, събрани от тази организация",
"searchLogs": "Търсете в логовете...",
"action": "Действие",
"actor": "Извършващ",
"timestamp": "Отбелязано време",
"accessLogs": "Достъп до логове",
"exportCsv": "Експортиране в CSV",
"actorId": "ID на извършващия",
"allowedByRule": "Разрешено от правило",
"allowedNoAuth": "Разрешено без удостоверение",
"validAccessToken": "Валиден токен за достъп",
"validHeaderAuth": "Валиден заглавен авто",
"validPincode": "Валиден ПИН код",
"validPassword": "Валидна парола",
"validEmail": "Валиден имейл",
"validSSO": "Валидно SSO",
"resourceBlocked": "Блокирани ресурси",
"droppedByRule": "Прекратено от правило",
"noSessions": "Няма сесии",
"temporaryRequestToken": "Временен токен на заявка",
"noMoreAuthMethods": "Няма валидни методи за удостоверение",
"ip": "IP",
"reason": "Причина",
"requestLogs": "Заявка за логове",
"host": "Хост",
"location": "Местоположение",
"actionLogs": "Дневници на действията",
"sidebarLogsRequest": "Заявка за логове",
"sidebarLogsAccess": "Достъп до логове",
"sidebarLogsAction": "Дневници на действията",
"logRetention": "Задържане на логове",
"logRetentionDescription": "Управлявайте времето за задържане на различни видове логове за тази организация или ги деактивирайте",
"requestLogsDescription": "Прегледайте подробни логове на заявки за ресурси в тази организация",
"logRetentionRequestLabel": "Задържане на логове на заявки",
"logRetentionRequestDescription": "Колко дълго да се задържат логовете на заявките",
"logRetentionAccessLabel": "Задържане на логове за достъп",
"logRetentionAccessDescription": "Колко дълго да се задържат логовете за достъп",
"logRetentionActionLabel": "Задържане на логове за действия",
"logRetentionActionDescription": "Колко дълго да се задържат логовете за действия",
"logRetentionDisabled": "Деактивирано",
"logRetention3Days": "3 дни",
"logRetention7Days": "7 дни",
"logRetention14Days": "14 дни",
"logRetention30Days": "30 дни",
"logRetention90Days": "90 дни",
"logRetentionForever": "Завинаги",
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
"licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.",
"certResolver": "Решавач на сертификати",
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
"selectCertResolver": "Изберете решавач на сертификати",
"enterCustomResolver": "Въведете персонализиран решавач",
"preferWildcardCert": "Предпочитайте универсален сертификат",
"unverified": "Невалидиран",
"domainSetting": "Настройки на домейните",
"domainSettingDescription": "Конфигурирайте настройките за вашия домейн",
"preferWildcardCertDescription": "Опит за генериране на универсален сертификат (изисква правилно конфигуриран решавач на сертификати).",
"recordName": "Име на запис",
"auto": "Автоматично",
"TTL": "TTL",
"howToAddRecords": "Как да добавите записи",
"dnsRecord": "DNS записи",
"required": "Задължително",
"domainSettingsUpdated": "Настройките на домейна са успешно актуализирани",
"orgOrDomainIdMissing": "Липсва идентификатор на организация или домейн",
"loadingDNSRecords": "Зареждане на DNS записи...",
"olmUpdateAvailableInfo": "Налична е актуализирана версия на Olm. Моля, актуализирайте до най-новата версия за най-добро преживяване.",
"client": "Клиент",
"proxyProtocol": "Настройки на прокси протокол",
"proxyProtocolDescription": "Конфигурирайте прокси протокол за запазване на IP адресите на клиентите за TCP/UDP услуги.",
"enableProxyProtocol": "Активирайте прокси протокола",
"proxyProtocolInfo": "Запазете IP адресите на клиентите за TCP/UDP бекенди",
"proxyProtocolVersion": "Версия на прокси протокола",
"version1": "Версия 1 (Препоръчително)",
"version2": "Версия 2",
"versionDescription": "Версия 1 е текстово-базирана и широко поддържана. Версия 2 е бърна и по-ефективна, но по-малко съвместима.",
"warning": "Предупреждение",
"proxyProtocolWarning": "Вашето бекенд приложение трябва да бъде конфигурирано да приема прокси протоколни връзки. Ако вашият бекенд не поддържа прокси протокол, активирането му ще прекъсне всички връзки. Уверете се, че сте конфигурирали вашия бекенд да се доверява на заглавията на прокси протокола от Traefik.",
"restarting": "Рестартиране...",
"manual": "Ръководство",
"messageSupport": "Съобщение до поддръжката",
"supportNotAvailableTitle": "Поддръжката не е налична",
"supportNotAvailableDescription": "Поддръжката не е налична в момента. Можете да изпратите имейл на support@pangolin.net.",
"supportRequestSentTitle": "Заявката за поддръжка е изпратена",
"supportRequestSentDescription": "Вашето съобщение беше изпратено успешно.",
"supportRequestFailedTitle": "Неуспешно изпращане на заявка",
"supportRequestFailedDescription": "Възникна грешка при изпращането на вашата заявка за поддръжка.",
"supportSubjectRequired": "Необходимо е въведеното описание",
"supportSubjectMaxLength": "Темата трябва да бъде до 255 символа",
"supportMessageRequired": "Необходимо е съобщение",
"supportReplyTo": "Отговор до",
"supportSubject": "Тема",
"supportSubjectPlaceholder": "Въведете тема",
"supportMessage": "Съобщение",
"supportMessagePlaceholder": "Въведете вашето съобщение",
"supportSending": "Изпращане...",
"supportSend": "Изпрати",
"supportMessageSent": "Съобщението е изпратено!",
"supportWillContact": "Ще се свържем с вас скоро!",
"selectLogRetention": "Изберете съхранение на логовете",
"showColumns": "Покажи колони",
"hideColumns": "Скрий колони",
"columnVisibility": "Видимост на колоните",
"toggleColumn": "Превключване на колоната {columnName}",
"allColumns": "Всички колони",
"defaultColumns": "По подразбиране колони",
"customizeView": "Персонализиране на изгледа",
"viewOptions": "Опции за изгледа",
"selectAll": "Избери всички",
"selectNone": "Избери нищо",
"selectedResources": "Избрани ресурси",
"enableSelected": "Разреши избраните",
"disableSelected": "Забрани избраните",
"checkSelectedStatus": "Проверете състоянието на избраните"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Platnost vyprší za",
"neverExpire": "Nikdy nevyprší",
"shareExpireDescription": "Doba platnosti určuje, jak dlouho bude odkaz použitelný a bude poskytovat přístup ke zdroji. Po této době odkaz již nebude fungovat a uživatelé kteří tento odkaz používali ztratí přístup ke zdroji.",
"shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Ujistěte se, že jste jej zkopírovali.",
"shareSeeOnce": "Tento odkaz uvidíte pouze jednou. Nezapomeňte jej zkopírovat.",
"shareAccessHint": "Kdokoli s tímto odkazem může přistupovat ke zdroji. Sdílejte jej s rozvahou.",
"shareTokenUsage": "Zobrazit využití přístupového tokenu",
"createLink": "Vytvořit odkaz",
@@ -179,7 +179,7 @@
"baseDomain": "Základní doména",
"subdomnainDescription": "Subdoména, kde bude váš zdroj přístupný.",
"resourceRawSettings": "Nastavení TCP/UDP",
"resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP",
"resourceRawSettingsDescription": "Nakonfigurujte, jak bude váš dokument přístupný přes TCP/UDP. Mapováte zdroj na port na serveru Pangolin, takže můžete přistupovat ke zdroji ze serveru-veřejné ip:mapped-port.",
"protocol": "Protokol",
"protocolSelect": "Vybrat protokol",
"resourcePortNumber": "Číslo portu",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Zkontrolujte svůj e-mail pro kód pro obnovení.",
"passwordNew": "Nové heslo",
"passwordNewConfirm": "Potvrdit nové heslo",
"changePassword": "Změnit heslo",
"changePasswordDescription": "Aktualizujte heslo k účtu",
"oldPassword": "Aktuální heslo",
"newPassword": "Nové heslo",
"confirmNewPassword": "Potvrdit nové heslo",
"changePasswordError": "Změna hesla se nezdařila",
"changePasswordErrorDescription": "Došlo k chybě při změně hesla",
"changePasswordSuccess": "Heslo úspěšně změněno",
"changePasswordSuccessDescription": "Vaše heslo bylo úspěšně aktualizováno",
"passwordExpiryRequired": "Vyžaduje vypršení platnosti hesla",
"passwordExpiryDescription": "Tato organizace vyžaduje změnu hesla každých {maxDays} dní.",
"changePasswordNow": "Změnit heslo",
"pincodeAuth": "Ověřovací kód",
"pincodeSubmit2": "Odeslat kód",
"passwordResetSubmit": "Žádost o obnovení",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licence",
"sidebarClients": "Klienti",
"sidebarDomains": "Domény",
"sidebarBluePrints": "Plány",
"blueprints": "Plány",
"blueprintsDescription": "Použít deklarativní konfigurace a zobrazit předchozí běhy",
"blueprintAdd": "Přidat plán",
"blueprintGoBack": "Zobrazit všechny plány",
"blueprintCreate": "Vytvořit plán",
"blueprintCreateDescription2": "Postupujte podle níže uvedených kroků pro vytvoření a použití nového plánu",
"blueprintDetails": "Podrobnosti plánu",
"blueprintDetailsDescription": "Podívejte se na výsledek použitého plánu a případné chyby, které se vyskytly",
"blueprintInfo": "Informace o plánu",
"message": "Zpráva",
"blueprintContentsDescription": "Definujte obsah YAML popisující vaši infrastrukturu",
"blueprintErrorCreateDescription": "Při aplikaci plánu došlo k chybě",
"blueprintErrorCreate": "Chyba při vytváření plánu",
"searchBlueprintProgress": "Hledat plány...",
"appliedAt": "Použito v",
"source": "Zdroj",
"contents": "Obsah",
"parsedContents": "Parsed content (Pouze pro čtení)",
"enableDockerSocket": "Povolit Docker plán",
"enableDockerSocketDescription": "Povolte seškrábání štítků na Docker Socket pro popisky plánů. Nová cesta musí být k dispozici.",
"enableDockerSocketLink": "Zjistit více",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Vyskytl se problém s použitím bezpečnostního klíče. Zkuste to prosím znovu.",
"twoFactorRequired": "Pro registraci bezpečnostního klíče je nutné dvoufaktorové ověření.",
"twoFactor": "Dvoufaktorové ověření",
"twoFactorAuthentication": "Dvoufaktorové ověření",
"twoFactorDescription": "Tato organizace vyžaduje dvoufaktorové ověření.",
"enableTwoFactor": "Povolit dvoufaktorové ověření",
"organizationSecurityPolicy": "Bezpečnostní politika organizace",
"organizationSecurityPolicyDescription": "Tato organizace má bezpečnostní požadavky, které musí být splněny, než k ní budete mít přístup",
"securityRequirements": "Bezpečnostní požadavky",
"allRequirementsMet": "Všechny požadavky byly splněny",
"completeRequirementsToContinue": "Pro pokračování v přístupu k této organizaci splněte níže uvedené požadavky",
"youCanNowAccessOrganization": "Nyní můžete přistupovat k této organizaci",
"reauthenticationRequired": "Délka relace",
"reauthenticationDescription": "Tato organizace vyžaduje, abyste se přihlásili každých {maxDays} dní.",
"reauthenticationDescriptionHours": "Tato organizace vyžaduje, abyste se přihlásili každých {maxHours} hodin.",
"reauthenticateNow": "Přihlásit se znovu",
"adminEnabled2FaOnYourAccount": "Váš správce povolil dvoufaktorové ověřování pro {email}. Chcete-li pokračovat, dokončete proces nastavení.",
"securityKeyAdd": "Přidat bezpečnostní klíč",
"securityKeyRegisterTitle": "Registrovat nový bezpečnostní klíč",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Upravit soubor: docker-compose.yml",
"emailVerificationRequired": "Je vyžadováno ověření e-mailu. Přihlaste se znovu pomocí {dashboardUrl}/auth/login dokončete tento krok. Poté se vraťte zde.",
"twoFactorSetupRequired": "Je vyžadováno nastavení dvoufaktorového ověřování. Přihlaste se znovu pomocí {dashboardUrl}/autentizace/přihlášení dokončí tento krok. Poté se vraťte zde.",
"additionalSecurityRequired": "Vyžadováno další zabezpečení",
"organizationRequiresAdditionalSteps": "Tato organizace vyžaduje další bezpečnostní kroky, než budete moci přistupovat ke zdrojům.",
"completeTheseSteps": "Dokončete tyto kroky",
"enableTwoFactorAuthentication": "Povolit dvoufaktorové ověření",
"completeSecuritySteps": "Dokončit bezpečnostní kroky",
"securitySettings": "Nastavení zabezpečení",
"securitySettingsDescription": "Konfigurace bezpečnostních zásad pro vaši organizaci",
"requireTwoFactorForAllUsers": "Vyžadovat dvoufaktorové ověření pro všechny uživatele",
"requireTwoFactorDescription": "Pokud je povoleno, všichni interní uživatelé v této organizaci musí mít dvoufaktorové ověření povoleno pro přístup k organizaci.",
"requireTwoFactorDisabledDescription": "Tato funkce vyžaduje platnou licenci (Enterprise) nebo aktivní předplatné (SaaS)",
"requireTwoFactorCannotEnableDescription": "Před vynucením dvoufaktorového ověření vašeho účtu musíte povolit dvoufaktorové ověření pro všechny uživatele",
"maxSessionLength": "Maximální délka relace",
"maxSessionLengthDescription": "Nastavte maximální dobu trvání relace uživatele. Po této době se uživatelé budou muset znovu přihlásit.",
"maxSessionLengthDisabledDescription": "Tato funkce vyžaduje platnou licenci (Enterprise) nebo aktivní předplatné (SaaS)",
"selectSessionLength": "Vyberte délku relace",
"unenforced": "Nevynucené",
"1Hour": "1 hodina",
"3Hours": "3 hodiny",
"6Hours": "6 hodin",
"12Hours": "12 hodin",
"1DaySession": "1 den",
"3Days": "3 dny",
"7Days": "7 dní",
"14Days": "14 dní",
"30DaysSession": "30 dní",
"90DaysSession": "90 dní",
"180DaysSession": "180 dní",
"passwordExpiryDays": "Platnost hesla",
"editPasswordExpiryDescription": "Nastavte počet dní před tím, než jsou uživatelé povinni změnit své heslo.",
"selectPasswordExpiry": "Vyberte vypršení platnosti hesla",
"30Days": "30 dní",
"1Day": "1 den",
"60Days": "60 dní",
"90Days": "90 dní",
"180Days": "180 dní",
"1Year": "1 rok",
"subscriptionBadge": "Vyžadováno předplatné",
"securityPolicyChangeWarning": "Upozornění na změnu bezpečnostní politiky",
"securityPolicyChangeDescription": "Chystáte se změnit nastavení bezpečnostních pravidel. Po uložení bude možná nutné znovu ověřit, abyste dodrželi tyto aktualizace přístupových práv. Všichni uživatelé, kteří nevyhovují, se také budou muset znovu přihlásit.",
"securityPolicyChangeConfirmMessage": "Potvrzuji",
"securityPolicyChangeWarningText": "Toto ovlivní všechny uživatele v organizaci",
"authPageErrorUpdateMessage": "Při aktualizaci nastavení autentizační stránky došlo k chybě",
"authPageErrorUpdate": "Nelze aktualizovat ověřovací stránku",
"authPageUpdated": "Autentizační stránka byla úspěšně aktualizována",
"healthCheckNotAvailable": "Místní",
"rewritePath": "Přepsat cestu",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "To nelze vrátit zpět.",
"toConfirm": "Potvrdit",
"deleteClientQuestion": "Jste si jisti, že chcete odstranit klienta z webu a organizace?",
"clientMessageRemove": "Po odstranění se klient již nebude moci připojit k webu."
}
"clientMessageRemove": "Po odstranění se klient již nebude moci připojit k webu.",
"sidebarLogs": "Logy",
"request": "Žádost",
"logs": "Logy",
"logsSettingsDescription": "Monitorovat logy shromážděné z této orginizace",
"searchLogs": "Hledat logy...",
"action": "Akce",
"actor": "Aktér",
"timestamp": "Časové razítko",
"accessLogs": "Protokoly přístupu",
"exportCsv": "Exportovat CSV",
"actorId": "ID aktéra",
"allowedByRule": "Povoleno pomocí pravidla",
"allowedNoAuth": "Povoleno bez ověření",
"validAccessToken": "Platný přístupový token",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Platné heslo",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Zablokované zdroje",
"droppedByRule": "Zrušeno pravidlem",
"noSessions": "Žádné relace",
"temporaryRequestToken": "Dočasný požadavek token",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP adresa",
"reason": "Důvod",
"requestLogs": "Záznamy požadavků",
"host": "Hostitel",
"location": "Poloha",
"actionLogs": "Záznamy akcí",
"sidebarLogsRequest": "Záznamy požadavků",
"sidebarLogsAccess": "Protokoly přístupu",
"sidebarLogsAction": "Záznamy akcí",
"logRetention": "Zaznamenávání záznamu",
"logRetentionDescription": "Spravovat, jak dlouho jsou různé typy logů uloženy pro tuto organizaci nebo je zakázat",
"requestLogsDescription": "Zobrazit podrobné protokoly požadavků pro zdroje v této organizaci",
"logRetentionRequestLabel": "Zachování logu žádosti",
"logRetentionRequestDescription": "Jak dlouho uchovávat záznamy požadavků",
"logRetentionAccessLabel": "Zachování záznamu přístupu",
"logRetentionAccessDescription": "Jak dlouho uchovávat přístupové záznamy",
"logRetentionActionLabel": "Uchovávání protokolu akcí",
"logRetentionActionDescription": "Jak dlouho uchovávat záznamy akcí",
"logRetentionDisabled": "Zakázáno",
"logRetention3Days": "3 dny",
"logRetention7Days": "7 dní",
"logRetention14Days": "14 dní",
"logRetention30Days": "30 dní",
"logRetention90Days": "90 dní",
"logRetentionForever": "Navždy",
"actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci",
"accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci",
"licenseRequiredToUse": "Pro použití této funkce je vyžadována licence pro podnikání.",
"certResolver": "Oddělovač certifikátů",
"certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.",
"selectCertResolver": "Vyberte řešič certifikátů",
"enterCustomResolver": "Zadejte vlastní rozlišovač",
"preferWildcardCert": "Preferovat Wildcard certifikát",
"unverified": "Neověřeno",
"domainSetting": "Nastavení domény",
"domainSettingDescription": "Konfigurace nastavení pro vaši doménu",
"preferWildcardCertDescription": "Pokus o vygenerování zástupného certifikátu (vyžaduje správně nakonfigurovaný certifikát).",
"recordName": "Název záznamu",
"auto": "Automaticky",
"TTL": "TTL",
"howToAddRecords": "Jak přidat záznamy",
"dnsRecord": "Záznamy DNS",
"required": "Požadováno",
"domainSettingsUpdated": "Nastavení domény bylo úspěšně aktualizováno",
"orgOrDomainIdMissing": "Chybí ID organizace nebo domény",
"loadingDNSRecords": "Načítání DNS záznamů...",
"olmUpdateAvailableInfo": "Je k dispozici aktualizovaná verze Olm. Pro nejlepší zážitek prosím aktualizujte na nejnovější verzi.",
"client": "Zákazník",
"proxyProtocol": "Nastavení proxy protokolu",
"proxyProtocolDescription": "Konfigurace Proxy protokolu pro zachování klientských IP adres pro služby TCP/UDP.",
"enableProxyProtocol": "Povolit Proxy protokol",
"proxyProtocolInfo": "Zachovat IP adresy klienta pro TCP/UDP backends",
"proxyProtocolVersion": "Verze proxy protokolu",
"version1": " Verze 1 (doporučeno)",
"version2": "Verze 2",
"versionDescription": "Verze 1 je textová a široce podporovaná. Verze 2 je binární a efektivnější, ale méně kompatibilní.",
"warning": "Varování",
"proxyProtocolWarning": "Vaše backend aplikace musí být nakonfigurována, aby mohla být přijata spojení s Proxy protokolem. Pokud vaše backend nepodporuje Proxy protokol, povolením tohoto protokolu dojde k přerušení všech připojení. Ujistěte se, že nastavíte svou backend a důvěřujte hlavičkám Proxy protokolu z Traefik.",
"restarting": "Restartování...",
"manual": "Ruční",
"messageSupport": "Podpora zpráv",
"supportNotAvailableTitle": "Podpora není k dispozici",
"supportNotAvailableDescription": "Podpora není momentálně k dispozici. Můžete poslat e-mail na support@pangolin.net.",
"supportRequestSentTitle": "Žádost o podporu odeslána",
"supportRequestSentDescription": "Vaše zpráva byla úspěšně odeslána.",
"supportRequestFailedTitle": "Nepodařilo se odeslat žádost",
"supportRequestFailedDescription": "Při odesílání vaší žádosti o podporu došlo k chybě.",
"supportSubjectRequired": "Předmět je povinný",
"supportSubjectMaxLength": "Předmět musí být 255 znaků nebo méně",
"supportMessageRequired": "Je vyžadována zpráva",
"supportReplyTo": "Odpovědět na",
"supportSubject": "Předmět",
"supportSubjectPlaceholder": "Zadejte předmět",
"supportMessage": "Zpráva",
"supportMessagePlaceholder": "Zadejte svou zprávu",
"supportSending": "Odesílání...",
"supportSend": "Poslat",
"supportMessageSent": "Zpráva odeslána!",
"supportWillContact": "Brzy budeme v kontaktu!",
"selectLogRetention": "Vyberte záznam",
"showColumns": "Zobrazit sloupce",
"hideColumns": "Skrýt sloupce",
"columnVisibility": "Viditelnost sloupců",
"toggleColumn": "Přepnout sloupec {columnName}",
"allColumns": "Všechny sloupce",
"defaultColumns": "Výchozí sloupce",
"customizeView": "Přizpůsobit zobrazení",
"viewOptions": "Možnosti zobrazení",
"selectAll": "Vybrat vše",
"selectNone": "Nevybrat žádný",
"selectedResources": "Vybrané zdroje",
"enableSelected": "Povolit vybrané",
"disableSelected": "Zakázat vybrané",
"checkSelectedStatus": "Zkontrolovat stav vybraného"
}

View File

@@ -10,7 +10,7 @@
"setupErrorIdentifier": "Organisations-ID ist bereits vergeben. Bitte wähle eine andere.",
"componentsErrorNoMemberCreate": "Du bist derzeit kein Mitglied einer Organisation. Erstelle eine Organisation, um zu starten.",
"componentsErrorNoMember": "Du bist aktuell kein Mitglied einer Organisation.",
"welcome": "Willkommen zu Pangolin",
"welcome": "Willkommen bei Pangolin!",
"welcomeTo": "Willkommen bei",
"componentsCreateOrg": "Erstelle eine Organisation",
"componentsMember": "Du bist Mitglied von {count, plural, =0 {keiner Organisation} one {einer Organisation} other {# Organisationen}}.",
@@ -27,7 +27,7 @@
"inviteLogInOtherUser": "Als anderer Benutzer anmelden",
"createAnAccount": "Konto erstellen",
"inviteNotAccepted": "Einladung nicht angenommen",
"authCreateAccount": "Erstellen ein Konto um loszulegen",
"authCreateAccount": "Erstelle ein Konto um loszulegen",
"authNoAccount": "Du besitzt noch kein Konto?",
"email": "E-Mail",
"password": "Passwort",
@@ -63,7 +63,7 @@
"siteLearnNewt": "Wie du Newt auf deinem System installieren kannst",
"siteSeeConfigOnce": "Du kannst die Konfiguration nur einmalig ansehen.",
"siteLoadWGConfig": "Lade WireGuard Konfiguration...",
"siteDocker": "Erweitern für Docker Details",
"siteDocker": "Docker-Details anzeigen",
"toggle": "Umschalten",
"dockerCompose": "Docker Compose",
"dockerRun": "Docker Run",
@@ -118,7 +118,7 @@
"tokenId": "Token-ID",
"requestHeades": "Anfrage-Header",
"queryParameter": "Abfrageparameter",
"importantNote": "Wichtige Notiz",
"importantNote": "Wichtiger Hinweis",
"shareImportantDescription": "Aus Sicherheitsgründen wird die Verwendung von Headern über Abfrageparameter empfohlen, wenn möglich, da Abfrageparameter in Server-Logs oder Browserverlauf protokolliert werden können.",
"token": "Token",
"shareTokenSecurety": "Halten Sie Ihr Zugangs-Token sicher. Teilen Sie es nicht in öffentlich zugänglichen Bereichen oder Client-seitigem Code.",
@@ -131,7 +131,7 @@
"expireIn": "Verfällt in",
"neverExpire": "Nie ablaufen",
"shareExpireDescription": "Ablaufzeit ist, wie lange der Link verwendet werden kann und bietet Zugriff auf die Ressource. Nach dieser Zeit wird der Link nicht mehr funktionieren und Benutzer, die diesen Link benutzt haben, verlieren den Zugriff auf die Ressource.",
"shareSeeOnce": "Sie können diese Linie nur sehen. Bitte kopieren Sie sie.",
"shareSeeOnce": "Sie können diesen Link nur einmal sehen. Bitte kopieren Sie ihn.",
"shareAccessHint": "Jeder mit diesem Link kann auf die Ressource zugreifen. Teilen Sie sie mit Vorsicht.",
"shareTokenUsage": "Zugriffstoken-Nutzung anzeigen",
"createLink": "Link erstellen",
@@ -156,7 +156,7 @@
"resourceQuestionRemove": "Sind Sie sicher, dass Sie die Ressource aus der Organisation entfernen möchten?",
"resourceHTTP": "HTTPS-Ressource",
"resourceHTTPDescription": "Proxy-Anfragen an Ihre App über HTTPS unter Verwendung einer Subdomain oder einer Basis-Domain.",
"resourceRaw": "Rohe TCP/UDP Ressource",
"resourceRaw": "Direkte TCP/UDP Ressource (raw)",
"resourceRawDescription": "Proxy-Anfragen an Ihre App über TCP/UDP mit einer Portnummer.",
"resourceCreate": "Ressource erstellen",
"resourceCreateDescription": "Folgen Sie den Schritten unten, um eine neue Ressource zu erstellen",
@@ -174,12 +174,12 @@
"resourceTypeDescription": "Legen Sie fest, wie Sie auf Ihre Ressource zugreifen möchten",
"resourceHTTPSSettings": "HTTPS-Einstellungen",
"resourceHTTPSSettingsDescription": "Konfigurieren Sie den Zugriff auf Ihre Ressource über HTTPS",
"domainType": "Domänentyp",
"domainType": "Domain-Typ",
"subdomain": "Subdomain",
"baseDomain": "Basisdomäne",
"subdomnainDescription": "Die Subdomäne, auf die Ihre Ressource zugegriffen werden soll.",
"baseDomain": "Basis-Domain",
"subdomnainDescription": "Die Subdomain, auf der Ihre Ressource erreichbar sein soll.",
"resourceRawSettings": "TCP/UDP Einstellungen",
"resourceRawSettingsDescription": "Konfigurieren Sie den Zugriff auf Ihre Ressource über TCP/UDP",
"resourceRawSettingsDescription": "Legen Sie fest, wie auf Ihre Ressource über TCP/UDP zugegriffen wird. Sie ordnen die Ressource einem Port auf dem Pangolin-Server zu, so dass Sie auf die Ressource von server-public-ip:mapped-port zugreifen können.",
"protocol": "Protokoll",
"protocolSelect": "Wählen Sie ein Protokoll",
"resourcePortNumber": "Portnummer",
@@ -188,7 +188,7 @@
"resourceConfig": "Konfiguration Snippets",
"resourceConfigDescription": "Kopieren und fügen Sie diese Konfigurations-Snippets ein, um Ihre TCP/UDP Ressource einzurichten",
"resourceAddEntrypoints": "Traefik: Einstiegspunkte hinzufügen",
"resourceExposePorts": "Gerbil: Ports im Docker Compose ausblenden",
"resourceExposePorts": "Gerbil: Ports im Docker Compose freigeben",
"resourceLearnRaw": "Lernen Sie, wie Sie TCP/UDP Ressourcen konfigurieren",
"resourceBack": "Zurück zu den Ressourcen",
"resourceGoTo": "Zu Ressource gehen",
@@ -461,8 +461,8 @@
"accessUsersRolesDescription": "Laden Sie Benutzer ein und fügen Sie sie zu Rollen hinzu, um den Zugriff auf Ihre Organisation zu verwalten",
"key": "Schlüssel",
"createdAt": "Erstellt am",
"proxyErrorInvalidHeader": "Ungültiger benutzerdefinierter Host-Header-Wert. Verwenden Sie das Domänennamensformat oder speichern Sie leer, um den benutzerdefinierten Host-Header zu deaktivieren.",
"proxyErrorTls": "Ungültiger TLS-Servername. Verwenden Sie das Domänennamensformat oder speichern Sie leer, um den TLS-Servernamen zu entfernen.",
"proxyErrorInvalidHeader": "Ungültiger benutzerdefinierter Host-Header-Wert. Verwenden Sie das Domain-Namensformat oder speichern Sie leer, um den benutzerdefinierten Host-Header zu deaktivieren.",
"proxyErrorTls": "Ungültiger TLS-Servername. Verwenden Sie das Domain-Namensformat oder speichern Sie leer, um den TLS-Servernamen zu entfernen.",
"proxyEnableSSL": "SSL aktivieren",
"proxyEnableSSLDescription": "Aktiviere SSL/TLS-Verschlüsselung für sichere HTTPS-Verbindungen zu deinen Zielen.",
"target": "Target",
@@ -526,7 +526,7 @@
"ipAddressErrorInvalidFormat": "Ungültiges IP-Adressformat",
"ipAddressErrorInvalidOctet": "Ungültiges IP-Adress-Oktett",
"path": "Pfad",
"matchPath": "Spielpfad",
"matchPath": "Match-Pfad",
"ipAddressRange": "IP-Bereich",
"rulesErrorFetch": "Fehler beim Abrufen der Regeln",
"rulesErrorFetchDescription": "Beim Abrufen der Regeln ist ein Fehler aufgetreten",
@@ -586,7 +586,7 @@
"none": "Keine",
"unknown": "Unbekannt",
"resources": "Ressourcen",
"resourcesDescription": "Ressourcen sind Proxies zu Anwendungen in Ihrem privaten Netzwerk. Erstellen Sie eine Ressource für jeden HTTP/HTTPS- oder rohen TCP/UDP-Dienst in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Site verbunden sein, um private, sichere Konnektivität über einen verschlüsselten WireGuard-Tunnel zu ermöglichen.",
"resourcesDescription": "Ressourcen sind Proxies zu Anwendungen in Ihrem privaten Netzwerk. Erstellen Sie eine Ressource für jeden HTTP/HTTPS- oder direkten TCP/UDP-Dienst in Ihrem privaten Netzwerk. Jede Ressource muss mit einer Site verbunden sein, um private, sichere Konnektivität über einen verschlüsselten WireGuard-Tunnel zu ermöglichen.",
"resourcesWireGuardConnect": "Sichere Verbindung mit WireGuard-Verschlüsselung",
"resourcesMultipleAuthenticationMethods": "Mehrere Authentifizierungsmethoden konfigurieren",
"resourcesUsersRolesAccess": "Benutzer- und rollenbasierte Zugriffskontrolle",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Prüfen Sie Ihre E-Mail für den Reset-Code.",
"passwordNew": "Neues Passwort",
"passwordNewConfirm": "Neues Passwort bestätigen",
"changePassword": "Passwort ändern",
"changePasswordDescription": "Passwort des Kontos aktualisieren",
"oldPassword": "Aktuelles Passwort",
"newPassword": "Neues Passwort",
"confirmNewPassword": "Neues Passwort bestätigen",
"changePasswordError": "Fehler beim Ändern des Passworts",
"changePasswordErrorDescription": "Fehler beim Ändern Ihres Passworts",
"changePasswordSuccess": "Passwort erfolgreich geändert",
"changePasswordSuccessDescription": "Ihr Passwort wurde erfolgreich aktualisiert",
"passwordExpiryRequired": "Passwortablauf erforderlich",
"passwordExpiryDescription": "Diese Organisation erfordert, dass Sie Ihr Passwort alle {maxDays} Tage ändern.",
"changePasswordNow": "Passwort jetzt ändern",
"pincodeAuth": "Authentifizierungscode",
"pincodeSubmit2": "Code absenden",
"passwordResetSubmit": "Zurücksetzung anfordern",
@@ -1004,7 +1016,7 @@
"actionUpdateUser": "Benutzer aktualisieren",
"actionGetUser": "Benutzer abrufen",
"actionGetOrgUser": "Organisationsbenutzer abrufen",
"actionListOrgDomains": "Organisationsdomänen auflisten",
"actionListOrgDomains": "Organisationsdomains auflisten",
"actionCreateSite": "Standort erstellen",
"actionDeleteSite": "Standort löschen",
"actionGetSite": "Standort abrufen",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Lizenz",
"sidebarClients": "Kunden",
"sidebarDomains": "Domänen",
"sidebarBluePrints": "Baupläne",
"blueprints": "Baupläne",
"blueprintsDescription": "Deklarative Konfigurationen anwenden und vorherige Abläufe anzeigen",
"blueprintAdd": "Blaupause hinzufügen",
"blueprintGoBack": "Alle Blaupausen ansehen",
"blueprintCreate": "Blaupause erstellen",
"blueprintCreateDescription2": "Folge den Schritten unten, um eine neue Blaupause zu erstellen und anzuwenden",
"blueprintDetails": "Blaupausendetails",
"blueprintDetailsDescription": "Siehe das Ergebnis der angewendeten Blaupause und alle aufgetretenen Fehler",
"blueprintInfo": "Blaupauseninformation",
"message": "Nachricht",
"blueprintContentsDescription": "Definieren Sie den YAML-Inhalt, der Ihre Infrastruktur beschreibt",
"blueprintErrorCreateDescription": "Fehler beim Anwenden der Blaupause",
"blueprintErrorCreate": "Fehler beim Erstellen der Blaupause",
"searchBlueprintProgress": "Blaupausen suchen...",
"appliedAt": "Angewandt am",
"source": "Quelle",
"contents": "Inhalt",
"parsedContents": "Analysierte Inhalte (Nur lesen)",
"enableDockerSocket": "Docker Blaupause aktivieren",
"enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blaupausenbeschriftungen. Der Socket-Pfad muss neu angegeben werden.",
"enableDockerSocketLink": "Mehr erfahren",
@@ -1158,15 +1189,15 @@
"containersIn": "Container in {siteName}",
"selectContainerDescription": "Wählen Sie einen Container, der als Hostname für dieses Ziel verwendet werden soll. Klicken Sie auf einen Port, um einen Port zu verwenden.",
"containerName": "Name",
"containerImage": "Bild",
"containerState": "Bundesland",
"containerImage": "Image",
"containerState": "Status",
"containerNetworks": "Netzwerke",
"containerHostnameIp": "Hostname/IP",
"containerLabels": "Etiketten",
"containerLabelsCount": "{count, plural, one {# Etikett} other {# Etiketten}}",
"containerLabelsTitle": "Container-Labels",
"containerLabelEmpty": "<leer>",
"containerPorts": "Häfen",
"containerPorts": "Ports",
"containerPortsMore": "+{count} mehr",
"containerActions": "Aktionen",
"select": "Auswählen",
@@ -1178,7 +1209,7 @@
"searchResultsCount": "{count, plural, one {# Ergebnis} other {# Ergebnisse}}",
"filters": "Filter",
"filterOptions": "Filteroptionen",
"filterPorts": "Häfen",
"filterPorts": "Ports",
"filterStopped": "Stoppt",
"clearAllFilters": "Alle Filter löschen",
"columns": "Spalten",
@@ -1247,13 +1278,13 @@
"settingsErrorUpdate": "Einstellungen konnten nicht aktualisiert werden",
"settingsErrorUpdateDescription": "Beim Aktualisieren der Einstellungen ist ein Fehler aufgetreten",
"sidebarCollapse": "Zusammenklappen",
"sidebarExpand": "Erweitern",
"sidebarExpand": "Aufklappen",
"newtUpdateAvailable": "Update verfügbar",
"newtUpdateAvailableInfo": "Eine neue Version von Newt ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für das beste Erlebnis.",
"domainPickerEnterDomain": "Domäne",
"domainPickerPlaceholder": "myapp.example.com",
"domainPickerDescription": "Geben Sie die vollständige Domäne der Ressource ein, um verfügbare Optionen zu sehen.",
"domainPickerDescriptionSaas": "Geben Sie eine vollständige Domäne, Subdomäne oder einfach einen Namen ein, um verfügbare Optionen zu sehen",
"domainPickerDescription": "Geben Sie die vollständige Domain der Ressource ein, um verfügbare Optionen zu sehen.",
"domainPickerDescriptionSaas": "Geben Sie eine vollständige Domain, Subdomain oder einfach einen Namen ein, um verfügbare Optionen zu sehen",
"domainPickerTabAll": "Alle",
"domainPickerTabOrganization": "Organisation",
"domainPickerTabProvided": "Bereitgestellt",
@@ -1278,7 +1309,7 @@
"billingDataUsage": "Datenverbrauch",
"billingOnlineTime": "Online-Zeit der Seite",
"billingUsers": "Aktive Benutzer",
"billingDomains": "Aktive Domänen",
"billingDomains": "Aktive Domains",
"billingRemoteExitNodes": "Aktive selbstgehostete Nodes",
"billingNoLimitConfigured": "Kein Limit konfiguriert",
"billingEstimatedPeriod": "Geschätzter Abrechnungszeitraum",
@@ -1306,7 +1337,7 @@
"billingDataUsageInfo": "Wenn Sie mit der Cloud verbunden sind, werden alle Daten über Ihre sicheren Tunnel belastet. Dies schließt eingehenden und ausgehenden Datenverkehr über alle Ihre Websites ein. Wenn Sie Ihr Limit erreichen, werden Ihre Seiten die Verbindung trennen, bis Sie Ihr Paket upgraden oder die Nutzung verringern. Daten werden nicht belastet, wenn Sie Knoten verwenden.",
"billingOnlineTimeInfo": "Sie werden belastet, abhängig davon, wie lange Ihre Seiten mit der Cloud verbunden bleiben. Zum Beispiel 44.640 Minuten entspricht einer Site, die 24 Stunden am Tag des Monats läuft. Wenn Sie Ihr Limit erreichen, werden Ihre Seiten die Verbindung trennen, bis Sie Ihr Paket upgraden oder die Nutzung verringern. Die Zeit wird nicht belastet, wenn Sie Knoten verwenden.",
"billingUsersInfo": "Ihnen wird für jeden Benutzer in Ihrer Organisation berechnet. Die Abrechnung erfolgt täglich basierend auf der Anzahl der aktiven Benutzerkonten in Ihrer Organisation.",
"billingDomainInfo": "Ihnen wird für jede Domäne in Ihrer Organisation berechnet. Die Abrechnung erfolgt täglich basierend auf der Anzahl der aktiven Domänenkonten in Ihrer Organisation.",
"billingDomainInfo": "Ihnen wird jede Domain in Ihrer Organisation berechnet. Die Abrechnung erfolgt täglich, basierend auf der Anzahl der aktiven Domain-Konten in Ihrer Organisation.",
"billingRemoteExitNodesInfo": "Ihnen wird für jeden verwalteten Node in Ihrer Organisation berechnet. Die Abrechnung erfolgt täglich basierend auf der Anzahl der aktiven verwalteten Nodes in Ihrer Organisation.",
"domainNotFound": "Domain nicht gefunden",
"domainNotFoundDescription": "Diese Ressource ist deaktiviert, weil die Domain nicht mehr in unserem System existiert. Bitte setzen Sie eine neue Domain für diese Ressource.",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Es gab ein Problem mit Ihrem Sicherheitsschlüssel. Bitte versuchen Sie es erneut.",
"twoFactorRequired": "Zur Registrierung eines Sicherheitsschlüssels ist eine Zwei-Faktor-Authentifizierung erforderlich.",
"twoFactor": "Zwei-Faktor-Authentifizierung",
"twoFactorAuthentication": "Zwei-Faktor-Authentifizierung",
"twoFactorDescription": "Diese Organisation erfordert Zwei-Faktor-Authentifizierung.",
"enableTwoFactor": "Zwei-Faktor-Authentifizierung aktivieren",
"organizationSecurityPolicy": "Sicherheitsrichtlinien der Organisation",
"organizationSecurityPolicyDescription": "Diese Organisation hat Sicherheitsanforderungen, die erfüllt werden müssen, bevor Sie darauf zugreifen können",
"securityRequirements": "Sicherheitsanforderungen",
"allRequirementsMet": "Alle Anforderungen wurden erfüllt",
"completeRequirementsToContinue": "Erfülle die folgenden Anforderungen, um weiterhin auf diese Organisation zuzugreifen",
"youCanNowAccessOrganization": "Sie können nun auf diese Organisation zugreifen",
"reauthenticationRequired": "Sitzungslänge",
"reauthenticationDescription": "Diese Organisation erfordert, dass Sie sich alle {maxDays} Tage anmelden.",
"reauthenticationDescriptionHours": "Diese Organisation erfordert, dass Sie sich alle {maxHours} Stunden einloggen.",
"reauthenticateNow": "Erneut anmelden",
"adminEnabled2FaOnYourAccount": "Ihr Administrator hat die Zwei-Faktor-Authentifizierung für {email} aktiviert. Bitte schließen Sie den Einrichtungsprozess ab, um fortzufahren.",
"securityKeyAdd": "Sicherheitsschlüssel hinzufügen",
"securityKeyRegisterTitle": "Neuen Sicherheitsschlüssel registrieren",
@@ -1381,7 +1425,7 @@
"olmTunnel": "Olm-Tunnel",
"olmTunnelDescription": "Nutzen Sie Olm für die Kundenverbindung",
"errorCreatingClient": "Fehler beim Erstellen des Clients",
"clientDefaultsNotFound": "Kundenvorgaben nicht gefunden",
"clientDefaultsNotFound": "Standardeinstellungen des Clients nicht gefunden",
"createClient": "Client erstellen",
"createClientDescription": "Erstellen Sie einen neuen Client für die Verbindung zu Ihren Standorten.",
"seeAllClients": "Alle Clients anzeigen",
@@ -1458,13 +1502,13 @@
"httpMethod": "HTTP-Methode",
"selectHttpMethod": "HTTP-Methode auswählen",
"domainPickerSubdomainLabel": "Subdomain",
"domainPickerBaseDomainLabel": "Basisdomäne",
"domainPickerBaseDomainLabel": "Basisdomain",
"domainPickerSearchDomains": "Domains suchen...",
"domainPickerNoDomainsFound": "Keine Domains gefunden",
"domainPickerLoadingDomains": "Domains werden geladen...",
"domainPickerSelectBaseDomain": "Basisdomäne auswählen...",
"domainPickerSelectBaseDomain": "Basisdomain auswählen...",
"domainPickerNotAvailableForCname": "Für CNAME-Domains nicht verfügbar",
"domainPickerEnterSubdomainOrLeaveBlank": "Geben Sie eine Subdomain ein oder lassen Sie das Feld leer, um die Basisdomäne zu verwenden.",
"domainPickerEnterSubdomainOrLeaveBlank": "Geben Sie eine Subdomain ein oder lassen Sie das Feld leer, um die Basisdomain zu verwenden.",
"domainPickerEnterSubdomainToSearch": "Geben Sie eine Subdomain ein, um verfügbare freie Domains zu suchen und auszuwählen.",
"domainPickerFreeDomains": "Freie Domains",
"domainPickerSearchForAvailableDomains": "Verfügbare Domains suchen",
@@ -1479,7 +1523,7 @@
"resourcesTableNoInternalResourcesFound": "Keine internen Ressourcen gefunden.",
"resourcesTableDestination": "Ziel",
"resourcesTableTheseResourcesForUseWith": "Diese Ressourcen sind zur Verwendung mit",
"resourcesTableClients": "Kunden",
"resourcesTableClients": "Clients",
"resourcesTableAndOnlyAccessibleInternally": "und sind nur intern zugänglich, wenn mit einem Client verbunden.",
"editInternalResourceDialogEditClientResource": "Client-Ressource bearbeiten",
"editInternalResourceDialogUpdateResourceProperties": "Aktualisieren Sie die Ressourceneigenschaften und die Zielkonfiguration für {resourceName}.",
@@ -1552,7 +1596,7 @@
"autoLoginErrorNoRedirectUrl": "Keine Weiterleitungs-URL vom Identitätsanbieter erhalten.",
"autoLoginErrorGeneratingUrl": "Fehler beim Generieren der Authentifizierungs-URL.",
"remoteExitNodeManageRemoteExitNodes": "Entfernte Knoten",
"remoteExitNodeDescription": "Self-Hoster einen oder mehrere entfernte Knoten, um Ihre Netzwerkverbindung zu erweitern und die Abhängigkeit von der Cloud zu verringern",
"remoteExitNodeDescription": "Self-Hosten Sie einen oder mehrere entfernte Knoten, um Ihr Netzwerk zu erweitern und die Abhängigkeit von der Cloud zu verringern",
"remoteExitNodes": "Knoten",
"searchRemoteExitNodes": "Knoten suchen...",
"remoteExitNodeAdd": "Knoten hinzufügen",
@@ -1564,7 +1608,7 @@
"sidebarRemoteExitNodes": "Entfernte Knoten",
"remoteExitNodeCreate": {
"title": "Knoten erstellen",
"description": "Erstellen Sie einen neuen Knoten, um Ihre Netzwerkverbindung zu erweitern",
"description": "Erstellen Sie einen neuen Knoten, um Ihr Netzwerk zu erweitern",
"viewAllButton": "Alle Knoten anzeigen",
"strategy": {
"title": "Erstellungsstrategie",
@@ -1672,7 +1716,7 @@
"idpAzureConfigurationDescription": "Konfigurieren Sie Ihre Azure Entra ID OAuth2 Zugangsdaten",
"idpTenantId": "Mandanten-ID",
"idpTenantIdPlaceholder": "deine Mandant-ID",
"idpAzureTenantIdDescription": "Ihre Azure Mieter-ID (gefunden in Azure Active Directory Übersicht)",
"idpAzureTenantIdDescription": "Ihre Azure Tenant-ID (gefunden in Azure Active Directory Übersicht)",
"idpAzureClientIdDescription": "Ihre Azure App Registration Client ID",
"idpAzureClientSecretDescription": "Ihr Azure App Registration Client Secret",
"idpGoogleTitle": "Google",
@@ -1691,7 +1735,7 @@
"authPage": "Auth Seite",
"authPageDescription": "Konfigurieren Sie die Auth-Seite für Ihre Organisation",
"authPageDomain": "Domain der Auth Seite",
"noDomainSet": "Keine Domäne gesetzt",
"noDomainSet": "Keine Domain gesetzt",
"changeDomain": "Domain ändern",
"selectDomain": "Domain auswählen",
"restartCertificate": "Zertifikat neu starten",
@@ -1707,7 +1751,7 @@
"domainPickerUnverified": "Nicht verifiziert",
"domainPickerInvalidSubdomainStructure": "Diese Subdomain enthält ungültige Zeichen oder Struktur. Sie wird beim Speichern automatisch bereinigt.",
"domainPickerError": "Fehler",
"domainPickerErrorLoadDomains": "Fehler beim Laden der Organisations-Domänen",
"domainPickerErrorLoadDomains": "Fehler beim Laden der Organisations-Domains",
"domainPickerErrorCheckAvailability": "Fehler beim Prüfen der Domain-Verfügbarkeit",
"domainPickerInvalidSubdomain": "Ungültige Subdomain",
"domainPickerInvalidSubdomainRemoved": "Die Eingabe \"{sub}\" wurde entfernt, weil sie nicht gültig ist.",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Datei bearbeiten: docker-compose.yml",
"emailVerificationRequired": "E-Mail-Verifizierung ist erforderlich. Bitte melden Sie sich erneut über {dashboardUrl}/auth/login an. Kommen Sie dann wieder hierher.",
"twoFactorSetupRequired": "Die Zwei-Faktor-Authentifizierung ist erforderlich. Bitte melden Sie sich erneut über {dashboardUrl}/auth/login an. Dann kommen Sie hierher zurück.",
"additionalSecurityRequired": "Zusätzliche Sicherheit erforderlich",
"organizationRequiresAdditionalSteps": "Diese Organisation erfordert zusätzliche Sicherheitsschritte, bevor Sie auf Ressourcen zugreifen können.",
"completeTheseSteps": "Schließe diese Schritte ab",
"enableTwoFactorAuthentication": "Zwei-Faktor-Authentifizierung aktivieren",
"completeSecuritySteps": "Schließe Sicherheitsschritte ab",
"securitySettings": "Sicherheitseinstellungen",
"securitySettingsDescription": "Konfigurieren Sie Sicherheitsrichtlinien für Ihre Organisation",
"requireTwoFactorForAllUsers": "Zwei-Faktor-Authentifizierung für alle Benutzer erforderlich",
"requireTwoFactorDescription": "Wenn aktiviert, müssen alle internen Benutzer in dieser Organisation die Zwei-Faktor-Authentifizierung aktiviert haben, um auf die Organisation zuzugreifen.",
"requireTwoFactorDisabledDescription": "Diese Funktion erfordert eine gültige Lizenz (Unternehmen) oder ein aktives Abonnement (SaaS)",
"requireTwoFactorCannotEnableDescription": "Sie müssen die Zwei-Faktor-Authentifizierung für Ihr Konto aktivieren, bevor Sie es für alle Benutzer erzwingen können",
"maxSessionLength": "Maximale Sitzungslänge",
"maxSessionLengthDescription": "Legen Sie die maximale Dauer für Benutzersitzungen fest. Nach dieser Zeit müssen Benutzer erneut authentifizieren.",
"maxSessionLengthDisabledDescription": "Diese Funktion erfordert eine gültige Lizenz (Unternehmen) oder ein aktives Abonnement (SaaS)",
"selectSessionLength": "Sitzungslänge auswählen",
"unenforced": "Unerzwungen",
"1Hour": "1 Stunde",
"3Hours": "3 Stunden",
"6Hours": "6 Stunden",
"12Hours": "12 Stunden",
"1DaySession": "1 Tag",
"3Days": "3 Tage",
"7Days": "7 Tage",
"14Days": "14 Tage",
"30DaysSession": "30 Tage",
"90DaysSession": "90 Tage",
"180DaysSession": "180 Tage",
"passwordExpiryDays": "Passwortablauf",
"editPasswordExpiryDescription": "Legen Sie die Anzahl der Tage fest, bevor Benutzer ihr Passwort ändern müssen.",
"selectPasswordExpiry": "Ablauf des Passworts auswählen",
"30Days": "30 Tage",
"1Day": "1 Tag",
"60Days": "60 Tage",
"90Days": "90 Tage",
"180Days": "180 Tage",
"1Year": "1 Jahr",
"subscriptionBadge": "Abonnement erforderlich",
"securityPolicyChangeWarning": "Sicherheitsrichtlinienänderungs-Warnung",
"securityPolicyChangeDescription": "Sie sind dabei, die Sicherheitseinstellungen zu ändern. Nach dem Speichern müssen Sie sich erneut authentifizieren, um diesen Richtlinien-Aktualisierungen nachzukommen. Alle Benutzer, die nicht konform sind, müssen sich ebenfalls neu authentifizieren.",
"securityPolicyChangeConfirmMessage": "Ich bestätige",
"securityPolicyChangeWarningText": "Dies betrifft alle Benutzer in der Organisation",
"authPageErrorUpdateMessage": "Beim Aktualisieren der Auth-Seiten-Einstellungen ist ein Fehler aufgetreten",
"authPageErrorUpdate": "Auth Seite kann nicht aktualisiert werden",
"authPageUpdated": "Auth-Seite erfolgreich aktualisiert",
"healthCheckNotAvailable": "Lokal",
"rewritePath": "Pfad neu schreiben",
@@ -1753,7 +1839,7 @@
"enterpriseEdition": "Enterprise Edition",
"unlicensed": "Nicht lizenziert",
"beta": "Beta",
"manageClients": "Kunden verwalten",
"manageClients": "Clients verwalten",
"manageClientsDescription": "Clients sind Geräte, die sich mit Ihren Websites verbinden können",
"licenseTableValidUntil": "Gültig bis",
"saasLicenseKeysSettingsTitle": "Enterprise-Lizenzen",
@@ -1885,11 +1971,129 @@
"pathRewritePrefix": "Präfix",
"pathRewriteExact": "Exakt",
"pathRewriteRegex": "Regex",
"pathRewriteStrip": "Streifen",
"pathRewriteStripLabel": "streifen",
"pathRewriteStrip": "Entfernen",
"pathRewriteStripLabel": "entfernen",
"sidebarEnableEnterpriseLicense": "Enterprise-Lizenz aktivieren",
"cannotbeUndone": "Dies kann nicht rückgängig gemacht werden.",
"toConfirm": "bestätigen",
"deleteClientQuestion": "Sind Sie sicher, dass Sie den Client von der Website und der Organisation entfernen möchten?",
"clientMessageRemove": "Nach dem Entfernen kann sich der Client nicht mehr mit der Website verbinden."
}
"clientMessageRemove": "Nach dem Entfernen kann sich der Client nicht mehr mit der Website verbinden.",
"sidebarLogs": "Logs",
"request": "Anfrage",
"logs": "Logs",
"logsSettingsDescription": "Aus dieser Orginisierung gesammelte Logs überwachen",
"searchLogs": "Logs suchen...",
"action": "Aktion",
"actor": "Akteur",
"timestamp": "Zeitstempel",
"accessLogs": "Zugriffsprotokolle",
"exportCsv": "CSV exportieren",
"actorId": "Akteur-ID",
"allowedByRule": "Erlaubt durch Regel",
"allowedNoAuth": "Keine Auth erlaubt",
"validAccessToken": "Gültiges Zugriffstoken",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Gültiges Passwort",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Ressource blockiert",
"droppedByRule": "Abgelegt durch Regel",
"noSessions": "Keine Sitzungen",
"temporaryRequestToken": "Temporäres Anfrage-Token",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Grund",
"requestLogs": "Logs anfordern",
"host": "Host",
"location": "Standort",
"actionLogs": "Aktionsprotokolle",
"sidebarLogsRequest": "Logs anfordern",
"sidebarLogsAccess": "Zugriffsprotokolle",
"sidebarLogsAction": "Aktionsprotokolle",
"logRetention": "Log-Speicherung",
"logRetentionDescription": "Verwalten, wie lange verschiedene Logs für diese Organisation gespeichert werden oder deaktivieren",
"requestLogsDescription": "Detaillierte Request-Logs für Ressourcen in dieser Organisation anzeigen",
"logRetentionRequestLabel": "Log-Speicherung anfordern",
"logRetentionRequestDescription": "Wie lange sollen Request-Logs gespeichert werden",
"logRetentionAccessLabel": "Zugriffsprotokoll-Speicherung",
"logRetentionAccessDescription": "Wie lange Zugriffsprotokolle beibehalten werden sollen",
"logRetentionActionLabel": "Aktionsprotokoll-Speicherung",
"logRetentionActionDescription": "Dauer des Action-Logs",
"logRetentionDisabled": "Deaktiviert",
"logRetention3Days": "3 Tage",
"logRetention7Days": "7 Tage",
"logRetention14Days": "14 Tage",
"logRetention30Days": "30 Tage",
"logRetention90Days": "90 Tage",
"logRetentionForever": "Für immer",
"actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen",
"accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen",
"licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise-Lizenz erforderlich.",
"certResolver": "Zertifikatsauflöser",
"certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.",
"selectCertResolver": "Zertifikatsauflöser auswählen",
"enterCustomResolver": "Eigenen Auflöser eingeben",
"preferWildcardCert": "Wildcard-Zertifikat bevorzugen",
"unverified": "Nicht verifiziert",
"domainSetting": "Domänen-Einstellungen",
"domainSettingDescription": "Einstellungen für Ihre Domain konfigurieren",
"preferWildcardCertDescription": "Versuch ein Platzhalterzertifikat zu generieren (erfordert einen richtig konfigurierten Zertifikatslöser).",
"recordName": "Name des Datensatzes",
"auto": "Auto",
"TTL": "TTL",
"howToAddRecords": "So kann man Datensätze hinzufügen",
"dnsRecord": "DNS-Einträge",
"required": "Benötigt",
"domainSettingsUpdated": "Domain-Einstellungen erfolgreich aktualisiert",
"orgOrDomainIdMissing": "Organisation oder Domänen-ID fehlt",
"loadingDNSRecords": "Lade DNS-Einträge...",
"olmUpdateAvailableInfo": "Eine aktualisierte Version von Olm ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für die beste Erfahrung.",
"client": "Kunde",
"proxyProtocol": "Proxy-Protokoll-Einstellungen",
"proxyProtocolDescription": "Konfigurieren Sie das Proxy-Protokoll, um die IP-Adressen des Clients für TCP/UDP-Dienste zu erhalten.",
"enableProxyProtocol": "Proxy-Protokoll aktivieren",
"proxyProtocolInfo": "Client-IP-Adressen für TCP/UDP Backends beibehalten",
"proxyProtocolVersion": "Proxy-Protokollversion",
"version1": " Version 1 (empfohlen)",
"version2": "Version 2",
"versionDescription": "Die Version 1 ist textbasiert und unterstützt die Version 2, ist binär und effizienter, aber weniger kompatibel.",
"warning": "Warnung",
"proxyProtocolWarning": "Ihre Backend-Anwendung muss so konfiguriert sein, dass sie Proxy-Protokoll-Verbindungen akzeptiert. Wenn Ihr Backend das Proxy-Protokoll nicht unterstützt, wird die Aktivierung dieser Option alle Verbindungen zerstören. Stellen Sie sicher, dass Sie Ihr Backend so konfigurieren, dass es Proxy-Protokoll-Header von Traefik vertraut.",
"restarting": "Neustarten...",
"manual": "Manuell",
"messageSupport": "Nachrichtenunterstützung",
"supportNotAvailableTitle": "Support nicht verfügbar",
"supportNotAvailableDescription": "Support ist momentan nicht verfügbar. Sie können eine E-Mail an support@pangolin.net senden.",
"supportRequestSentTitle": "Supportanfrage gesendet",
"supportRequestSentDescription": "Ihre Nachricht wurde erfolgreich gesendet.",
"supportRequestFailedTitle": "Senden der Anfrage fehlgeschlagen",
"supportRequestFailedDescription": "Beim Senden Ihrer Supportanfrage ist ein Fehler aufgetreten.",
"supportSubjectRequired": "Betreff ist erforderlich",
"supportSubjectMaxLength": "Betreff muss mindestens 255 Zeichen lang sein",
"supportMessageRequired": "Nachricht ist erforderlich",
"supportReplyTo": "Antwort an",
"supportSubject": "Betreff",
"supportSubjectPlaceholder": "Betreff eingeben",
"supportMessage": "Nachricht",
"supportMessagePlaceholder": "Geben Sie Ihre Nachricht ein",
"supportSending": "Senden...",
"supportSend": "Senden",
"supportMessageSent": "Nachricht gesendet!",
"supportWillContact": "Wir werden in Kürze kontaktieren!",
"selectLogRetention": "Log-Speicherung auswählen",
"showColumns": "Spalten anzeigen",
"hideColumns": "Spalten ausblenden",
"columnVisibility": "Spaltensichtbarkeit",
"toggleColumn": "{columnName} Spalte umschalten",
"allColumns": "Alle Spalten",
"defaultColumns": "Standardspalten",
"customizeView": "Ansicht anpassen",
"viewOptions": "Optionen anzeigen",
"selectAll": "Alle auswählen",
"selectNone": "Nichts auswählen",
"selectedResources": "Ausgewählte Ressourcen",
"enableSelected": "Ausgewählte aktivieren",
"disableSelected": "Ausgewählte deaktivieren",
"checkSelectedStatus": "Status der Auswahl überprüfen"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Expire In",
"neverExpire": "Never expire",
"shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.",
"shareSeeOnce": "You will only be able to see this linkonce. Make sure to copy it.",
"shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.",
"shareAccessHint": "Anyone with this link can access the resource. Share it with care.",
"shareTokenUsage": "See Access Token Usage",
"createLink": "Create Link",
@@ -179,7 +179,7 @@
"baseDomain": "Base Domain",
"subdomnainDescription": "The subdomain where your resource will be accessible.",
"resourceRawSettings": "TCP/UDP Settings",
"resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP",
"resourceRawSettingsDescription": "Configure how your resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from server-public-ip:mapped-port.",
"protocol": "Protocol",
"protocolSelect": "Select a protocol",
"resourcePortNumber": "Port Number",
@@ -207,7 +207,7 @@
"alwaysAllow": "Always Allow",
"alwaysDeny": "Always Deny",
"passToAuth": "Pass to Auth",
"orgSettingsDescription": "Configure your organization's general settings",
"orgSettingsDescription": "Configure your organization's settings",
"orgGeneralSettings": "Organization Settings",
"orgGeneralSettingsDescription": "Manage your organization details and configuration",
"saveGeneralSettings": "Save General Settings",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Check your email for the reset code.",
"passwordNew": "New Password",
"passwordNewConfirm": "Confirm New Password",
"changePassword": "Change Password",
"changePasswordDescription": "Update your account password",
"oldPassword": "Current Password",
"newPassword": "New Password",
"confirmNewPassword": "Confirm New Password",
"changePasswordError": "Failed to change password",
"changePasswordErrorDescription": "An error occurred while changing your password",
"changePasswordSuccess": "Password Changed Successfully",
"changePasswordSuccessDescription": "Your password has been updated successfully",
"passwordExpiryRequired": "Password Expiry Required",
"passwordExpiryDescription": "This organization requires you to change your password every {maxDays} days.",
"changePasswordNow": "Change Password Now",
"pincodeAuth": "Authenticator Code",
"pincodeSubmit2": "Submit Code",
"passwordResetSubmit": "Request Reset",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "License",
"sidebarClients": "Clients",
"sidebarDomains": "Domains",
"sidebarBluePrints": "Blueprints",
"blueprints": "Blueprints",
"blueprintsDescription": "Apply declarative configurations and view previous runs",
"blueprintAdd": "Add Blueprint",
"blueprintGoBack": "See all Blueprints",
"blueprintCreate": "Create Blueprint",
"blueprintCreateDescription2": "Follow the steps below to create and apply a new blueprint",
"blueprintDetails": "Blueprint Details",
"blueprintDetailsDescription": "See the result of the applied blueprint and any errors that occurred",
"blueprintInfo": "Blueprint Information",
"message": "Message",
"blueprintContentsDescription": "Define the YAML content describing your infrastructure",
"blueprintErrorCreateDescription": "An error occurred when applying the blueprint",
"blueprintErrorCreate": "Error creating blueprint",
"searchBlueprintProgress": "Search blueprints...",
"appliedAt": "Applied At",
"source": "Source",
"contents": "Contents",
"parsedContents": "Parsed Contents (Read Only)",
"enableDockerSocket": "Enable Docker Blueprint",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.",
"enableDockerSocketLink": "Learn More",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "There was a problem using your security key. Please try again.",
"twoFactorRequired": "Two-factor authentication is required to register a security key.",
"twoFactor": "Two-Factor Authentication",
"twoFactorAuthentication": "Two-Factor Authentication",
"twoFactorDescription": "This organization requires two-factor authentication.",
"enableTwoFactor": "Enable Two-Factor Authentication",
"organizationSecurityPolicy": "Organization Security Policy",
"organizationSecurityPolicyDescription": "This organization has security requirements that must be met before you can access it",
"securityRequirements": "Security Requirements",
"allRequirementsMet": "All requirements have been met",
"completeRequirementsToContinue": "Complete the requirements below to continue accessing this organization",
"youCanNowAccessOrganization": "You can now access this organization",
"reauthenticationRequired": "Session Length",
"reauthenticationDescription": "This organization requires you to log in every {maxDays} days.",
"reauthenticationDescriptionHours": "This organization requires you to log in every {maxHours} hours.",
"reauthenticateNow": "Log In Again",
"adminEnabled2FaOnYourAccount": "Your administrator has enabled two-factor authentication for {email}. Please complete the setup process to continue.",
"securityKeyAdd": "Add Security Key",
"securityKeyRegisterTitle": "Register New Security Key",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Edit file: docker-compose.yml",
"emailVerificationRequired": "Email verification is required. Please log in again via {dashboardUrl}/auth/login complete this step. Then, come back here.",
"twoFactorSetupRequired": "Two-factor authentication setup is required. Please log in again via {dashboardUrl}/auth/login complete this step. Then, come back here.",
"additionalSecurityRequired": "Additional Security Required",
"organizationRequiresAdditionalSteps": "This organization requires additional security steps before you can access resources.",
"completeTheseSteps": "Complete these steps",
"enableTwoFactorAuthentication": "Enable two-factor authentication",
"completeSecuritySteps": "Complete Security Steps",
"securitySettings": "Security Settings",
"securitySettingsDescription": "Configure security policies for your organization",
"requireTwoFactorForAllUsers": "Require Two-Factor Authentication for All Users",
"requireTwoFactorDescription": "When enabled, all internal users in this organization must have two-factor authentication enabled to access the organization.",
"requireTwoFactorDisabledDescription": "This feature requires a valid license (Enterprise) or active subscription (SaaS)",
"requireTwoFactorCannotEnableDescription": "You must enable two-factor authentication for your account before enforcing it for all users",
"maxSessionLength": "Maximum Session Length",
"maxSessionLengthDescription": "Set the maximum duration for user sessions. After this time, users will need to re-authenticate.",
"maxSessionLengthDisabledDescription": "This feature requires a valid license (Enterprise) or active subscription (SaaS)",
"selectSessionLength": "Select session length",
"unenforced": "Unenforced",
"1Hour": "1 hour",
"3Hours": "3 hours",
"6Hours": "6 hours",
"12Hours": "12 hours",
"1DaySession": "1 day",
"3Days": "3 days",
"7Days": "7 days",
"14Days": "14 days",
"30DaysSession": "30 days",
"90DaysSession": "90 days",
"180DaysSession": "180 days",
"passwordExpiryDays": "Password Expiry",
"editPasswordExpiryDescription": "Set the number of days before users are required to change their password.",
"selectPasswordExpiry": "Select password expiry",
"30Days": "30 days",
"1Day": "1 day",
"60Days": "60 days",
"90Days": "90 days",
"180Days": "180 days",
"1Year": "1 year",
"subscriptionBadge": "Subscription Required",
"securityPolicyChangeWarning": "Security Policy Change Warning",
"securityPolicyChangeDescription": "You are about to change security policy settings. After saving, you may need to reauthenticate to comply with these policy updates. All users who are not compliant will also need to reauthenticate.",
"securityPolicyChangeConfirmMessage": "I confirm",
"securityPolicyChangeWarningText": "This will affect all users in the organization",
"authPageErrorUpdateMessage": "An error occurred while updating the auth page settings",
"authPageErrorUpdate": "Unable to update auth page",
"authPageUpdated": "Auth page updated successfully",
"healthCheckNotAvailable": "Local",
"rewritePath": "Rewrite Path",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "This can not be undone.",
"toConfirm": "to confirm",
"deleteClientQuestion": "Are you sure you want to remove the client from the site and organization?",
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site."
}
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site.",
"sidebarLogs": "Logs",
"request": "Request",
"logs": "Logs",
"logsSettingsDescription": "Monitor logs collected from this orginization",
"searchLogs": "Search logs...",
"action": "Action",
"actor": "Actor",
"timestamp": "Timestamp",
"accessLogs": "Access Logs",
"exportCsv": "Export CSV",
"actorId": "Actor ID",
"allowedByRule": "Allowed by Rule",
"allowedNoAuth": "Allowed No Auth",
"validAccessToken": "Valid Access Token",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Valid Password",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Resource Blocked",
"droppedByRule": "Dropped by Rule",
"noSessions": "No Sessions",
"temporaryRequestToken": "Temporary Request Token",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Reason",
"requestLogs": "Request Logs",
"host": "Host",
"location": "Location",
"actionLogs": "Action Logs",
"sidebarLogsRequest": "Request Logs",
"sidebarLogsAccess": "Access Logs",
"sidebarLogsAction": "Action Logs",
"logRetention": "Log Retention",
"logRetentionDescription": "Manage how long different types of logs are retained for this organization or disable them",
"requestLogsDescription": "View detailed request logs for resources in this organization",
"logRetentionRequestLabel": "Request Log Retention",
"logRetentionRequestDescription": "How long to retain request logs",
"logRetentionAccessLabel": "Access Log Retention",
"logRetentionAccessDescription": "How long to retain access logs",
"logRetentionActionLabel": "Action Log Retention",
"logRetentionActionDescription": "How long to retain action logs",
"logRetentionDisabled": "Disabled",
"logRetention3Days": "3 days",
"logRetention7Days": "7 days",
"logRetention14Days": "14 days",
"logRetention30Days": "30 days",
"logRetention90Days": "90 days",
"logRetentionForever": "Forever",
"actionLogsDescription": "View a history of actions performed in this organization",
"accessLogsDescription": "View access auth requests for resources in this organization",
"licenseRequiredToUse": "An Enterprise license is required to use this feature.",
"certResolver": "Certificate Resolver",
"certResolverDescription": "Select the certificate resolver to use for this resource.",
"selectCertResolver": "Select Certificate Resolver",
"enterCustomResolver": "Enter Custom Resolver",
"preferWildcardCert": "Prefer Wildcard Certificate",
"unverified": "Unverified",
"domainSetting": "Domain Settings",
"domainSettingDescription": "Configure settings for your domain",
"preferWildcardCertDescription": "Attempt to generate a wildcard certificate (require a properly configured certificate resolver).",
"recordName": "Record Name",
"auto": "Auto",
"TTL": "TTL",
"howToAddRecords": "How to Add Records",
"dnsRecord": "DNS Records",
"required": "Required",
"domainSettingsUpdated": "Domain settings updated successfully",
"orgOrDomainIdMissing": "Organization or Domain ID is missing",
"loadingDNSRecords": "Loading DNS records...",
"olmUpdateAvailableInfo": "An updated version of Olm is available. Please update to the latest version for the best experience.",
"client": "Client",
"proxyProtocol": "Proxy Protocol Settings",
"proxyProtocolDescription": "Configure Proxy Protocol to preserve client IP addresses for TCP/UDP services.",
"enableProxyProtocol": "Enable Proxy Protocol",
"proxyProtocolInfo": "Preserve client IP addresses for TCP/UDP backends",
"proxyProtocolVersion": "Proxy Protocol Version",
"version1": " Version 1 (Recommended)",
"version2": "Version 2",
"versionDescription": "Version 1 is text-based and widely supported. Version 2 is binary and more efficient but less compatible. Make sure servers transport is added to dynamic config.",
"warning": "Warning",
"proxyProtocolWarning": "Your backend application must be configured to accept Proxy Protocol connections. If your backend doesn't support Proxy Protocol, enabling this will break all connections so only enable this if you know what you're doing. Make sure to configure your backend to trust Proxy Protocol headers from Traefik.",
"restarting": "Restarting...",
"manual": "Manual",
"messageSupport": "Message Support",
"supportNotAvailableTitle": "Support Not Available",
"supportNotAvailableDescription": "Support is not available right now. You can send an email to support@pangolin.net.",
"supportRequestSentTitle": "Support Request Sent",
"supportRequestSentDescription": "Your message has been sent successfully.",
"supportRequestFailedTitle": "Failed to Send Request",
"supportRequestFailedDescription": "An error occurred while sending your support request.",
"supportSubjectRequired": "Subject is required",
"supportSubjectMaxLength": "Subject must be 255 characters or less",
"supportMessageRequired": "Message is required",
"supportReplyTo": "Reply To",
"supportSubject": "Subject",
"supportSubjectPlaceholder": "Enter subject",
"supportMessage": "Message",
"supportMessagePlaceholder": "Enter your message",
"supportSending": "Sending...",
"supportSend": "Send",
"supportMessageSent": "Message Sent!",
"supportWillContact": "We'll be in touch shortly!",
"selectLogRetention": "Select log retention",
"showColumns": "Show Columns",
"hideColumns": "Hide Columns",
"columnVisibility": "Column Visibility",
"toggleColumn": "Toggle {columnName} column",
"allColumns": "All Columns",
"defaultColumns": "Default Columns",
"customizeView": "Customize View",
"viewOptions": "View Options",
"selectAll": "Select All",
"selectNone": "Select None",
"selectedResources": "Selected Resources",
"enableSelected": "Enable Selected",
"disableSelected": "Disable Selected",
"checkSelectedStatus": "Check Status of Selected"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Caduca en",
"neverExpire": "Nunca expirar",
"shareExpireDescription": "El tiempo de caducidad es cuánto tiempo el enlace será utilizable y proporcionará acceso al recurso. Después de este tiempo, el enlace ya no funcionará, y los usuarios que usaron este enlace perderán el acceso al recurso.",
"shareSeeOnce": "Sólo podrá ver este enlace una vez. Asegúrese de copiarlo.",
"shareSeeOnce": "Sólo podrás ver este enlace una vez. Asegúrate de copiarlo.",
"shareAccessHint": "Cualquiera con este enlace puede acceder al recurso. Compártelo con cuidado.",
"shareTokenUsage": "Ver Uso de Token de Acceso",
"createLink": "Crear enlace",
@@ -179,7 +179,7 @@
"baseDomain": "Dominio base",
"subdomnainDescription": "El subdominio al que su recurso será accesible.",
"resourceRawSettings": "Configuración TCP/UDP",
"resourceRawSettingsDescription": "Configurar cómo se accederá a su recurso a través de TCP/UDP",
"resourceRawSettingsDescription": "Configure cómo se accederá a su recurso a través de TCP/UDP. Mapeas el recurso a un puerto en el servidor Pangolin host, así puedes acceder al recurso desde el servidor-public-ip:mapped-port.",
"protocol": "Protocolo",
"protocolSelect": "Seleccionar un protocolo",
"resourcePortNumber": "Número de puerto",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Revisa tu correo electrónico para ver el código de restablecimiento.",
"passwordNew": "Nueva contraseña",
"passwordNewConfirm": "Confirmar nueva contraseña",
"changePassword": "Cambiar Contraseña",
"changePasswordDescription": "Actualizar la contraseña de tu cuenta",
"oldPassword": "Contraseña Actual",
"newPassword": "Nueva Contraseña",
"confirmNewPassword": "Confirme Nueva Contraseña",
"changePasswordError": "Error al cambiar la contraseña",
"changePasswordErrorDescription": "Se ha producido un error al cambiar la contraseña",
"changePasswordSuccess": "La contraseña ha sido cambiada correctamente",
"changePasswordSuccessDescription": "Su contraseña ha sido actualizada correctamente",
"passwordExpiryRequired": "Contraseña con caducidad requerida",
"passwordExpiryDescription": "Esta organización requiere que cambies tu contraseña cada {maxDays} días.",
"changePasswordNow": "Cambiar Contraseña Ahora",
"pincodeAuth": "Código de autenticación",
"pincodeSubmit2": "Enviar código",
"passwordResetSubmit": "Reiniciar Solicitud",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licencia",
"sidebarClients": "Clientes",
"sidebarDomains": "Dominios",
"sidebarBluePrints": "Planos",
"blueprints": "Planos",
"blueprintsDescription": "Aplicar configuraciones declarativas y ver ejecuciones anteriores",
"blueprintAdd": "Añadir plano",
"blueprintGoBack": "Ver todos los Planos",
"blueprintCreate": "Crear Plano",
"blueprintCreateDescription2": "Siga los siguientes pasos para crear y aplicar un nuevo plano",
"blueprintDetails": "Detalles del plano",
"blueprintDetailsDescription": "Ver el resultado del plano aplicado y cualquier error que haya ocurrido",
"blueprintInfo": "Información del plano",
"message": "Mensaje",
"blueprintContentsDescription": "Defina el contenido YAML describiendo su infraestructura",
"blueprintErrorCreateDescription": "Se ha producido un error al aplicar el plano",
"blueprintErrorCreate": "Error al crear el plano",
"searchBlueprintProgress": "Buscar planos...",
"appliedAt": "Aplicado en",
"source": "Fuente",
"contents": "Contenido",
"parsedContents": "Contenido analizado (Sólo lectura)",
"enableDockerSocket": "Habilitar Plano Docker",
"enableDockerSocketDescription": "Activar el raspado de etiquetas de Socket Docker para etiquetas de planos. La ruta del Socket debe proporcionarse a Newt.",
"enableDockerSocketLink": "Saber más",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Hubo un problema al usar tu llave de seguridad. Por favor, inténtalo de nuevo.",
"twoFactorRequired": "Se requiere autenticación de dos factores para registrar una llave de seguridad.",
"twoFactor": "Autenticación de dos factores",
"twoFactorAuthentication": "Autenticación de Dos Factores",
"twoFactorDescription": "Esta organización requiere autenticación de dos factores.",
"enableTwoFactor": "Habilitar autenticación de dos factores",
"organizationSecurityPolicy": "Política de Seguridad de la Organización",
"organizationSecurityPolicyDescription": "Esta organización tiene requisitos de seguridad que deben cumplirse antes de poder acceder a ella",
"securityRequirements": "Requisitos de seguridad",
"allRequirementsMet": "Todos los requisitos han sido cumplidos",
"completeRequirementsToContinue": "Completa los siguientes requisitos para seguir accediendo a esta organización",
"youCanNowAccessOrganization": "Ahora puedes acceder a esta organización",
"reauthenticationRequired": "Longitud de la sesión",
"reauthenticationDescription": "Esta organización requiere que inicies sesión cada {maxDays} días.",
"reauthenticationDescriptionHours": "Esta organización requiere que inicies sesión cada {maxHours} horas.",
"reauthenticateNow": "Iniciar sesión de nuevo",
"adminEnabled2FaOnYourAccount": "Su administrador ha habilitado la autenticación de dos factores para {email}. Por favor, complete el proceso de configuración para continuar.",
"securityKeyAdd": "Agregar llave de seguridad",
"securityKeyRegisterTitle": "Registrar nueva llave de seguridad",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Editar archivo: docker-compose.yml",
"emailVerificationRequired": "Se requiere verificación de correo electrónico. Por favor, inicie sesión de nuevo a través de {dashboardUrl}/auth/login complete este paso. Luego, vuelva aquí.",
"twoFactorSetupRequired": "La configuración de autenticación de doble factor es requerida. Por favor, inicia sesión de nuevo a través de {dashboardUrl}/auth/login completa este paso. Luego, vuelve aquí.",
"additionalSecurityRequired": "Seguridad adicional requerida",
"organizationRequiresAdditionalSteps": "Esta organización requiere pasos de seguridad adicionales antes de poder acceder a los recursos.",
"completeTheseSteps": "Completa estos pasos",
"enableTwoFactorAuthentication": "Habilitar autenticación de doble factor",
"completeSecuritySteps": "Pasos de seguridad completos",
"securitySettings": "Ajustes de seguridad",
"securitySettingsDescription": "Configurar políticas de seguridad para su organización",
"requireTwoFactorForAllUsers": "Requiere autenticación de doble factor para todos los usuarios",
"requireTwoFactorDescription": "Cuando está activado, todos los usuarios internos de esta organización deben tener habilitada la autenticación de dos factores para acceder a la organización.",
"requireTwoFactorDisabledDescription": "Esta característica requiere una licencia válida (Enterprise) o una suscripción activa (SaBudget)",
"requireTwoFactorCannotEnableDescription": "Debes habilitar la autenticación de doble factor para tu cuenta antes de aplicarla a todos los usuarios",
"maxSessionLength": "Longitud máxima de la sesión",
"maxSessionLengthDescription": "Establecer la duración máxima de las sesiones de usuario. Después de este tiempo, los usuarios tendrán que volver a autenticarse.",
"maxSessionLengthDisabledDescription": "Esta característica requiere una licencia válida (Enterprise) o una suscripción activa (SaBudget)",
"selectSessionLength": "Seleccionar duración de sesión",
"unenforced": "No aplicado",
"1Hour": "1 hora",
"3Hours": "3 horas",
"6Hours": "6 horas",
"12Hours": "12 horas",
"1DaySession": "1 día",
"3Days": "3 días",
"7Days": "7 días",
"14Days": "14 días",
"30DaysSession": "30 días",
"90DaysSession": "90 días",
"180DaysSession": "180 días",
"passwordExpiryDays": "Caduca la contraseña",
"editPasswordExpiryDescription": "Establecer el número de días antes de que los usuarios tengan que cambiar su contraseña.",
"selectPasswordExpiry": "Seleccione la contraseña expirada",
"30Days": "30 días",
"1Day": "1 día",
"60Days": "60 días",
"90Days": "90 días",
"180Days": "180 días",
"1Year": "1 año",
"subscriptionBadge": "Suscripción requerida",
"securityPolicyChangeWarning": "Advertencia de cambio de política de seguridad",
"securityPolicyChangeDescription": "Está a punto de cambiar la configuración de la política de seguridad. Después de guardar, puede que necesite volver a autenticarse para cumplir con estas actualizaciones de política. Todos los usuarios que no cumplan con los requisitos también tendrán que volver a autenticarse.",
"securityPolicyChangeConfirmMessage": "Confirmo",
"securityPolicyChangeWarningText": "Esto afectará a todos los usuarios de la organización",
"authPageErrorUpdateMessage": "Ocurrió un error mientras se actualizaban los ajustes de la página auth",
"authPageErrorUpdate": "No se puede actualizar la página de autenticación",
"authPageUpdated": "Página auth actualizada correctamente",
"healthCheckNotAvailable": "Local",
"rewritePath": "Reescribir Ruta",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Esto no se puede deshacer.",
"toConfirm": "confirmar",
"deleteClientQuestion": "¿Está seguro que desea eliminar el cliente del sitio y la organización?",
"clientMessageRemove": "Una vez eliminado, el cliente ya no podrá conectarse al sitio."
}
"clientMessageRemove": "Una vez eliminado, el cliente ya no podrá conectarse al sitio.",
"sidebarLogs": "Registros",
"request": "Solicitud",
"logs": "Registros",
"logsSettingsDescription": "Monitorear registros recogidos de esta orginización",
"searchLogs": "Buscar registros...",
"action": "Accin",
"actor": "Actor",
"timestamp": "Timestamp",
"accessLogs": "Registros de acceso",
"exportCsv": "Exportar CSV",
"actorId": "ID de Actor",
"allowedByRule": "Permitido por regla",
"allowedNoAuth": "No se permite autorización",
"validAccessToken": "Token de Acceso Válido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Contraseña válida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Recurso bloqueado",
"droppedByRule": "Soltado por regla",
"noSessions": "No hay sesiones",
"temporaryRequestToken": "Token de solicitud temporal",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Razón",
"requestLogs": "Registros de Solicitud",
"host": "Anfitrión",
"location": "Ubicación",
"actionLogs": "Registros de acción",
"sidebarLogsRequest": "Registros de Solicitud",
"sidebarLogsAccess": "Registros de acceso",
"sidebarLogsAction": "Registros de acción",
"logRetention": "Retención de Log",
"logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos",
"requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización",
"logRetentionRequestLabel": "Retención de Registro de Solicitud",
"logRetentionRequestDescription": "Cuánto tiempo conservar los registros de solicitudes",
"logRetentionAccessLabel": "Retención de Log de Acceso",
"logRetentionAccessDescription": "Cuánto tiempo retener los registros de acceso",
"logRetentionActionLabel": "Retención de registro de acción",
"logRetentionActionDescription": "Cuánto tiempo retener los registros de acción",
"logRetentionDisabled": "Deshabilitado",
"logRetention3Days": "3 días",
"logRetention7Days": "7 días",
"logRetention14Days": "14 días",
"logRetention30Days": "30 días",
"logRetention90Days": "90 días",
"logRetentionForever": "Para siempre",
"actionLogsDescription": "Ver un historial de acciones realizadas en esta organización",
"accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización",
"licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.",
"certResolver": "Resolver certificado",
"certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.",
"selectCertResolver": "Seleccionar Resolver Certificado",
"enterCustomResolver": "Introducir resolución personalizada",
"preferWildcardCert": "Certificado de comodín preferido",
"unverified": "Sin verificar",
"domainSetting": "Ajustes de dominio",
"domainSettingDescription": "Configurar ajustes para tu dominio",
"preferWildcardCertDescription": "Intento de generar un certificado comodín (requiere una resolución de certificados correctamente configurada).",
"recordName": "Nombre del registro",
"auto": "Auto",
"TTL": "TTL",
"howToAddRecords": "Cómo añadir registros",
"dnsRecord": "Registros DNS",
"required": "Requerido",
"domainSettingsUpdated": "Configuración de dominio actualizada correctamente",
"orgOrDomainIdMissing": "Falta el ID de organización o dominio",
"loadingDNSRecords": "Cargando registros DNS...",
"olmUpdateAvailableInfo": "Una versión actualizada de Olm está disponible. Por favor, actualice a la última versión para obtener la mejor experiencia.",
"client": "Cliente",
"proxyProtocol": "Configuración del Protocolo Proxy",
"proxyProtocolDescription": "Configurar el protocolo de proxy para preservar las direcciones IP del cliente para los servicios TCP/UDP.",
"enableProxyProtocol": "Habilitar protocolo proxy",
"proxyProtocolInfo": "Conservar direcciones IP del cliente para backends TCP/UDP",
"proxyProtocolVersion": "Versión del Protocolo Proxy",
"version1": " Versión 1 (Recomendado)",
"version2": "Versión 2",
"versionDescription": "La versión 1 está basada en texto y es ampliamente soportada. La versión 2 es binaria y más eficiente pero menos compatible.",
"warning": "Advertencia",
"proxyProtocolWarning": "Su aplicación de backend debe estar configurada para aceptar conexiones Proxy Protocol. Si su backend no soporta Proxy Protocol, habilitando esto romperá todas las conexiones. Asegúrese de configurar su backend para que confíe en las cabeceras del protocolo Proxy de Traefik.",
"restarting": "Reiniciando...",
"manual": "Manual",
"messageSupport": "Soporte de mensajes",
"supportNotAvailableTitle": "Soporte no disponible",
"supportNotAvailableDescription": "El soporte no está disponible en este momento. Puedes enviar un correo electrónico a support@pangolin.net.",
"supportRequestSentTitle": "Solicitud de soporte enviada",
"supportRequestSentDescription": "Su mensaje ha sido enviado con éxito.",
"supportRequestFailedTitle": "Error al enviar la solicitud",
"supportRequestFailedDescription": "Se ha producido un error al enviar su solicitud de soporte.",
"supportSubjectRequired": "El asunto es obligatorio",
"supportSubjectMaxLength": "El asunto debe tener 255 caracteres o menos",
"supportMessageRequired": "El mensaje es obligatorio",
"supportReplyTo": "Responder a",
"supportSubject": "Asunto",
"supportSubjectPlaceholder": "Introducir asunto",
"supportMessage": "Mensaje",
"supportMessagePlaceholder": "Introduce tu mensaje",
"supportSending": "Enviando...",
"supportSend": "Enviar",
"supportMessageSent": "¡Mensaje enviado!",
"supportWillContact": "¡Estaremos en contacto en breve!",
"selectLogRetention": "Seleccionar retención de registro",
"showColumns": "Mostrar columnas",
"hideColumns": "Ocultar columnas",
"columnVisibility": "Visibilidad de la columna",
"toggleColumn": "Cambiar columna {columnName}",
"allColumns": "Todas las columnas",
"defaultColumns": "Columnas por defecto",
"customizeView": "Personalizar vista",
"viewOptions": "Ver opciones",
"selectAll": "Seleccionar todo",
"selectNone": "No seleccionar",
"selectedResources": "Recursos seleccionados",
"enableSelected": "Habilitar seleccionados",
"disableSelected": "Desactivar Seleccionado",
"checkSelectedStatus": "Comprobar el estado de selección"
}

View File

@@ -10,20 +10,20 @@
"setupErrorIdentifier": "L'ID de l'organisation est déjà pris. Veuillez en choisir un autre.",
"componentsErrorNoMemberCreate": "Vous n'êtes actuellement membre d'aucune organisation. Créez une organisation pour commencer.",
"componentsErrorNoMember": "Vous n'êtes actuellement membre d'aucune organisation.",
"welcome": "Bienvenue à Pangolin",
"welcome": "Bienvenue sur Pangolin !",
"welcomeTo": "Bienvenue chez",
"componentsCreateOrg": "Créer une organisation",
"componentsMember": "Vous êtes membre de {count, plural, =0 {aucune organisation} one {une organisation} other {# organisations}}.",
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"dismiss": "Refuser",
"componentsLicenseViolation": "Violation de licence : Ce serveur utilise des sites {usedSites} qui dépassent la limite autorisée des sites {maxSites} . Suivez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Veuillez respecter les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"dismiss": "Rejeter",
"componentsLicenseViolation": "Violation de licence : ce serveur utilise {usedSites} sites, ce qui dépasse la limite autorisée de {maxSites} sites. Respectez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
"componentsSupporterMessage": "Merci de soutenir Pangolin en tant que {tier}!",
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder n'ait pas été acceptée ou n'est plus valide.",
"inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour cet utilisateur.",
"inviteLoginUser": "Assurez-vous que vous êtes bien connecté en tant qu'utilisateur correct.",
"inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation que vous essayez d'accéder ne soit pas pour un utilisateur qui existe.",
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder n'ait pas été acceptée ou ne soit plus valide.",
"inviteErrorUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne soit pas pour cet utilisateur.",
"inviteLoginUser": "Veuillez vous assurer que vous êtes connecté avec le bon utilisateur.",
"inviteErrorNoUser": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder ne concerne pas un utilisateur existant.",
"inviteCreateUser": "Veuillez d'abord créer un compte.",
"goHome": "Retour à la maison",
"goHome": "Retour à l'accueil",
"inviteLogInOtherUser": "Se connecter en tant qu'utilisateur différent",
"createAnAccount": "Créer un compte",
"inviteNotAccepted": "Invitation non acceptée",
@@ -39,12 +39,12 @@
"online": "En ligne",
"offline": "Hors ligne",
"site": "Site",
"dataIn": "Données dans",
"dataOut": "Données épuisées",
"dataIn": "Données entrantes",
"dataOut": "Données sortantes",
"connectionType": "Type de connexion",
"tunnelType": "Type de tunnel",
"local": "Locale",
"edit": "Editer",
"edit": "Éditer",
"siteConfirmDelete": "Confirmer la suppression du site",
"siteDelete": "Supprimer le site",
"siteMessageRemove": "Une fois supprimé, le site ne sera plus accessible. Toutes les cibles associées au site seront également supprimées.",
@@ -63,11 +63,11 @@
"siteLearnNewt": "Apprenez à installer Newt sur votre système",
"siteSeeConfigOnce": "Vous ne pourrez voir la configuration qu'une seule fois.",
"siteLoadWGConfig": "Chargement de la configuration WireGuard...",
"siteDocker": "Développer les détails du déploiement Docker",
"siteDocker": "Développer pour obtenir plus de détails sur le déploiement Docker",
"toggle": "Activer/désactiver",
"dockerCompose": "Composition Docker",
"dockerRun": "Exécution Docker",
"siteLearnLocal": "Les sites locaux ne tunnel, en savoir plus",
"siteLearnLocal": "Les sites locaux ne font pas de tunnel, en savoir plus",
"siteConfirmCopy": "J'ai copié la configuration",
"searchSitesProgress": "Rechercher des sites...",
"siteAdd": "Ajouter un site",
@@ -78,7 +78,7 @@
"operatingSystem": "Système d'exploitation",
"commands": "Commandes",
"recommended": "Recommandé",
"siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet d'adresser vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.",
"siteNewtDescription": "Pour une meilleure expérience d'utilisateur, utilisez Newt. Il utilise WireGuard sous le capot et vous permet de vous connecter à vos ressources privées par leur adresse LAN sur votre réseau privé à partir du tableau de bord Pangolin.",
"siteRunsInDocker": "Exécute dans Docker",
"siteRunsInShell": "Exécute en shell sur macOS, Linux et Windows",
"siteErrorDelete": "Erreur lors de la suppression du site",
@@ -93,7 +93,7 @@
"siteNewtTunnelDescription": "La façon la plus simple de créer un point d'entrée dans votre 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.",
"siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES",
"siteWgDescriptionSaas": "Utilisez n'importe quel client WireGuard pour établir un tunnel. Configuration NAT manuelle requise. FONCTIONNE UNIQUEMENT SUR DES NŒUDS AUTONOMES.",
"siteLocalDescription": "Ressources locales seulement. Pas de tunneling.",
"siteLocalDescriptionSaas": "Ressources locales uniquement. Pas de tunneling. Disponible uniquement sur les nœuds distants.",
"siteSeeAll": "Voir tous les sites",
@@ -130,9 +130,9 @@
"shareTitleOptional": "Titre (facultatif)",
"expireIn": "Expire dans",
"neverExpire": "N'expire jamais",
"shareExpireDescription": "Le temps d'expiration est combien de temps le lien sera utilisable et fournira un accès à la ressource. Après cette période, le lien ne fonctionnera plus et les utilisateurs qui ont utilisé ce lien perdront l'accès à la ressource.",
"shareSeeOnce": "Vous ne pourrez voir ce lien. Assurez-vous de le copier.",
"shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec soin.",
"shareExpireDescription": "La durée d'expiration correspond à la période pendant laquelle le lien sera utilisable et permettra d'accéder à la ressource. Passé ce délai, le lien ne fonctionnera plus et les utilisateurs qui l'ont utilisé perdront l'accès à la ressource.",
"shareSeeOnce": "Vous ne pourrez voir ce lien qu'une seule fois. Assurez-vous de le copier.",
"shareAccessHint": "N'importe qui avec ce lien peut accéder à la ressource. Partagez-le avec précaution.",
"shareTokenUsage": "Voir Utilisation du jeton d'accès",
"createLink": "Créer un lien",
"resourcesNotFound": "Aucune ressource trouvée",
@@ -155,9 +155,9 @@
"resourceMessageRemove": "Une fois supprimée, la ressource ne sera plus accessible. Toutes les cibles associées à la ressource seront également supprimées.",
"resourceQuestionRemove": "Êtes-vous sûr de vouloir supprimer la ressource de l'organisation ?",
"resourceHTTP": "Ressource HTTPS",
"resourceHTTPDescription": "Requêtes de proxy à votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.",
"resourceHTTPDescription": "Requêtes de proxy vers votre application via HTTPS en utilisant un sous-domaine ou un domaine de base.",
"resourceRaw": "Ressource TCP/UDP brute",
"resourceRawDescription": "Demandes de proxy à votre application via TCP/UDP en utilisant un numéro de port.",
"resourceRawDescription": "Demandes de proxy vers votre application via TCP/UDP en utilisant un numéro de port.",
"resourceCreate": "Créer une ressource",
"resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource",
"resourceSeeAll": "Voir toutes les ressources",
@@ -179,7 +179,7 @@
"baseDomain": "Domaine de base",
"subdomnainDescription": "Le sous-domaine où votre ressource sera accessible.",
"resourceRawSettings": "Paramètres TCP/UDP",
"resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP",
"resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP. Vous mappez la ressource à un port sur le serveur Pangolin, de sorte que vous puissiez accéder à la ressource depuis server-public-ip:mapped-port.",
"protocol": "Protocole",
"protocolSelect": "Sélectionner un protocole",
"resourcePortNumber": "Numéro de port",
@@ -206,7 +206,7 @@
"resourceSetting": "Réglages {resourceName}",
"alwaysAllow": "Toujours autoriser",
"alwaysDeny": "Toujours refuser",
"passToAuth": "Paser à l'authentification",
"passToAuth": "Passer à l'authentification",
"orgSettingsDescription": "Configurer les paramètres généraux de votre organisation",
"orgGeneralSettings": "Paramètres de l'organisation",
"orgGeneralSettingsDescription": "Gérer les détails et la configuration de votre organisation",
@@ -342,7 +342,7 @@
"licenseTitleDescription": "Voir et gérer les clés de licence dans le système",
"licenseHost": "Licence Hôte",
"licenseHostDescription": "Gérer la clé de licence principale de l'hôte.",
"licensedNot": "Non licenc",
"licensedNot": "Pas de licence",
"hostId": "ID de l'hôte",
"licenseReckeckAll": "Revérifier toutes les clés",
"licenseSiteUsage": "Utilisation des sites",
@@ -350,7 +350,7 @@
"licenseNoSiteLimit": "Il n'y a pas de limite sur le nombre de sites utilisant un hôte non autorisé.",
"licensePurchase": "Acheter une licence",
"licensePurchaseSites": "Acheter des sites supplémentaires",
"licenseSitesUsedMax": "{usedSites} des sites {maxSites} utilisés",
"licenseSitesUsedMax": "{usedSites} des {maxSites} sites utilisés",
"licenseSitesUsed": "{count, plural, =0 {# sites} one {# site} other {# sites}} dans le système.",
"licensePurchaseDescription": "Choisissez le nombre de sites que vous voulez {selectedMode, select, license {achetez une licence. Vous pouvez toujours ajouter plus de sites plus tard.} other {ajouter à votre licence existante.}}",
"licenseFee": "Frais de licence",
@@ -371,7 +371,7 @@
"inviteQuestionRemove": "Êtes-vous sûr de vouloir supprimer l'invitation?",
"inviteMessageRemove": "Une fois supprimée, cette invitation ne sera plus valide. Vous pourrez toujours réinviter l'utilisateur plus tard.",
"inviteMessageConfirm": "Pour confirmer, veuillez saisir l'adresse e-mail de l'invitation ci-dessous.",
"inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation {email}? Cela révoquera l'invitation précédente.",
"inviteQuestionRegenerate": "Êtes-vous sûr de vouloir régénérer l'invitation pour {email}? Cela révoquera l'invitation précédente.",
"inviteRemoveConfirm": "Confirmer la suppression de l'invitation",
"inviteRegenerated": "Invitation régénérée",
"inviteSent": "Une nouvelle invitation a été envoyée à {email}.",
@@ -465,7 +465,7 @@
"proxyErrorTls": "Nom de serveur TLS invalide. Utilisez le format de nom de domaine, ou laissez vide pour supprimer le nom de serveur TLS.",
"proxyEnableSSL": "Activer SSL",
"proxyEnableSSLDescription": "Activez le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers vos cibles.",
"target": "Target",
"target": "Cible",
"configureTarget": "Configurer les cibles",
"targetErrorFetch": "Échec de la récupération des cibles",
"targetErrorFetchDescription": "Une erreur s'est produite lors de la récupération des cibles",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Vérifiez votre e-mail pour le code de réinitialisation.",
"passwordNew": "Nouveau mot de passe",
"passwordNewConfirm": "Confirmer le nouveau mot de passe",
"changePassword": "Changer le mot de passe",
"changePasswordDescription": "Mettre à jour le mot de passe de votre compte",
"oldPassword": "Mot de passe actuel",
"newPassword": "Nouveau mot de passe",
"confirmNewPassword": "Confirmer le nouveau mot de passe",
"changePasswordError": "Impossible de changer le mot de passe",
"changePasswordErrorDescription": "Une erreur s'est produite lors de la modification de votre mot de passe",
"changePasswordSuccess": "Mot de passe modifié avec succès",
"changePasswordSuccessDescription": "Votre mot de passe a été mis à jour avec succès",
"passwordExpiryRequired": "Expiration du mot de passe requise",
"passwordExpiryDescription": "Cette organisation vous demande de changer votre mot de passe tous les {maxDays} jours.",
"changePasswordNow": "Changer le mot de passe maintenant",
"pincodeAuth": "Code d'authentification",
"pincodeSubmit2": "Soumettre le code",
"passwordResetSubmit": "Demander la réinitialisation",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licence",
"sidebarClients": "Clients",
"sidebarDomains": "Domaines",
"sidebarBluePrints": "Plans",
"blueprints": "Plans",
"blueprintsDescription": "Appliquer les configurations déclaratives et afficher les exécutions précédentes",
"blueprintAdd": "Ajouter un Plan",
"blueprintGoBack": "Voir tous les plans",
"blueprintCreate": "Créer un Plan",
"blueprintCreateDescription2": "Suivez les étapes ci-dessous pour créer et appliquer un nouveau plan",
"blueprintDetails": "Détails du Plan",
"blueprintDetailsDescription": "Voir le résultat du plan appliqué et les erreurs qui se sont produites",
"blueprintInfo": "Informations sur le Plan",
"message": "Message",
"blueprintContentsDescription": "Définissez le contenu YAML décrivant votre infrastructure",
"blueprintErrorCreateDescription": "Une erreur s'est produite lors de l'application du plan",
"blueprintErrorCreate": "Erreur lors de la création du plan",
"searchBlueprintProgress": "Rechercher des plans...",
"appliedAt": "Appliqué à",
"source": "Source",
"contents": "Contenus",
"parsedContents": "Contenu analysé (lecture seule)",
"enableDockerSocket": "Activer le Plan Docker",
"enableDockerSocketDescription": "Activer le ramassage d'étiquettes de socket Docker pour les étiquettes de plan. Le chemin de socket doit être fourni à Newt.",
"enableDockerSocketLink": "En savoir plus",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Un problème est survenu avec votre clé de sécurité. Veuillez réessayer.",
"twoFactorRequired": "L'authentification à deux facteurs est requise pour enregistrer une clé de sécurité.",
"twoFactor": "Authentification à deux facteurs",
"twoFactorAuthentication": "Authentification à deux facteurs",
"twoFactorDescription": "Cette organisation nécessite une authentification à deux facteurs.",
"enableTwoFactor": "Activer l'authentification à deux facteurs",
"organizationSecurityPolicy": "Politique de sécurité de l'organisation",
"organizationSecurityPolicyDescription": "Cette organisation a des exigences de sécurité qui doivent être remplies avant que vous puissiez y accéder",
"securityRequirements": "Exigences de sécurité",
"allRequirementsMet": "Toutes les conditions ont été remplies",
"completeRequirementsToContinue": "Remplissez les conditions ci-dessous pour continuer à accéder à cette organisation",
"youCanNowAccessOrganization": "Vous pouvez maintenant accéder à cette organisation",
"reauthenticationRequired": "Durée de la session",
"reauthenticationDescription": "Cette organisation vous demande de vous connecter tous les {maxDays} jours.",
"reauthenticationDescriptionHours": "Cette organisation vous demande de vous connecter toutes les {maxHours} heures.",
"reauthenticateNow": "Reconnectez-vous",
"adminEnabled2FaOnYourAccount": "Votre administrateur a activé l'authentification à deux facteurs pour {email}. Veuillez terminer le processus d'installation pour continuer.",
"securityKeyAdd": "Ajouter une clé de sécurité",
"securityKeyRegisterTitle": "Enregistrer une nouvelle clé de sécurité",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Modifier le fichier : docker-compose.yml",
"emailVerificationRequired": "La vérification de l'e-mail est requise. Veuillez vous reconnecter via {dashboardUrl}/auth/login terminé cette étape. Puis revenez ici.",
"twoFactorSetupRequired": "La configuration d'authentification à deux facteurs est requise. Veuillez vous reconnecter via {dashboardUrl}/auth/login terminé cette étape. Puis revenez ici.",
"additionalSecurityRequired": "Sécurité supplémentaire requise",
"organizationRequiresAdditionalSteps": "Cette organisation nécessite des étapes de sécurité supplémentaires avant de pouvoir accéder aux ressources.",
"completeTheseSteps": "Compléter ces étapes",
"enableTwoFactorAuthentication": "Activer l'authentification à deux facteurs",
"completeSecuritySteps": "Compléter les étapes de sécurité",
"securitySettings": "Paramètres de sécurité",
"securitySettingsDescription": "Configurer les politiques de sécurité de votre organisation",
"requireTwoFactorForAllUsers": "Exiger une authentification à deux facteurs pour tous les utilisateurs",
"requireTwoFactorDescription": "Lorsque cette option est activée, tous les utilisateurs internes de cette organisation doivent avoir l'authentification à deux facteurs pour accéder à l'organisation.",
"requireTwoFactorDisabledDescription": "Cette fonctionnalité nécessite une licence valide (Entreprise) ou un abonnement actif (SaaS)",
"requireTwoFactorCannotEnableDescription": "Vous devez activer l'authentification à deux facteurs pour votre compte avant de l'appliquer pour tous les utilisateurs",
"maxSessionLength": "Longueur maximale de la session",
"maxSessionLengthDescription": "Définissez la durée maximale des sessions utilisateur. Après cette période, les utilisateurs devront se ré-authentifier.",
"maxSessionLengthDisabledDescription": "Cette fonctionnalité nécessite une licence valide (Entreprise) ou un abonnement actif (SaaS)",
"selectSessionLength": "Sélectionnez la durée de la session",
"unenforced": "Non appliqué",
"1Hour": "1 heure",
"3Hours": "3heures",
"6Hours": "6 heures",
"12Hours": "12 heures",
"1DaySession": "1 jour",
"3Days": "3 jours",
"7Days": "7 jours",
"14Days": "14 jours",
"30DaysSession": "30 jours",
"90DaysSession": "90 jours",
"180DaysSession": "180 jours",
"passwordExpiryDays": "Expiration du mot de passe",
"editPasswordExpiryDescription": "Définissez le nombre de jours avant que les utilisateurs ne soient tenus de changer leur mot de passe.",
"selectPasswordExpiry": "Sélectionnez l'expiration du mot de passe",
"30Days": "30 jours",
"1Day": "1 jour",
"60Days": "60 jours",
"90Days": "90 jours",
"180Days": "180 jours",
"1Year": "1 an",
"subscriptionBadge": "Abonnement Requis",
"securityPolicyChangeWarning": "Avertissement de changement de politique de sécurité",
"securityPolicyChangeDescription": "Vous êtes sur le point de modifier les paramètres de la politique de sécurité. Une fois enregistré, vous devrez peut-être vous authentifier à nouveau pour vous conformer à ces mises à jour de règles. Tous les utilisateurs qui ne sont pas conformes devront également se réauthentifier.",
"securityPolicyChangeConfirmMessage": "Je confirme",
"securityPolicyChangeWarningText": "Cela affectera tous les utilisateurs de l'organisation",
"authPageErrorUpdateMessage": "Une erreur s'est produite lors de la mise à jour de la page d\u000027authentification",
"authPageErrorUpdate": "Impossible de mettre à jour la page d'authentification",
"authPageUpdated": "Page d\u000027authentification mise à jour avec succès",
"healthCheckNotAvailable": "Locale",
"rewritePath": "Réécrire le chemin",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Cela ne peut pas être annulé.",
"toConfirm": "pour confirmer",
"deleteClientQuestion": "Êtes-vous sûr de vouloir supprimer le client du site et de l'organisation ?",
"clientMessageRemove": "Une fois supprimé, le client ne pourra plus se connecter au site."
}
"clientMessageRemove": "Une fois supprimé, le client ne pourra plus se connecter au site.",
"sidebarLogs": "Journaux",
"request": "Demander",
"logs": "Journaux",
"logsSettingsDescription": "Surveiller les logs collectés à partir de cette organisation",
"searchLogs": "Rechercher dans les journaux...",
"action": "Action",
"actor": "Acteur",
"timestamp": "Horodatage",
"accessLogs": "Journaux d'accès",
"exportCsv": "Exporter CSV",
"actorId": "ID de l'acteur",
"allowedByRule": "Autorisé par la règle",
"allowedNoAuth": "Aucune authentification autorisée",
"validAccessToken": "Jeton d'accès valide",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Mot de passe valide",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Ressource bloquée",
"droppedByRule": "Abandonné par la règle",
"noSessions": "Aucune session",
"temporaryRequestToken": "Jeton de requête temporaire",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Raison",
"requestLogs": "Journal des requêtes",
"host": "Hôte",
"location": "Localisation",
"actionLogs": "Journaux des actions",
"sidebarLogsRequest": "Journal des requêtes",
"sidebarLogsAccess": "Journaux d'accès",
"sidebarLogsAction": "Journaux des actions",
"logRetention": "Journaliser la rétention",
"logRetentionDescription": "Gérer la durée de conservation des différents types de logs pour cette organisation ou les désactiver",
"requestLogsDescription": "Voir les journaux détaillés des requêtes pour les ressources de cette organisation",
"logRetentionRequestLabel": "Demander la rétention des journaux",
"logRetentionRequestDescription": "Durée de conservation des journaux de requêtes",
"logRetentionAccessLabel": "Rétention du journal d'accès",
"logRetentionAccessDescription": "Durée de conservation des journaux d'accès",
"logRetentionActionLabel": "Retention du journal des actions",
"logRetentionActionDescription": "Durée de conservation du journal des actions",
"logRetentionDisabled": "Désactivé",
"logRetention3Days": "3 jours",
"logRetention7Days": "7 jours",
"logRetention14Days": "14 jours",
"logRetention30Days": "30 jours",
"logRetention90Days": "90 jours",
"logRetentionForever": "Pour toujours",
"actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation",
"accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation",
"licenseRequiredToUse": "Une licence Entreprise est nécessaire pour utiliser cette fonctionnalité.",
"certResolver": "Résolveur de certificat",
"certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.",
"selectCertResolver": "Sélectionnez le résolveur de certificat",
"enterCustomResolver": "Entrez le résolveur personnalisé",
"preferWildcardCert": "Préférez le certificat Wildcard",
"unverified": "Non vérifié",
"domainSetting": "Paramètres de domaine",
"domainSettingDescription": "Configurer les paramètres de votre domaine",
"preferWildcardCertDescription": "Tentative de génération d'un certificat générique (nécessite un résolveur de certificat correctement configuré).",
"recordName": "Nom de l'enregistrement",
"auto": "Automatique",
"TTL": "TTC",
"howToAddRecords": "Comment ajouter des enregistrements",
"dnsRecord": "Enregistrements DNS",
"required": "Requis",
"domainSettingsUpdated": "Paramètres de domaine mis à jour avec succès",
"orgOrDomainIdMissing": "L'organisation ou l'identifiant de domaine est manquant",
"loadingDNSRecords": "Chargement des enregistrements DNS...",
"olmUpdateAvailableInfo": "Une version mise à jour de Olm est disponible. Veuillez mettre à jour vers la dernière version pour la meilleure expérience.",
"client": "Client",
"proxyProtocol": "Paramètres du protocole proxy",
"proxyProtocolDescription": "Configurer le protocole Proxy pour préserver les adresses IP du client pour les services TCP/UDP.",
"enableProxyProtocol": "Activer le protocole Proxy",
"proxyProtocolInfo": "Conserver les adresses IP du client pour les backends TCP/UDP",
"proxyProtocolVersion": "Version du protocole proxy",
"version1": " Version 1 (Recommandé)",
"version2": "Version 2",
"versionDescription": "La version 1 est basée sur du texte et est largement supportée. La version 2 est binaire et plus efficace mais moins compatible.",
"warning": "Avertissement",
"proxyProtocolWarning": "Votre application backend doit être configurée pour accepter les connexions Proxy Protocol. Si votre backend ne prend pas en charge le protocole Proxy, activer ceci va casser toutes les connexions. Assurez-vous de configurer votre backend pour faire confiance aux en-têtes du protocole Proxy de Traefik.",
"restarting": "Redémarrage...",
"manual": "Manuelle",
"messageSupport": "Soutien aux messages",
"supportNotAvailableTitle": "Support non disponible",
"supportNotAvailableDescription": "L'assistance n'est pas disponible pour le moment. Vous pouvez envoyer un e-mail à support@pangolin.net.",
"supportRequestSentTitle": "Demande de support envoyée",
"supportRequestSentDescription": "Votre message a été envoyé avec succès.",
"supportRequestFailedTitle": "Échec de l'envoi de la demande",
"supportRequestFailedDescription": "Une erreur s'est produite lors de l'envoi de votre demande d'assistance.",
"supportSubjectRequired": "Le sujet est requis",
"supportSubjectMaxLength": "Le sujet doit être de 255 caractères ou moins",
"supportMessageRequired": "Le message est requis",
"supportReplyTo": "Répondre à",
"supportSubject": "Sujet",
"supportSubjectPlaceholder": "Entrez le sujet",
"supportMessage": "Message",
"supportMessagePlaceholder": "Entrez votre message",
"supportSending": "Envoi...",
"supportSend": "Envoyer",
"supportMessageSent": "Message envoyé !",
"supportWillContact": "Nous vous contacterons sous peu!",
"selectLogRetention": "Sélectionner la durée de rétention du journal",
"showColumns": "Afficher les colonnes",
"hideColumns": "Cacher les colonnes",
"columnVisibility": "Visibilité des colonnes",
"toggleColumn": "Activer/désactiver la colonne {columnName}",
"allColumns": "Toutes les colonnes",
"defaultColumns": "Colonnes par défaut",
"customizeView": "Personnaliser la vue",
"viewOptions": "Voir les options",
"selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner",
"selectedResources": "Ressources sélectionnées",
"enableSelected": "Activer la sélection",
"disableSelected": "Désactiver la sélection",
"checkSelectedStatus": "Vérifier le statut de la sélection"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Scadenza In",
"neverExpire": "Mai scadere",
"shareExpireDescription": "Il tempo di scadenza è per quanto tempo il link sarà utilizzabile e fornirà accesso alla risorsa. Dopo questo tempo, il link non funzionerà più e gli utenti che hanno utilizzato questo link perderanno l'accesso alla risorsa.",
"shareSeeOnce": "Potrai vedere solo questo linkonce. Assicurati di copiarlo.",
"shareSeeOnce": "Potrai vedere questo link solo una volta. Assicurati di copiarlo.",
"shareAccessHint": "Chiunque abbia questo link può accedere alla risorsa. Condividilo con cura.",
"shareTokenUsage": "Vedi Utilizzo Token Di Accesso",
"createLink": "Crea Collegamento",
@@ -179,7 +179,7 @@
"baseDomain": "Dominio Base",
"subdomnainDescription": "Il sottodominio in cui la tua risorsa sarà accessibile.",
"resourceRawSettings": "Impostazioni TCP/UDP",
"resourceRawSettingsDescription": "Configura come accedere alla tua risorsa tramite TCP/UDP",
"resourceRawSettingsDescription": "Configura come sarà possibile accedere alla tua risorsa tramite TCP/UDP. Mappare la risorsa a una porta sul server host Pangolin, in modo da poter accedere alla risorsa dal server-public ip:mapped-port.",
"protocol": "Protocollo",
"protocolSelect": "Seleziona un protocollo",
"resourcePortNumber": "Numero Porta",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Controlla la tua email per il codice di reset.",
"passwordNew": "Nuova Password",
"passwordNewConfirm": "Conferma Nuova Password",
"changePassword": "Cambia Password",
"changePasswordDescription": "Aggiorna la password del tuo account",
"oldPassword": "Password Attuale",
"newPassword": "Nuova Password",
"confirmNewPassword": "Conferma Nuova Password",
"changePasswordError": "Impossibile cambiare la password",
"changePasswordErrorDescription": "Si è verificato un errore durante la modifica della password",
"changePasswordSuccess": "Password Cambiata Con Successo",
"changePasswordSuccessDescription": "La password è stata aggiornata con successo",
"passwordExpiryRequired": "Scadenza Password Richiesta",
"passwordExpiryDescription": "Questa organizzazione richiede di cambiare la password ogni {maxDays} giorni.",
"changePasswordNow": "Cambia Password Ora",
"pincodeAuth": "Codice Autenticatore",
"pincodeSubmit2": "Invia Codice",
"passwordResetSubmit": "Richiedi Reset",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licenza",
"sidebarClients": "Client",
"sidebarDomains": "Domini",
"sidebarBluePrints": "Progetti",
"blueprints": "Progetti",
"blueprintsDescription": "Applica le configurazioni dichiarative e visualizza le partite precedenti",
"blueprintAdd": "Aggiungi Progetto",
"blueprintGoBack": "Vedi tutti i progetti",
"blueprintCreate": "Crea Progetto",
"blueprintCreateDescription2": "Segui i passaggi qui sotto per creare e applicare un nuovo progetto",
"blueprintDetails": "Dettagli Progetto",
"blueprintDetailsDescription": "Vedere il risultato del progetto applicato e gli eventuali errori verificatisi",
"blueprintInfo": "Informazioni Sul Progetto",
"message": "Messaggio",
"blueprintContentsDescription": "Definisci il contenuto di YAML che descrive la tua infrastruttura",
"blueprintErrorCreateDescription": "Si è verificato un errore durante l'applicazione del progetto",
"blueprintErrorCreate": "Errore nella creazione del progetto",
"searchBlueprintProgress": "Cerca progetti...",
"appliedAt": "Applicato Il",
"source": "Fonte",
"contents": "Contenuti",
"parsedContents": "Sommario Analizzato (Solo Lettura)",
"enableDockerSocket": "Abilita Progetto Docker",
"enableDockerSocketDescription": "Abilita la raschiatura dell'etichetta Docker Socket per le etichette dei progetti. Il percorso del socket deve essere fornito a Newt.",
"enableDockerSocketLink": "Scopri di più",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Si è verificato un problema con la tua chiave di sicurezza. Riprova.",
"twoFactorRequired": "È richiesta l'autenticazione a due fattori per registrare una chiave di sicurezza.",
"twoFactor": "Autenticazione a Due Fattori",
"twoFactorAuthentication": "Autenticazione A Due Fattori",
"twoFactorDescription": "Questa organizzazione richiede l'autenticazione a due fattori.",
"enableTwoFactor": "Abilita Autenticazione A Due Fattori",
"organizationSecurityPolicy": "Politica Di Sicurezza Dell'Organizzazione",
"organizationSecurityPolicyDescription": "Questa organizzazione ha requisiti di sicurezza che devono essere soddisfatti prima di poter accedere",
"securityRequirements": "Requisiti Di Sicurezza",
"allRequirementsMet": "Tutti i requisiti sono stati soddisfatti",
"completeRequirementsToContinue": "Completa i requisiti qui sotto per continuare ad accedere a questa organizzazione",
"youCanNowAccessOrganization": "Ora puoi accedere a questa organizzazione",
"reauthenticationRequired": "Durata Sessione",
"reauthenticationDescription": "Questa organizzazione richiede di accedere ogni {maxDays} giorni.",
"reauthenticationDescriptionHours": "Questa organizzazione richiede di accedere ogni {maxHours} ore.",
"reauthenticateNow": "Accedi Di Nuovo",
"adminEnabled2FaOnYourAccount": "Il tuo amministratore ha abilitato l'autenticazione a due fattori per {email}. Completa il processo di configurazione per continuare.",
"securityKeyAdd": "Aggiungi Chiave di Sicurezza",
"securityKeyRegisterTitle": "Registra Nuova Chiave di Sicurezza",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Modifica file: docker-compose.yml",
"emailVerificationRequired": "Verifica via email. Effettua nuovamente il login via {dashboardUrl}/auth/login completa questo passaggio. Quindi, torna qui.",
"twoFactorSetupRequired": "È richiesta la configurazione di autenticazione a due fattori. Effettua nuovamente l'accesso tramite {dashboardUrl}/auth/login completa questo passaggio. Quindi, torna qui.",
"additionalSecurityRequired": "Necessaria Sicurezza Aggiuntiva",
"organizationRequiresAdditionalSteps": "Questa organizzazione richiede ulteriori passi di sicurezza prima di poter accedere alle risorse.",
"completeTheseSteps": "Completa questi passaggi",
"enableTwoFactorAuthentication": "Abilita autenticazione a due fattori",
"completeSecuritySteps": "Passi Di Sicurezza Completa",
"securitySettings": "Impostazioni Di Sicurezza",
"securitySettingsDescription": "Configura i criteri di sicurezza per la tua organizzazione",
"requireTwoFactorForAllUsers": "Richiede l'autenticazione a due fattori per tutti gli utenti",
"requireTwoFactorDescription": "Se abilitata, tutti gli utenti interni di questa organizzazione devono avere un'autenticazione a due fattori abilitata per accedere all'organizzazione.",
"requireTwoFactorDisabledDescription": "Questa funzione richiede una licenza valida (Enterprise) o un abbonamento attivo (SaaS)",
"requireTwoFactorCannotEnableDescription": "Devi abilitare l'autenticazione a due fattori per il tuo account prima di applicarla per tutti gli utenti",
"maxSessionLength": "Lunghezza Massima Della Sessione",
"maxSessionLengthDescription": "Imposta la durata massima per le sessioni utente. Dopo questo periodo, gli utenti dovranno autenticarsi.",
"maxSessionLengthDisabledDescription": "Questa funzione richiede una licenza valida (Enterprise) o un abbonamento attivo (SaaS)",
"selectSessionLength": "Seleziona lunghezza sessione",
"unenforced": "Non Applicato",
"1Hour": "1 ora",
"3Hours": "3 ore",
"6Hours": "6 ore",
"12Hours": "12 ore",
"1DaySession": "1 giorno",
"3Days": "3 giorni",
"7Days": "7 giorni",
"14Days": "14 giorni",
"30DaysSession": "30 giorni",
"90DaysSession": "90 giorni",
"180DaysSession": "180 giorni",
"passwordExpiryDays": "Scadenza Password",
"editPasswordExpiryDescription": "Imposta il numero di giorni prima che gli utenti debbano cambiare la password.",
"selectPasswordExpiry": "Seleziona scadenza password",
"30Days": "30 giorni",
"1Day": "1 giorno",
"60Days": "60 giorni",
"90Days": "90 giorni",
"180Days": "180 giorni",
"1Year": "1 anno",
"subscriptionBadge": "Abbonamento Richiesto",
"securityPolicyChangeWarning": "Avviso Modifica Politica Di Sicurezza",
"securityPolicyChangeDescription": "Si sta per modificare le impostazioni dei criteri di sicurezza. Dopo il salvataggio, potrebbe essere necessario autenticarsi nuovamente per conformarsi a questi aggiornamenti dei criteri. Tutti gli utenti che non sono conformi dovranno anche autenticarsi.",
"securityPolicyChangeConfirmMessage": "Confermo",
"securityPolicyChangeWarningText": "Questo influenzerà tutti gli utenti dell'organizzazione",
"authPageErrorUpdateMessage": "Si è verificato un errore durante l'aggiornamento delle impostazioni della pagina di autenticazione",
"authPageErrorUpdate": "Impossibile aggiornare la pagina di autenticazione",
"authPageUpdated": "Pagina di autenticazione aggiornata con successo",
"healthCheckNotAvailable": "Locale",
"rewritePath": "Riscrivi percorso",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Questo non può essere annullato.",
"toConfirm": "per confermare",
"deleteClientQuestion": "Sei sicuro di voler rimuovere il client dal sito e dall'organizzazione?",
"clientMessageRemove": "Una volta rimosso, il client non sarà più in grado di connettersi al sito."
}
"clientMessageRemove": "Una volta rimosso, il client non sarà più in grado di connettersi al sito.",
"sidebarLogs": "Registri",
"request": "Richiesta",
"logs": "Registri",
"logsSettingsDescription": "Monitora i registri raccolti da questa orginizzazione",
"searchLogs": "Cerca registro...",
"action": "Azione",
"actor": "Attore",
"timestamp": "Timestamp",
"accessLogs": "Log Accesso",
"exportCsv": "Esporta CSV",
"actorId": "Id Attore",
"allowedByRule": "Consentito dalla regola",
"allowedNoAuth": "Non Consentito Auth",
"validAccessToken": "Token Di Accesso Valido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Password Valida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Risorsa Bloccata",
"droppedByRule": "Eliminato dalla regola",
"noSessions": "Nessuna Sessione",
"temporaryRequestToken": "Token Di Richiesta Temporaneo",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Motivo",
"requestLogs": "Log Richiesta",
"host": "Host",
"location": "Posizione",
"actionLogs": "Log Azioni",
"sidebarLogsRequest": "Log Richiesta",
"sidebarLogsAccess": "Log Accesso",
"sidebarLogsAction": "Log Azioni",
"logRetention": "Ritenzione Registro",
"logRetentionDescription": "Gestisci per quanto tempo i diversi tipi di log sono mantenuti per questa organizzazione o disabilitali",
"requestLogsDescription": "Visualizza i registri di richiesta dettagliati per le risorse in questa organizzazione",
"logRetentionRequestLabel": "Richiedi Ritenzione Log",
"logRetentionRequestDescription": "Per quanto tempo conservare i log delle richieste",
"logRetentionAccessLabel": "Ritenzione Registro Accesso",
"logRetentionAccessDescription": "Per quanto tempo conservare i log di accesso",
"logRetentionActionLabel": "Ritenzione Registro Azioni",
"logRetentionActionDescription": "Per quanto tempo conservare i log delle azioni",
"logRetentionDisabled": "Disabilitato",
"logRetention3Days": "3 giorni",
"logRetention7Days": "7 giorni",
"logRetention14Days": "14 giorni",
"logRetention30Days": "30 giorni",
"logRetention90Days": "90 giorni",
"logRetentionForever": "Per Sempre",
"actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione",
"accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione",
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise.",
"certResolver": "Risolutore Di Certificato",
"certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.",
"selectCertResolver": "Seleziona Risolutore Di Certificato",
"enterCustomResolver": "Inserisci Risolutore Personalizzato",
"preferWildcardCert": "Preferisci Certificato Wildcard",
"unverified": "Non Verificato",
"domainSetting": "Impostazioni Dominio",
"domainSettingDescription": "Configura le impostazioni per il tuo dominio",
"preferWildcardCertDescription": "Tentativo di generare un certificato jolly (richiede un risolutore di certificati correttamente configurato).",
"recordName": "Nome Record",
"auto": "Automatico",
"TTL": "TTL",
"howToAddRecords": "Come aggiungere record",
"dnsRecord": "Record DNS",
"required": "Richiesto",
"domainSettingsUpdated": "Impostazioni dominio aggiornate con successo",
"orgOrDomainIdMissing": "Manca l'ID dell'organizzazione o del dominio",
"loadingDNSRecords": "Caricamento record DNS...",
"olmUpdateAvailableInfo": "È disponibile una versione aggiornata di Olm. Si prega di aggiornare all'ultima versione per la migliore esperienza.",
"client": "Client",
"proxyProtocol": "Impostazioni Protocollo Proxy",
"proxyProtocolDescription": "Configurare il protocollo proxy per preservare gli indirizzi IP client per i servizi TCP/UDP.",
"enableProxyProtocol": "Abilita Protocollo Proxy",
"proxyProtocolInfo": "Conserva gli indirizzi IP del client per i backend TCP/UDP",
"proxyProtocolVersion": "Versione Protocollo Proxy",
"version1": " Versione 1 (Consigliato)",
"version2": "Versione 2",
"versionDescription": "La versione 1 è testuale e ampiamente supportata. La versione 2 è binaria e più efficiente, ma meno compatibile.",
"warning": "Attenzione",
"proxyProtocolWarning": "La tua applicazione backend deve essere configurata per accettare le connessioni del protocollo proxy. Se il tuo backend non supporta il protocollo proxy, abilitando questa opzione si interromperanno tutte le connessioni. Assicurati di configurare il tuo backend per fidarti delle intestazioni del protocollo proxy da Traefik.",
"restarting": "Riavvio...",
"manual": "Manuale",
"messageSupport": "Supporto Messaggio",
"supportNotAvailableTitle": "Supporto Non Disponibile",
"supportNotAvailableDescription": "Il supporto non è disponibile in questo momento. Puoi inviare un'email a support@pangolin.net.",
"supportRequestSentTitle": "Richiesta Di Supporto Inviata",
"supportRequestSentDescription": "Il tuo messaggio è stato inviato con successo.",
"supportRequestFailedTitle": "Impossibile inviare la richiesta",
"supportRequestFailedDescription": "Si è verificato un errore durante l'invio della richiesta di supporto.",
"supportSubjectRequired": "L'oggetto è obbligatorio",
"supportSubjectMaxLength": "L'oggetto deve contenere almeno 255 caratteri",
"supportMessageRequired": "Il messaggio è obbligatorio",
"supportReplyTo": "Rispondi A",
"supportSubject": "Oggetto",
"supportSubjectPlaceholder": "Inserisci oggetto",
"supportMessage": "Messaggio",
"supportMessagePlaceholder": "Inserisci il tuo messaggio",
"supportSending": "Invio...",
"supportSend": "Invia",
"supportMessageSent": "Messaggio Inviato!",
"supportWillContact": "Saremo in contatto a breve!",
"selectLogRetention": "Seleziona ritenzione log",
"showColumns": "Mostra Colonne",
"hideColumns": "Nascondi Colonne",
"columnVisibility": "Visibilità Colonna",
"toggleColumn": "Attiva/disattiva colonna {columnName}",
"allColumns": "Tutte Le Colonne",
"defaultColumns": "Colonne Predefinite",
"customizeView": "Personalizza Vista",
"viewOptions": "Opzioni Visualizzazione",
"selectAll": "Seleziona Tutto",
"selectNone": "Seleziona Nessuno",
"selectedResources": "Risorse Selezionate",
"enableSelected": "Abilita Selezionati",
"disableSelected": "Disabilita Selezionati",
"checkSelectedStatus": "Controlla lo stato dei selezionati"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "기본 도메인",
"subdomnainDescription": "리소스에 접근할 수 있는 하위 도메인입니다.",
"resourceRawSettings": "TCP/UDP 설정",
"resourceRawSettingsDescription": "TCP/UDP를 통해 리소스에 접근하는 방법을 구성하세요.",
"resourceRawSettingsDescription": "리소스를 TCP/UDP를 통해 액세스하는 방법을 구성합니다. 리소스를 호스트 Pangolin 서버의 포트에 매핑하여 서버-public-ip:매핑된 포트에서 리소스에 액세스할 수 있습니다.",
"protocol": "프로토콜",
"protocolSelect": "프로토콜 선택",
"resourcePortNumber": "포트 번호",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "재설정 코드를 확인하려면 이메일을 확인하세요.",
"passwordNew": "새 비밀번호",
"passwordNewConfirm": "새 비밀번호 확인",
"changePassword": "비밀번호 변경",
"changePasswordDescription": "계정 비밀번호를 업데이트하십시오",
"oldPassword": "현재 비밀번호",
"newPassword": "새 비밀번호",
"confirmNewPassword": "새 비밀번호 확인",
"changePasswordError": "비밀번호 변경 실패",
"changePasswordErrorDescription": "비밀번호를 변경하는 중 오류가 발생했습니다",
"changePasswordSuccess": "비밀번호 변경 완료",
"changePasswordSuccessDescription": "비밀번호가 성공적으로 업데이트되었습니다",
"passwordExpiryRequired": "비밀번호 만료 필요",
"passwordExpiryDescription": "이 조직은 {maxDays}일마다 비밀번호 변경을 요구합니다.",
"changePasswordNow": "지금 비밀번호 변경",
"pincodeAuth": "인증 코드",
"pincodeSubmit2": "코드 제출",
"passwordResetSubmit": "재설정 요청",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "라이선스",
"sidebarClients": "클라이언트",
"sidebarDomains": "도메인",
"sidebarBluePrints": "청사진",
"blueprints": "청사진",
"blueprintsDescription": "선언적 구성을 적용하고 이전 실행을 봅니다",
"blueprintAdd": "청사진 추가",
"blueprintGoBack": "모든 청사진 보기",
"blueprintCreate": "청사진 생성",
"blueprintCreateDescription2": "새 청사진을 생성하고 적용하려면 아래 단계를 따르십시오",
"blueprintDetails": "청사진 세부사항",
"blueprintDetailsDescription": "적용된 청사진의 결과와 발생한 오류를 확인합니다",
"blueprintInfo": "청사진 정보",
"message": "메시지",
"blueprintContentsDescription": "인프라를 설명하는 YAML 콘텐츠를 정의하십시오",
"blueprintErrorCreateDescription": "청사진을 적용하는 중 오류가 발생했습니다",
"blueprintErrorCreate": "청사진 생성 오류",
"searchBlueprintProgress": "청사진 검색...",
"appliedAt": "적용 시점",
"source": "출처",
"contents": "콘텐츠",
"parsedContents": "구문 분석된 콘텐츠 (읽기 전용)",
"enableDockerSocket": "Docker 청사진 활성화",
"enableDockerSocketDescription": "블루프린트 레이블을 위한 Docker 소켓 레이블 수집을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.",
"enableDockerSocketLink": "자세히 알아보기",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "보안 키를 사용하는 데 문제가 발생했습니다. 다시 시도하세요.",
"twoFactorRequired": "보안 키를 등록하려면 이중 인증이 필요합니다.",
"twoFactor": "이중 인증",
"twoFactorAuthentication": "이중 인증",
"twoFactorDescription": "이 조직은 이중 인증을 요구합니다.",
"enableTwoFactor": "이중 인증 활성화",
"organizationSecurityPolicy": "조직 보안 정책",
"organizationSecurityPolicyDescription": "이 조직에는 접근하기 전에 준수해야 하는 보안 요구 사항이 있습니다",
"securityRequirements": "보안 요구 사항",
"allRequirementsMet": "모든 요구 사항이 충족되었습니다",
"completeRequirementsToContinue": "이 조직에 계속 접근하려면 아래 요구 사항을 완료하십시오",
"youCanNowAccessOrganization": "이제 이 조직에 접근할 수 있습니다",
"reauthenticationRequired": "세션 길이",
"reauthenticationDescription": "이 조직은 {maxDays}일마다 로그인하는 것을 요구합니다.",
"reauthenticationDescriptionHours": "이 조직은 {maxHours}시간마다 로그인하는 것을 요구합니다.",
"reauthenticateNow": "다시 로그인",
"adminEnabled2FaOnYourAccount": "관리자가 {email}에 대한 이중 인증을 활성화했습니다. 계속하려면 설정을 완료하세요.",
"securityKeyAdd": "보안 키 추가",
"securityKeyRegisterTitle": "새 보안 키 등록",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "파일 편집: docker-compose.yml",
"emailVerificationRequired": "이메일 인증이 필요합니다. 이 단계를 완료하려면 {dashboardUrl}/auth/login 통해 다시 로그인하십시오. 그런 다음 여기로 돌아오세요.",
"twoFactorSetupRequired": "이중 인증 설정이 필요합니다. 이 단계를 완료하려면 {dashboardUrl}/auth/login 통해 다시 로그인하십시오. 그런 다음 여기로 돌아오세요.",
"additionalSecurityRequired": "추가 보안 필요",
"organizationRequiresAdditionalSteps": "이 조직은 자원에 접근하기 전에 추가 보안 단계를 요구합니다.",
"completeTheseSteps": "이 단계를 완료하십시오",
"enableTwoFactorAuthentication": "이중 인증 활성화",
"completeSecuritySteps": "보안 단계 완료",
"securitySettings": "보안 설정",
"securitySettingsDescription": "조직에 대한 보안 정책을 구성합니다",
"requireTwoFactorForAllUsers": "모든 사용자에 대해 이중 인증 요구",
"requireTwoFactorDescription": "활성화되면, 이 조직의 모든 내부 사용자는 조직에 접근하기 위해 이중 인증을 활성화해야 합니다.",
"requireTwoFactorDisabledDescription": "이 기능을 사용하려면 유효한 라이선스(Enterprise) 또는 활성 구독(SaaS)가 필요합니다.",
"requireTwoFactorCannotEnableDescription": "모든 사용자에게 강제하기 전에 계정에 대해 이중 인증을 활성화해야 합니다",
"maxSessionLength": "최대 세션 길이",
"maxSessionLengthDescription": "사용자 세션의 최대 지속 시간을 설정합니다. 이 시간이 지나면 사용자는 다시 인증해야 합니다.",
"maxSessionLengthDisabledDescription": "이 기능을 사용하려면 유효한 라이선스(Enterprise) 또는 활성 구독(SaaS)가 필요합니다.",
"selectSessionLength": "세션 길이 선택",
"unenforced": "강제되지 않음",
"1Hour": "1 시간",
"3Hours": "3 시간",
"6Hours": "6 시간",
"12Hours": "12 시간",
"1DaySession": "1 일",
"3Days": "3 일",
"7Days": "7 일",
"14Days": "14 일",
"30DaysSession": "30 일",
"90DaysSession": "90 일",
"180DaysSession": "180 일",
"passwordExpiryDays": "비밀번호 만료",
"editPasswordExpiryDescription": "사용자가 비밀번호를 변경해야 하는 날 수를 설정합니다.",
"selectPasswordExpiry": "비밀번호 만료 선택",
"30Days": "30 일",
"1Day": "1 일",
"60Days": "60 일",
"90Days": "90 일",
"180Days": "180 일",
"1Year": "1 년",
"subscriptionBadge": "구독 필요",
"securityPolicyChangeWarning": "보안 정책 변경 경고",
"securityPolicyChangeDescription": "보안 정책 설정을 변경하려고 합니다. 저장 후, 정책 업데이트를 준수하기 위해 다시 인증해야 할 수도 있습니다. 규정을 준수하지 않는 모든 사용자도 다시 인증해야 합니다.",
"securityPolicyChangeConfirmMessage": "확인합니다",
"securityPolicyChangeWarningText": "이 작업은 조직의 모든 사용자에게 영향을 미칩니다",
"authPageErrorUpdateMessage": "인증 페이지 설정을 업데이트하는 동안 오류가 발생했습니다",
"authPageErrorUpdate": "인증 페이지를 업데이트할 수 없습니다",
"authPageUpdated": "인증 페이지가 성공적으로 업데이트되었습니다",
"healthCheckNotAvailable": "로컬",
"rewritePath": "경로 재작성",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "이 작업은 되돌릴 수 없습니다.",
"toConfirm": "확인하려면",
"deleteClientQuestion": "고객을 사이트와 조직에서 제거하시겠습니까?",
"clientMessageRemove": "제거되면 클라이언트는 사이트에 더 이상 연결할 수 없습니다."
}
"clientMessageRemove": "제거되면 클라이언트는 사이트에 더 이상 연결할 수 없습니다.",
"sidebarLogs": "로그",
"request": "요청",
"logs": "로그",
"logsSettingsDescription": "이 조직에서 수집된 로그를 모니터링합니다",
"searchLogs": "로그 검색...",
"action": "작업",
"actor": "행위자",
"timestamp": "타임스탬프",
"accessLogs": "접근 로그",
"exportCsv": "CSV 내보내기",
"actorId": "행위자 ID",
"allowedByRule": "룰에 의해 허용됨",
"allowedNoAuth": "인증 없음 허용됨",
"validAccessToken": "유효한 접근 토큰",
"validHeaderAuth": "유효한 헤더 인증",
"validPincode": "유효한 핀코드",
"validPassword": "유효한 비밀번호",
"validEmail": "유효한 이메일",
"validSSO": "유효한 SSO",
"resourceBlocked": "리소스 차단됨",
"droppedByRule": "룰에 의해 드롭됨",
"noSessions": "세션 없음",
"temporaryRequestToken": "임시 요청 토큰",
"noMoreAuthMethods": "유효한 인증 없음",
"ip": "IP",
"reason": "이유",
"requestLogs": "요청 로그",
"host": "호스트",
"location": "위치",
"actionLogs": "작업 로그",
"sidebarLogsRequest": "요청 로그",
"sidebarLogsAccess": "접근 로그",
"sidebarLogsAction": "작업 로그",
"logRetention": "로그 보관",
"logRetentionDescription": "다양한 유형의 로그를 이 조직에 대해 얼마나 오래 보관할지 관리하거나 비활성화합니다",
"requestLogsDescription": "이 조직의 자원에 대한 상세한 요청 로그를 봅니다",
"logRetentionRequestLabel": "요청 로그 보관",
"logRetentionRequestDescription": "요청 로그를 얼마나 오래 보관할지",
"logRetentionAccessLabel": "접근 로그 보관",
"logRetentionAccessDescription": "접근 로그를 얼마나 오래 보관할지",
"logRetentionActionLabel": "작업 로그 보관",
"logRetentionActionDescription": "작업 로그를 얼마나 오래 보관할지",
"logRetentionDisabled": "비활성화됨",
"logRetention3Days": "3 일",
"logRetention7Days": "7 일",
"logRetention14Days": "14 일",
"logRetention30Days": "30 일",
"logRetention90Days": "90 일",
"logRetentionForever": "영구",
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
"licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.",
"certResolver": "인증서 해결사",
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
"selectCertResolver": "인증서 해결사 선택",
"enterCustomResolver": "사용자 정의 해결사 입력",
"preferWildcardCert": "와일드카드 인증서 선호",
"unverified": "검증되지 않음",
"domainSetting": "도메인 설정",
"domainSettingDescription": "도메인에 대한 설정을 구성하세요.",
"preferWildcardCertDescription": "와일드카드 인증서를 생성하려고 시도합니다 (올바르게 구성된 인증서 해결사가 필요합니다).",
"recordName": "레코드 이름",
"auto": "자동",
"TTL": "TTL",
"howToAddRecords": "레코드 추가 방법",
"dnsRecord": "DNS 레코드",
"required": "필수",
"domainSettingsUpdated": "도메인 설정이 성공적으로 업데이트되었습니다",
"orgOrDomainIdMissing": "조직 ID 또는 도메인 ID가 누락되었습니다",
"loadingDNSRecords": "DNS 레코드를 로드하는 중...",
"olmUpdateAvailableInfo": "올름의 새 버전이 이용 가능합니다. 최상의 경험을 위해 최신 버전으로 업데이트하세요.",
"client": "클라이언트",
"proxyProtocol": "프록시 프로토콜 설정",
"proxyProtocolDescription": "프록시 프로토콜을 구성하여 TCP/UDP 서비스에 대한 클라이언트 IP 주소를 보존하십시오.",
"enableProxyProtocol": "프록시 프로토콜 활성화",
"proxyProtocolInfo": "TCP/UDP 백엔드의 클라이언트 IP 주소를 보존합니다",
"proxyProtocolVersion": "프록시 프로토콜 버전",
"version1": " 버전 1 (추천)",
"version2": "버전 2",
"versionDescription": "버전 1은 텍스트 기반으로 널리 지원됩니다. 버전 2는 이진 기반으로 더 효율적이지만 호환성이 낮습니다.",
"warning": "경고",
"proxyProtocolWarning": "백엔드 애플리케이션이 프록시 프로토콜 연결을 허용하도록 구성되어야 합니다. 백엔드가 프록시 프로토콜을 지원하지 않으면, 이를 활성화하면 모든 연결이 끊어집니다. 트래픽에서 온 프록시 프로토콜 헤더를 백엔드가 신뢰하도록 구성하십시오.",
"restarting": "재시작 중...",
"manual": "수동",
"messageSupport": "지원 메시지",
"supportNotAvailableTitle": "지원 불가",
"supportNotAvailableDescription": "현재 지원을 받을 수 없습니다. support@pangolin.net으로 이메일을 보낼 수 있습니다.",
"supportRequestSentTitle": "지원 요청 전송 완료",
"supportRequestSentDescription": "메시지가 성공적으로 전송되었습니다.",
"supportRequestFailedTitle": "요청 전송 실패",
"supportRequestFailedDescription": "지원 요청을 보내는 중 오류가 발생했습니다.",
"supportSubjectRequired": "제목은 필수입니다",
"supportSubjectMaxLength": "제목은 255자 이내여야 합니다",
"supportMessageRequired": "메시지는 필수입니다",
"supportReplyTo": "회신",
"supportSubject": "제목",
"supportSubjectPlaceholder": "제목 입력",
"supportMessage": "메시지",
"supportMessagePlaceholder": "메시지를 입력하십시오",
"supportSending": "발송 중...",
"supportSend": "보내기",
"supportMessageSent": "메시지 전송 완료!",
"supportWillContact": "곧 연락드리겠습니다!",
"selectLogRetention": "로그 보존 선택",
"showColumns": "열 표시",
"hideColumns": "열 숨기기",
"columnVisibility": "열 가시성",
"toggleColumn": "{columnName} 열 토글",
"allColumns": "모든 열",
"defaultColumns": "기본 열",
"customizeView": "보기 사용자 지정",
"viewOptions": "보기 옵션",
"selectAll": "모두 선택",
"selectNone": "선택하지 않음",
"selectedResources": "선택된 리소스",
"enableSelected": "선택된 항목 활성화",
"disableSelected": "선택된 항목 비활성화",
"checkSelectedStatus": "선택된 항목 상태 확인"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Utløper om",
"neverExpire": "Utløper aldri",
"shareExpireDescription": "Utløpstid er hvor lenge lenken vil være brukbar og gi tilgang til ressursen. Etter denne tiden vil lenken ikke lenger fungere, og brukere som brukte denne lenken vil miste tilgangen til ressursen.",
"shareSeeOnce": "Du får bare se denne lenken én gang. Pass på å kopiere den.",
"shareSeeOnce": "Du vil bare kunne se denne linken én gang. Pass på å kopiere den.",
"shareAccessHint": "Alle med denne lenken kan få tilgang til ressursen. Del forsiktig.",
"shareTokenUsage": "Se tilgangstokenbruk",
"createLink": "Opprett lenke",
@@ -179,7 +179,7 @@
"baseDomain": "Grunndomene",
"subdomnainDescription": "Underdomenet der ressursen din vil være tilgjengelig.",
"resourceRawSettings": "TCP/UDP-innstillinger",
"resourceRawSettingsDescription": "Konfigurer tilgang til ressursen din over TCP/UDP",
"resourceRawSettingsDescription": "Konfigurer hvordan din ressurs vil bli tilgjengelig over TCP/UDP. Du kartlegger ressursen til en port på vertsserveren Pangolin slik at du får tilgang til ressursene fra server-ip:mappet port.",
"protocol": "Protokoll",
"protocolSelect": "Velg en protokoll",
"resourcePortNumber": "Portnummer",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Sjekk e-posten din for tilbakestillingskoden.",
"passwordNew": "Nytt passord",
"passwordNewConfirm": "Bekreft nytt passord",
"changePassword": "Endre passord",
"changePasswordDescription": "Oppdater passordet for din konto",
"oldPassword": "Nåværende passord",
"newPassword": "Nytt passord",
"confirmNewPassword": "Bekreft nytt passord",
"changePasswordError": "Kunne ikke endre passord",
"changePasswordErrorDescription": "Det oppstod en feil under endring av passordet",
"changePasswordSuccess": "Passordet er endret",
"changePasswordSuccessDescription": "Ditt passord ble oppdatert",
"passwordExpiryRequired": "Passordutløp kreves",
"passwordExpiryDescription": "Denne organisasjonen krever at du bytter passord hver {maxDays} dag.",
"changePasswordNow": "Bytt passord nå",
"pincodeAuth": "Autentiseringskode",
"pincodeSubmit2": "Send inn kode",
"passwordResetSubmit": "Be om tilbakestilling",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Lisens",
"sidebarClients": "Klienter",
"sidebarDomains": "Domener",
"sidebarBluePrints": "Tegninger",
"blueprints": "Tegninger",
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
"blueprintAdd": "Legg til blåkopi",
"blueprintGoBack": "Se alle blåkopier",
"blueprintCreate": "Opprette mal",
"blueprintCreateDescription2": "Følg trinnene nedenfor for å opprette og bruke en ny plantegning",
"blueprintDetails": "Blåkopi detaljer",
"blueprintDetailsDescription": "Se resultatet av den påførte blåkopien og alle feil som oppstod",
"blueprintInfo": "Blåkopi informasjon",
"message": "Melding",
"blueprintContentsDescription": "Definer innhold av YAML som beskriver din infrastruktur",
"blueprintErrorCreateDescription": "Det oppstod en feil da plantegningen ble lagt til",
"blueprintErrorCreate": "Feil ved opprettelse av plantegning",
"searchBlueprintProgress": "Søk etter plantegninger...",
"appliedAt": "Anvendt på",
"source": "Kilde",
"contents": "Innhold",
"parsedContents": "Parastinnhold (kun lese)",
"enableDockerSocket": "Aktiver Docker blåkopi",
"enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.",
"enableDockerSocketLink": "Lær mer",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Det oppstod et problem med å bruke sikkerhetsnøkkelen din. Vennligst prøv igjen.",
"twoFactorRequired": "Tofaktorautentisering er påkrevd for å registrere en sikkerhetsnøkkel.",
"twoFactor": "Tofaktorautentisering",
"twoFactorAuthentication": "To-faktor autentisering",
"twoFactorDescription": "Denne organisasjonen krever to-faktor-autentisering.",
"enableTwoFactor": "Aktiver to-faktor autentisering",
"organizationSecurityPolicy": "Retningslinjer for organisasjons sikkerhet",
"organizationSecurityPolicyDescription": "Denne organisasjonen har sikkerhetskrav som må oppfylles før du får tilgang til den",
"securityRequirements": "Krav Til Sikkerhet",
"allRequirementsMet": "Alle krav er oppfylt",
"completeRequirementsToContinue": "Fullfør kravene nedenfor for å fortsette tilgangen til denne organisasjonen",
"youCanNowAccessOrganization": "Du har nå tilgang til denne organisasjonen",
"reauthenticationRequired": "Økt lengde",
"reauthenticationDescription": "Denne organisasjonen krever at du logger på alle {maxDays} dager.",
"reauthenticationDescriptionHours": "Denne organisasjonen krever at du logger inn hver {maxHours} time.",
"reauthenticateNow": "Logg inn igjen",
"adminEnabled2FaOnYourAccount": "Din administrator har aktivert tofaktorautentisering for {email}. Vennligst fullfør oppsettsprosessen for å fortsette.",
"securityKeyAdd": "Legg til sikkerhetsnøkkel",
"securityKeyRegisterTitle": "Registrer ny sikkerhetsnøkkel",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Rediger fil: docker-compose.yml",
"emailVerificationRequired": "E-postbekreftelse er nødvendig. Logg inn på nytt via {dashboardUrl}/auth/login og fullfør dette trinnet. Kom deretter tilbake her.",
"twoFactorSetupRequired": "To-faktor autentiseringsoppsett er nødvendig. Vennligst logg inn igjen via {dashboardUrl}/auth/login og fullfør dette steget. Kom deretter tilbake her.",
"additionalSecurityRequired": "Ekstra sikkerhet kreves",
"organizationRequiresAdditionalSteps": "Denne organisasjonen krever ytterligere sikkerhetstrinn før du får tilgang til ressurser.",
"completeTheseSteps": "Fullfør disse trinnene",
"enableTwoFactorAuthentication": "Aktiver to-faktor autentisering",
"completeSecuritySteps": "Fullfør sikkerhetstrinnene",
"securitySettings": "Sikkerhet innstillinger",
"securitySettingsDescription": "Konfigurere sikkerhetspolicyer for din organisasjon",
"requireTwoFactorForAllUsers": "Krev to-faktor autentisering for alle brukere",
"requireTwoFactorDescription": "Når aktivert må alle interne brukere i denne organisasjonen ha to-faktorautentisering aktivert for å få tilgang til organisasjonen.",
"requireTwoFactorDisabledDescription": "Denne funksjonen krever en gyldig lisens (Enterprise) eller aktivt abonnement (SaaS)",
"requireTwoFactorCannotEnableDescription": "Du må aktivere to-faktor-autentisering for din konto før det håndheves for alle brukere",
"maxSessionLength": "Maksimal øktlengde",
"maxSessionLengthDescription": "Angi maksimal varighet for brukerøkter. Etter denne gangen må brukerne logge inn på nytt.",
"maxSessionLengthDisabledDescription": "Denne funksjonen krever en gyldig lisens (Enterprise) eller aktivt abonnement (SaaS)",
"selectSessionLength": "Velg øktlengde",
"unenforced": "Tvungen",
"1Hour": "1 time",
"3Hours": "3 timer",
"6Hours": "6 timer",
"12Hours": "12 timer",
"1DaySession": "1 dag",
"3Days": "3 dager",
"7Days": "7 dager",
"14Days": "14 dager",
"30DaysSession": "30 dager",
"90DaysSession": "90 dager",
"180DaysSession": "180 dager",
"passwordExpiryDays": "Passord utløper",
"editPasswordExpiryDescription": "Angi antall dager før brukere må endre passordet sitt.",
"selectPasswordExpiry": "Velg passordutløp",
"30Days": "30 dager",
"1Day": "1 dag",
"60Days": "60 dager",
"90Days": "90 dager",
"180Days": "180 dager",
"1Year": "1 år",
"subscriptionBadge": "Abonnement kreves",
"securityPolicyChangeWarning": "Sikkerhetsregler forandring advarsel",
"securityPolicyChangeDescription": "Du er i ferd med å endre innstillingene for sikkerhetspolicy. Etter å ha spart må du kanskje gjenopplogge deg på for å oppfylle disse policyoppdateringene. Alle brukere som ikke samsvarer vil også måtte autentisere.",
"securityPolicyChangeConfirmMessage": "Jeg bekrefter",
"securityPolicyChangeWarningText": "Dette vil påvirke alle brukere i organisasjonen",
"authPageErrorUpdateMessage": "Det oppstod en feil under oppdatering av innstillingene for godkjenningssiden",
"authPageErrorUpdate": "Kunne ikke oppdatere autoriseringssiden",
"authPageUpdated": "Godkjenningsside oppdatert",
"healthCheckNotAvailable": "Lokal",
"rewritePath": "Omskriv sti",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Dette kan ikke angres.",
"toConfirm": "å bekrefte",
"deleteClientQuestion": "Er du sikker på at du vil fjerne klienten fra nettstedet og organisasjonen?",
"clientMessageRemove": "Når klienten er fjernet, kan den ikke lenger koble seg til nettstedet."
}
"clientMessageRemove": "Når klienten er fjernet, kan den ikke lenger koble seg til nettstedet.",
"sidebarLogs": "Logger",
"request": "Forespørsel",
"logs": "Logger",
"logsSettingsDescription": "Overvåk logger samlet fra denne orginiasjonen",
"searchLogs": "Søk i logger...",
"action": "Handling",
"actor": "Aktør",
"timestamp": "Tidsstempel",
"accessLogs": "Tilgangslogger (Automatic Translation)",
"exportCsv": "Eksportere CSV",
"actorId": "Skuespiller ID",
"allowedByRule": "Tillatt etter regel",
"allowedNoAuth": "Tillatt Ingen Auth",
"validAccessToken": "Gyldig tilgangsnøkkel",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Gyldig passord",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Ressurs blokkert",
"droppedByRule": "Legg i regelen",
"noSessions": "Ingen økter",
"temporaryRequestToken": "Midlertidig forespørsel Token",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Grunn",
"requestLogs": "Forespørselslogger (Automatic Translation)",
"host": "Vert",
"location": "Sted",
"actionLogs": "Handlingslogger",
"sidebarLogsRequest": "Forespørselslogger (Automatic Translation)",
"sidebarLogsAccess": "Tilgangslogger (Automatic Translation)",
"sidebarLogsAction": "Handlingslogger",
"logRetention": "Logg tilbaketrekning",
"logRetentionDescription": "Håndter hvor lenge ulike typer logger beholdes for denne organisasjonen, eller deaktiver dem",
"requestLogsDescription": "Se detaljerte forespørselslogger for ressurser i denne organisasjonen",
"logRetentionRequestLabel": "Be om loggoverføring",
"logRetentionRequestDescription": "Hvor lenge du vil beholde forespørselslogger",
"logRetentionAccessLabel": "Få tilgang til loggoverføring",
"logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger",
"logRetentionActionLabel": "Handlings logg nytt",
"logRetentionActionDescription": "Hvor lenge handlingen skal lagres",
"logRetentionDisabled": "Deaktivert",
"logRetention3Days": "3 dager",
"logRetention7Days": "7 dager",
"logRetention14Days": "14 dager",
"logRetention30Days": "30 dager",
"logRetention90Days": "90 dager",
"logRetentionForever": "Alltid",
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
"licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.",
"certResolver": "Sertifikat løser",
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
"selectCertResolver": "Velg sertifikatløser",
"enterCustomResolver": "Legg inn egendefinert løser",
"preferWildcardCert": "Foretrekk Wildcard sertifikat",
"unverified": "Uverifisert",
"domainSetting": "Domene innstillinger",
"domainSettingDescription": "Konfigurer innstillinger for ditt domene",
"preferWildcardCertDescription": "Forsøk på å generere et jokertegn (krever en riktig konfigurert sertifikatløsning).",
"recordName": "Lagre navn",
"auto": "Automatisk",
"TTL": "TTL",
"howToAddRecords": "Hvordan legge til poster",
"dnsRecord": "DNS registre",
"required": "Påkrevd",
"domainSettingsUpdated": "Domene innstillinger ble oppdatert",
"orgOrDomainIdMissing": "ID for organisasjon eller domene mangler",
"loadingDNSRecords": "Laster DNS-poster...",
"olmUpdateAvailableInfo": "En oppdatert versjon av Olm er tilgjengelig. Oppdater til den nyeste versjonen for å få den beste opplevelsen.",
"client": "Klient",
"proxyProtocol": "Protokoll innstillinger for Protokoll",
"proxyProtocolDescription": "Konfigurer Proxy-protokoll for å bevare klientens IP-adresser til TCP/UDP tjenester.",
"enableProxyProtocol": "Aktiver Proxy-protokoll",
"proxyProtocolInfo": "Bevar klientens IP-adresser for TCP/UDP bakover",
"proxyProtocolVersion": "Proxy protokoll versjon",
"version1": " Versjon 1 (Anbefalt)",
"version2": "Versjon 2",
"versionDescription": "Versjon 1 er tekstbasert og støttet. Versjon 2 er binært og mer effektivt, men mindre kompatibel.",
"warning": "Advarsel",
"proxyProtocolWarning": "Din backend applikasjon må være konfigurert for å godta Proxy Protokoller. Hvis din backend ikke støtter Proxy Protocol, vil aktivering av dette bryte alle tilkoblinger. Sørg for å konfigurere backend til å stole på Proxy Protokoll overskrifter fra Traefik.",
"restarting": "Restarter...",
"manual": "Manuell",
"messageSupport": "Støtte for melding",
"supportNotAvailableTitle": "Støtte ikke tilgjengelig",
"supportNotAvailableDescription": "Støtte er ikke tilgjengelig akkurat nå. Du kan sende en e-post til support@pangolin.net.",
"supportRequestSentTitle": "Supportforespørsel sendt",
"supportRequestSentDescription": "Din melding er sendt.",
"supportRequestFailedTitle": "Kunne ikke sende forespørsel",
"supportRequestFailedDescription": "En feil oppstod under sending av din forespørsel om støtte.",
"supportSubjectRequired": "Emne er påkrevd",
"supportSubjectMaxLength": "Emne må være 255 tegn eller mindre",
"supportMessageRequired": "Melding er påkrevd",
"supportReplyTo": "Svar til",
"supportSubject": "Emne",
"supportSubjectPlaceholder": "Angi emne",
"supportMessage": "Melding",
"supportMessagePlaceholder": "Skriv din melding",
"supportSending": "Sender...",
"supportSend": "Sende",
"supportMessageSent": "Melding sendt!",
"supportWillContact": "Vi kommer raskt til å ta kontakt!",
"selectLogRetention": "Velg oppbevaring av logg",
"showColumns": "Vis kolonner",
"hideColumns": "Skjul kolonner",
"columnVisibility": "Kolonne Synlighet",
"toggleColumn": "Veksle {columnName} kolonne",
"allColumns": "Alle kolonner",
"defaultColumns": "Standard kolonner",
"customizeView": "Tilpass visning",
"viewOptions": "Vis alternativer",
"selectAll": "Velg alle",
"selectNone": "Velg ingen",
"selectedResources": "Valgte ressurser",
"enableSelected": "Aktiver valgte",
"disableSelected": "Deaktiver valgte",
"checkSelectedStatus": "Kontroller status for valgte"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Vervalt in",
"neverExpire": "Nooit verlopen",
"shareExpireDescription": "Vervaltijd is hoe lang de link bruikbaar is en geeft toegang tot de bron. Na deze tijd zal de link niet meer werken en zullen gebruikers die deze link hebben gebruikt de toegang tot de pagina verliezen.",
"shareSeeOnce": "Je kunt deze koppeling alleen zien. Zorg ervoor dat je het kopieert.",
"shareSeeOnce": "U kunt deze link slechts één keer zien. Zorg ervoor dat u deze kopieert.",
"shareAccessHint": "Iedereen met deze link heeft toegang tot de bron. Deel deze met zorg.",
"shareTokenUsage": "Zie Toegangstoken Gebruik",
"createLink": "Koppeling aanmaken",
@@ -179,7 +179,7 @@
"baseDomain": "Basis domein",
"subdomnainDescription": "Het subdomein waar de bron toegankelijk is.",
"resourceRawSettings": "TCP/UDP instellingen",
"resourceRawSettingsDescription": "Stel in hoe je bron wordt benaderd via TCP/UDP",
"resourceRawSettingsDescription": "Stel in hoe uw bron wordt benaderd via TCP/UDP. Je gooit de bron toe aan een poort op de host-Pangolin server, zodat je de bron kan bereiken vanaf server-public-ip:mapped-port.",
"protocol": "Protocol",
"protocolSelect": "Selecteer een protocol",
"resourcePortNumber": "Nummer van poort",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Controleer je e-mail voor de reset code.",
"passwordNew": "Nieuw wachtwoord",
"passwordNewConfirm": "Bevestig nieuw wachtwoord",
"changePassword": "Wachtwoord wijzigen",
"changePasswordDescription": "Uw wachtwoord bijwerken",
"oldPassword": "Huidig wachtwoord",
"newPassword": "Nieuw wachtwoord",
"confirmNewPassword": "Bevestig nieuw wachtwoord",
"changePasswordError": "Wachtwoord wijzigen mislukt",
"changePasswordErrorDescription": "Er is een fout opgetreden tijdens het wijzigen van uw wachtwoord",
"changePasswordSuccess": "Wachtwoord succesvol gewijzigd",
"changePasswordSuccessDescription": "Uw wachtwoord is met succes bijgewerkt",
"passwordExpiryRequired": "Wachtwoord vervalt verplicht",
"passwordExpiryDescription": "Deze organisatie vereist dat u om de {maxDays} dagen uw wachtwoord wijzigt.",
"changePasswordNow": "Wijzig wachtwoord nu",
"pincodeAuth": "Authenticatiecode",
"pincodeSubmit2": "Code indienen",
"passwordResetSubmit": "Opnieuw instellen aanvragen",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licentie",
"sidebarClients": "Clienten",
"sidebarDomains": "Domeinen",
"sidebarBluePrints": "Blauwdrukken",
"blueprints": "Blauwdrukken",
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
"blueprintAdd": "Blauwdruk toevoegen",
"blueprintGoBack": "Bekijk alle Blauwdrukken",
"blueprintCreate": "Creëer blauwdruk",
"blueprintCreateDescription2": "Volg de onderstaande stappen om een nieuwe blauwdruk te maken en toe te passen",
"blueprintDetails": "Blauwdruk Details",
"blueprintDetailsDescription": "Bekijk het resultaat van de toegepaste blauwdruk en eventuele fouten",
"blueprintInfo": "Blauwdruk Informatie",
"message": "bericht",
"blueprintContentsDescription": "Definieer de YAML content die je infrastructuur beschrijft",
"blueprintErrorCreateDescription": "Er is een fout opgetreden bij het toepassen van de blauwdruk",
"blueprintErrorCreate": "Fout bij maken blauwdruk",
"searchBlueprintProgress": "Blauwdrukken zoeken...",
"appliedAt": "Toegepast op",
"source": "Bron",
"contents": "Inhoud",
"parsedContents": "Geparseerde inhoud (alleen lezen)",
"enableDockerSocket": "Schakel Docker Blauwdruk in",
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
"enableDockerSocketLink": "Meer informatie",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Er was een probleem met het gebruik van je beveiligingssleutel. Probeer het opnieuw.",
"twoFactorRequired": "Tweestapsverificatie is vereist om een beveiligingssleutel te registreren.",
"twoFactor": "Tweestapsverificatie",
"twoFactorAuthentication": "Tweestapsverificatie verificatie",
"twoFactorDescription": "Deze organisatie vereist tweestapsverificatie.",
"enableTwoFactor": "Tweestapsverificatie inschakelen",
"organizationSecurityPolicy": "Organisatie Veiligheidsbeleid",
"organizationSecurityPolicyDescription": "Deze organisatie heeft beveiligingsvereisten waaraan moet worden voldaan voordat u deze kunt openen",
"securityRequirements": "Veiligheidsvereisten",
"allRequirementsMet": "Aan alle vereisten is voldaan",
"completeRequirementsToContinue": "Voltooi de onderstaande vereisten om toegang te blijven krijgen tot deze organisatie",
"youCanNowAccessOrganization": "U heeft nu toegang tot deze organisatie",
"reauthenticationRequired": "Sessie Lengte",
"reauthenticationDescription": "Deze organisatie vereist dat u elke {maxDays} dagen inlogt.",
"reauthenticationDescriptionHours": "Deze organisatie vereist dat u elke {maxHours} uur inlogt.",
"reauthenticateNow": "Opnieuw inloggen",
"adminEnabled2FaOnYourAccount": "Je beheerder heeft tweestapsverificatie voor {email} ingeschakeld. Voltooi het instellingsproces om verder te gaan.",
"securityKeyAdd": "Beveiligingssleutel toevoegen",
"securityKeyRegisterTitle": "Nieuwe beveiligingssleutel registreren",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Bestand bewerken: docker-compose.yml",
"emailVerificationRequired": "E-mail verificatie is vereist. Log opnieuw in via {dashboardUrl}/auth/login voltooide deze stap. Kom daarna hier terug.",
"twoFactorSetupRequired": "Tweestapsverificatie instellen is vereist. Log opnieuw in via {dashboardUrl}/auth/login voltooide deze stap. Kom daarna hier terug.",
"additionalSecurityRequired": "Extra beveiliging vereist",
"organizationRequiresAdditionalSteps": "Deze organisatie vereist extra beveiligingsstappen voordat u toegang hebt tot de bronnen.",
"completeTheseSteps": "Voltooi deze stappen",
"enableTwoFactorAuthentication": "Tweestapsverificatie inschakelen",
"completeSecuritySteps": "Voltooi beveiligingsstappen",
"securitySettings": "Beveiliging instellingen",
"securitySettingsDescription": "Beveiligingsbeleid voor uw organisatie configureren",
"requireTwoFactorForAllUsers": "Authenticatie in twee stappen vereist voor alle gebruikers",
"requireTwoFactorDescription": "Wanneer ingeschakeld, moeten alle interne gebruikers in deze organisatie tweestapsverificatie ingeschakeld hebben om toegang te krijgen tot de organisatie.",
"requireTwoFactorDisabledDescription": "Deze functie vereist een geldig licentie (Enterprise) of actief abonnement (SaaS)",
"requireTwoFactorCannotEnableDescription": "U moet tweestapsverificatie inschakelen voor uw account voordat u deze voor alle gebruikers kan afdwingen",
"maxSessionLength": "Maximale sessielengte",
"maxSessionLengthDescription": "Stel de maximale duur van de gebruikerssessies in. Na deze tijd zullen gebruikers opnieuw moeten verifiëren.",
"maxSessionLengthDisabledDescription": "Deze functie vereist een geldig licentie (Enterprise) of actief abonnement (SaaS)",
"selectSessionLength": "Selecteer sessie lengte",
"unenforced": "Onafgedwongen",
"1Hour": "1 uur",
"3Hours": "3 uur",
"6Hours": "6 uur",
"12Hours": "12 uur",
"1DaySession": "1 dag",
"3Days": "3 dagen",
"7Days": "7 dagen",
"14Days": "14 dagen",
"30DaysSession": "30 dagen",
"90DaysSession": "90 dagen",
"180DaysSession": "180 dagen",
"passwordExpiryDays": "Wachtwoord verloopt",
"editPasswordExpiryDescription": "Stel het aantal dagen in voordat gebruikers verplicht zijn hun wachtwoord te wijzigen.",
"selectPasswordExpiry": "Selecteer wachtwoord vervaldatum",
"30Days": "30 dagen",
"1Day": "1 dag",
"60Days": "60 dagen",
"90Days": "90 dagen",
"180Days": "180 dagen",
"1Year": "1 jaar",
"subscriptionBadge": "Abonnement vereist",
"securityPolicyChangeWarning": "Waarschuwing wijzigen beveiligingsbeleid",
"securityPolicyChangeDescription": "U staat op het punt om de instellingen van het beveiligingsbeleid te wijzigen. Na het opslaan moet u zich opnieuw aanmelden om te voldoen aan deze beleidsupdates. Alle gebruikers die niet aan de voorwaarden voldoen, moeten zich ook opnieuw authenticeren.",
"securityPolicyChangeConfirmMessage": "Ik bevestig",
"securityPolicyChangeWarningText": "Dit heeft invloed op alle gebruikers in de organisatie",
"authPageErrorUpdateMessage": "Er is een fout opgetreden bij het bijwerken van de instellingen van de auth-pagina",
"authPageErrorUpdate": "Kan de autorisatiepagina niet bijwerken",
"authPageUpdated": "Auth-pagina succesvol bijgewerkt",
"healthCheckNotAvailable": "Lokaal",
"rewritePath": "Herschrijf Pad",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Dit kan niet ongedaan worden gemaakt.",
"toConfirm": "om te bevestigen",
"deleteClientQuestion": "Weet u zeker dat u de client van de site en organisatie wilt verwijderen?",
"clientMessageRemove": "Eenmaal verwijderd, kan de client geen verbinding meer maken met de site."
}
"clientMessageRemove": "Eenmaal verwijderd, kan de client geen verbinding meer maken met de site.",
"sidebarLogs": "Logboeken",
"request": "Aanvragen",
"logs": "Logboeken",
"logsSettingsDescription": "Monitor logs verzameld van deze orginiatie",
"searchLogs": "Logboeken zoeken...",
"action": "actie",
"actor": "Acteur",
"timestamp": "Artikeldatering",
"accessLogs": "Toegang tot logboek",
"exportCsv": "Exporteren als CSV",
"actorId": "Acteur ID",
"allowedByRule": "Toegestaan door regel",
"allowedNoAuth": "Toegestaan geen authenticatie",
"validAccessToken": "Geldige toegangstoken",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Geldig wachtwoord",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Bron geblokkeerd",
"droppedByRule": "Achtergelaten door regel",
"noSessions": "Geen sessies",
"temporaryRequestToken": "Tijdelijk verzoek token",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP-adres",
"reason": "Reden",
"requestLogs": "Logboeken aanvragen",
"host": "Hostnaam",
"location": "Locatie",
"actionLogs": "Actie logs",
"sidebarLogsRequest": "Logboeken aanvragen",
"sidebarLogsAccess": "Toegang tot logboek",
"sidebarLogsAction": "Actie logs",
"logRetention": "Log bewaring",
"logRetentionDescription": "Beheren hoe lang verschillende soorten logs bewaard worden voor deze organisatie of schakel ze uit",
"requestLogsDescription": "Bekijk gedetailleerde verzoeklogboeken voor resources in deze organisatie",
"logRetentionRequestLabel": "Logboekbewaring aanvragen",
"logRetentionRequestDescription": "Hoe lang de aanvraaglogboeken te behouden",
"logRetentionAccessLabel": "Toegang logboek bewaring",
"logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven",
"logRetentionActionLabel": "Actie log bewaring",
"logRetentionActionDescription": "Hoe lang de action logs behouden moeten blijven",
"logRetentionDisabled": "Uitgeschakeld",
"logRetention3Days": "3 dagen",
"logRetention7Days": "7 dagen",
"logRetention14Days": "14 dagen",
"logRetention30Days": "30 dagen",
"logRetention90Days": "90 dagen",
"logRetentionForever": "Voor altijd",
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
"licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.",
"certResolver": "Certificaat Resolver",
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
"selectCertResolver": "Certificaat Resolver selecteren",
"enterCustomResolver": "Aangepaste Oplossing invoeren",
"preferWildcardCert": "Bij voorkeur Wildcard Certificaat",
"unverified": "Ongeverifieerd",
"domainSetting": "Domein instellingen",
"domainSettingDescription": "Configureer instellingen voor uw domein",
"preferWildcardCertDescription": "Poging om een certificaat met een wildcard te genereren (vereist een correct geconfigureerde certificaatresolver).",
"recordName": "Record Naam",
"auto": "Automatisch",
"TTL": "TTL",
"howToAddRecords": "Hoe voeg ik Records toe",
"dnsRecord": "DNS Records",
"required": "vereist",
"domainSettingsUpdated": "Domeininstellingen succesvol bijgewerkt",
"orgOrDomainIdMissing": "Organisatie of domein ID ontbreekt",
"loadingDNSRecords": "DNS-records laden...",
"olmUpdateAvailableInfo": "Er is een bijgewerkte versie van Olm beschikbaar. Update alstublieft naar de nieuwste versie voor de beste ervaring.",
"client": "Klant",
"proxyProtocol": "Proxy Protocol Instellingen",
"proxyProtocolDescription": "Proxyprotocol configureren om de IP-adressen van de client voor TCP/UDP-diensten te bewaren.",
"enableProxyProtocol": "Proxy Protocol inschakelen",
"proxyProtocolInfo": "Behoud IP adressen van de client voor TCP/UDP backends",
"proxyProtocolVersion": "Proxy Protocol Versie",
"version1": " Versie 1 (Aanbevolen)",
"version2": "Versie 2",
"versionDescription": "Versie 1 is text-based en breed ondersteund. Versie 2 is binair en efficiënter maar minder compatibel.",
"warning": "Waarschuwing",
"proxyProtocolWarning": "Je backend applicatie moet worden geconfigureerd om connecties met Proxy Protocol te accepteren. Als je backend geen Proxy Protocol ondersteunt, zal het inschakelen van dit alle verbindingen verbreken. Zorg ervoor dat je je backend configureert om Proxy Protocol headers van Traefik.",
"restarting": "Herstarten...",
"manual": "Handleiding",
"messageSupport": "Bericht ondersteuning",
"supportNotAvailableTitle": "Ondersteuning niet beschikbaar",
"supportNotAvailableDescription": "Ondersteuning is momenteel niet beschikbaar. U kunt een e-mail sturen naar support@pangolin.net.",
"supportRequestSentTitle": "Ondersteuningsverzoek verzonden",
"supportRequestSentDescription": "Uw bericht is succesvol verzonden.",
"supportRequestFailedTitle": "Kon aanvraag niet verzenden",
"supportRequestFailedDescription": "Er is een fout opgetreden tijdens het verzenden van uw supportverzoek.",
"supportSubjectRequired": "Onderwerp is vereist",
"supportSubjectMaxLength": "Onderwerp moet 255 tekens of minder lang zijn",
"supportMessageRequired": "Bericht is vereist",
"supportReplyTo": "Antwoord aan",
"supportSubject": "Onderwerp",
"supportSubjectPlaceholder": "Onderwerp invoeren",
"supportMessage": "bericht",
"supportMessagePlaceholder": "Voer uw bericht in",
"supportSending": "Verzenden...",
"supportSend": "Verzenden",
"supportMessageSent": "Bericht verzonden!",
"supportWillContact": "We nemen binnenkort contact met u op!",
"selectLogRetention": "Selecteer log retentie",
"showColumns": "Kolommen weergeven",
"hideColumns": "Kolommen verbergen",
"columnVisibility": "Zichtbaarheid kolommen",
"toggleColumn": "{columnName} kolom in-/uitschakelen",
"allColumns": "Alle kolommen",
"defaultColumns": "Standaard Kolommen",
"customizeView": "Weergave aanpassen",
"viewOptions": "Bekijk opties",
"selectAll": "Alles selecteren",
"selectNone": "Niets selecteren",
"selectedResources": "Geselecteerde bronnen",
"enableSelected": "Selectie inschakelen",
"disableSelected": "Selectie uitschakelen",
"checkSelectedStatus": "Controleer de status van de geselecteerde"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Wygasa za",
"neverExpire": "Nigdy nie wygasa",
"shareExpireDescription": "Czas wygaśnięcia to jak długo link będzie mógł być użyty i zapewni dostęp do zasobu. Po tym czasie link nie będzie już działał, a użytkownicy, którzy użyli tego linku, utracą dostęp do zasobu.",
"shareSeeOnce": "Możesz zobaczyć tylko ten link. Upewnij się, że go skopiowało.",
"shareSeeOnce": "Możesz zobaczyć ten link tylko raz. Pamiętaj, aby go skopiować.",
"shareAccessHint": "Każdy z tym linkiem może uzyskać dostęp do zasobu. Podziel się nim ostrożnie.",
"shareTokenUsage": "Zobacz użycie tokenu dostępu",
"createLink": "Utwórz link",
@@ -179,7 +179,7 @@
"baseDomain": "Bazowa domena",
"subdomnainDescription": "Poddomena, w której twój zasób będzie dostępny.",
"resourceRawSettings": "Ustawienia TCP/UDP",
"resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP",
"resourceRawSettingsDescription": "Skonfiguruj jak twój zasób będzie dostępny przez TCP/UDP. Zmapujesz zasób do portu na serwerze hosta Pangolin, dzięki czemu możesz uzyskać dostęp do zasobu z serwera-public ip:mapped-port.",
"protocol": "Protokół",
"protocolSelect": "Wybierz protokół",
"resourcePortNumber": "Numer portu",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Sprawdź swój e-mail, aby znaleźć kod resetowania.",
"passwordNew": "Nowe hasło",
"passwordNewConfirm": "Potwierdź nowe hasło",
"changePassword": "Zmień hasło",
"changePasswordDescription": "Zaktualizuj hasło do konta",
"oldPassword": "Bieżące hasło",
"newPassword": "Nowe hasło",
"confirmNewPassword": "Potwierdź nowe hasło",
"changePasswordError": "Nie udało się zmienić hasła",
"changePasswordErrorDescription": "Wystąpił błąd podczas zmiany hasła",
"changePasswordSuccess": "Hasło zostało pomyślnie zmienione",
"changePasswordSuccessDescription": "Twoje hasło zostało pomyślnie zaktualizowane",
"passwordExpiryRequired": "Wymagane hasło wygasające",
"passwordExpiryDescription": "Organizacja wymaga zmiany hasła co {maxDays} dni.",
"changePasswordNow": "Zmień hasło teraz",
"pincodeAuth": "Kod uwierzytelniający",
"pincodeSubmit2": "Wyślij kod",
"passwordResetSubmit": "Zażądaj resetowania",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Licencja",
"sidebarClients": "Klientami",
"sidebarDomains": "Domeny",
"sidebarBluePrints": "Schematy",
"blueprints": "Schematy",
"blueprintsDescription": "Zastosuj konfiguracje deklaracyjne i wyświetl poprzednie operacje",
"blueprintAdd": "Dodaj schemat",
"blueprintGoBack": "Zobacz wszystkie schematy",
"blueprintCreate": "Utwórz schemat",
"blueprintCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i zastosować nowy schemat",
"blueprintDetails": "Szczegóły Projektu",
"blueprintDetailsDescription": "Zobacz wynik zastosowanego schematu i wszelkie błędy, które wystąpiły",
"blueprintInfo": "Informacje o projekcie",
"message": "Wiadomość",
"blueprintContentsDescription": "Zdefiniuj zawartość YAML opisującą Twoją infrastrukturę",
"blueprintErrorCreateDescription": "Wystąpił błąd podczas stosowania schematu",
"blueprintErrorCreate": "Błąd podczas tworzenia schematu",
"searchBlueprintProgress": "Szukaj schematów...",
"appliedAt": "Zastosowano",
"source": "Źródło",
"contents": "Treść",
"parsedContents": "Przetworzona zawartość (tylko do odczytu)",
"enableDockerSocket": "Włącz schemat dokera",
"enableDockerSocketDescription": "Włącz etykietowanie kieszeni dokującej dla etykiet schematów. Ścieżka do gniazda musi być dostarczona do Newt.",
"enableDockerSocketLink": "Dowiedz się więcej",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Wystąpił problem z używaniem klucza bezpieczeństwa. Proszę spróbować ponownie.",
"twoFactorRequired": "Uwierzytelnianie dwuskładnikowe jest wymagane do zarejestrowania klucza bezpieczeństwa.",
"twoFactor": "Uwierzytelnianie dwuskładnikowe",
"twoFactorAuthentication": "Uwierzytelnianie dwuetapowe",
"twoFactorDescription": "Ta organizacja wymaga uwierzytelniania dwuskładnikowego.",
"enableTwoFactor": "Włącz uwierzytelnianie dwuetapowe",
"organizationSecurityPolicy": "Polityka bezpieczeństwa organizacji",
"organizationSecurityPolicyDescription": "Ta organizacja ma wymagania bezpieczeństwa, które muszą być spełnione, zanim będziesz mógł uzyskać dostęp do niej",
"securityRequirements": "Wymogi bezpieczeństwa",
"allRequirementsMet": "Wszystkie wymagania zostały spełnione",
"completeRequirementsToContinue": "Wypełnij poniższe wymagania, aby kontynuować dostęp do tej organizacji",
"youCanNowAccessOrganization": "Teraz możesz uzyskać dostęp do tej organizacji",
"reauthenticationRequired": "Długość sesji",
"reauthenticationDescription": "Organizacja wymaga logowania co {maxDays} dni.",
"reauthenticationDescriptionHours": "Organizacja wymaga logowania co {maxHours} godzin.",
"reauthenticateNow": "Zaloguj się ponownie",
"adminEnabled2FaOnYourAccount": "Twój administrator włączył uwierzytelnianie dwuskładnikowe dla {email}. Proszę ukończyć proces konfiguracji, aby kontynuować.",
"securityKeyAdd": "Dodaj klucz bezpieczeństwa",
"securityKeyRegisterTitle": "Zarejestruj nowy klucz bezpieczeństwa",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Edytuj plik: docker-compose.yml",
"emailVerificationRequired": "Weryfikacja adresu e-mail jest wymagana. Zaloguj się ponownie przez {dashboardUrl}/auth/login zakończył ten krok. Następnie wróć tutaj.",
"twoFactorSetupRequired": "Konfiguracja uwierzytelniania dwuskładnikowego jest wymagana. Zaloguj się ponownie przez {dashboardUrl}/auth/login dokończ ten krok. Następnie wróć tutaj.",
"additionalSecurityRequired": "Wymagane dodatkowe zabezpieczenie",
"organizationRequiresAdditionalSteps": "Ta organizacja wymaga dodatkowych kroków bezpieczeństwa, zanim będziesz mógł uzyskać dostęp do zasobów.",
"completeTheseSteps": "Wykonaj te kroki",
"enableTwoFactorAuthentication": "Włącz uwierzytelnianie dwuskładnikowe",
"completeSecuritySteps": "Zakończ kroki bezpieczeństwa",
"securitySettings": "Ustawienia zabezpieczeń",
"securitySettingsDescription": "Skonfiguruj politykę bezpieczeństwa dla Twojej organizacji",
"requireTwoFactorForAllUsers": "Wymagaj uwierzytelniania dwuetapowego dla wszystkich użytkowników",
"requireTwoFactorDescription": "Po włączeniu wszyscy użytkownicy wewnętrzni w tej organizacji muszą mieć włączone uwierzytelnianie dwuskładnikowe, aby uzyskać dostęp do organizacji.",
"requireTwoFactorDisabledDescription": "Ta funkcja wymaga poprawnej licencji (Enterprise) lub aktywnej subskrypcji (SaaaS)",
"requireTwoFactorCannotEnableDescription": "Musisz włączyć uwierzytelnianie dwuskładnikowe dla swojego konta przed wymuszaniem go dla wszystkich użytkowników",
"maxSessionLength": "Maksymalna długość sesji",
"maxSessionLengthDescription": "Ustaw maksymalny czas trwania sesji użytkownika. Po tym czasie użytkownicy będą musieli ponownie uwierzytelniać.",
"maxSessionLengthDisabledDescription": "Ta funkcja wymaga poprawnej licencji (Enterprise) lub aktywnej subskrypcji (SaaaS)",
"selectSessionLength": "Wybierz długość sesji",
"unenforced": "Niewymuszony",
"1Hour": "1 godzina",
"3Hours": "3 godziny",
"6Hours": "6 godzin",
"12Hours": "12 godzin",
"1DaySession": "1 dzień",
"3Days": "3 dni",
"7Days": "7 dni",
"14Days": "14 dni",
"30DaysSession": "30 dni",
"90DaysSession": "90 dni",
"180DaysSession": "180 dni",
"passwordExpiryDays": "Hasło wygasa",
"editPasswordExpiryDescription": "Ustaw liczbę dni zanim użytkownicy będą musieli zmienić swoje hasło.",
"selectPasswordExpiry": "Wybierz wygasanie hasła",
"30Days": "30 dni",
"1Day": "1 dzień",
"60Days": "60 dni",
"90Days": "90 dni",
"180Days": "180 dni",
"1Year": "1 rok",
"subscriptionBadge": "Wymagana subskrypcja",
"securityPolicyChangeWarning": "Ostrzeżenie o zmianach w polityce bezpieczeństwa",
"securityPolicyChangeDescription": "Zamierzasz zmienić ustawienia polityki bezpieczeństwa. Po zapisaniu konieczne może być ponowne uwierzytelnienie w celu zapewnienia zgodności z tymi aktualizacjami polityki. Wszyscy użytkownicy, którzy nie są zgodni, będą również musieli ponownie uwierzytelniać.",
"securityPolicyChangeConfirmMessage": "Potwierdzam",
"securityPolicyChangeWarningText": "To wpłynie na wszystkich użytkowników w organizacji",
"authPageErrorUpdateMessage": "Wystąpił błąd podczas aktualizacji ustawień strony uwierzytelniania",
"authPageErrorUpdate": "Nie można zaktualizować strony uwierzytelniania",
"authPageUpdated": "Strona uwierzytelniania została pomyślnie zaktualizowana",
"healthCheckNotAvailable": "Lokalny",
"rewritePath": "Przepis Ścieżki",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Tej operacji nie można cofnąć.",
"toConfirm": "potwierdzić",
"deleteClientQuestion": "Czy na pewno chcesz usunąć klienta z witryny i organizacji?",
"clientMessageRemove": "Po usunięciu, klient nie będzie już mógł połączyć się z witryną."
}
"clientMessageRemove": "Po usunięciu, klient nie będzie już mógł połączyć się z witryną.",
"sidebarLogs": "Logi",
"request": "Żądanie",
"logs": "Logi",
"logsSettingsDescription": "Monitoruj logi zebrane z tej orginizacji",
"searchLogs": "Szukaj dzienników...",
"action": "Akcja",
"actor": "Aktor",
"timestamp": "Znacznik czasu",
"accessLogs": "Logi dostępu",
"exportCsv": "Eksportuj CSV",
"actorId": "Identyfikator podmiotu",
"allowedByRule": "Dozwolone przez regułę",
"allowedNoAuth": "Dozwolone Brak Auth",
"validAccessToken": "Ważny token dostępu",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Prawidłowe hasło",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Zasób zablokowany",
"droppedByRule": "Upuszczone przez regułę",
"noSessions": "Brak sesji",
"temporaryRequestToken": "Tymczasowy token żądania",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Powód",
"requestLogs": "Dzienniki żądań",
"host": "Host",
"location": "Lokalizacja",
"actionLogs": "Dzienniki działań",
"sidebarLogsRequest": "Dzienniki żądań",
"sidebarLogsAccess": "Logi dostępu",
"sidebarLogsAction": "Dzienniki działań",
"logRetention": "Zachowanie dziennika",
"logRetentionDescription": "Zarządzaj jak długo różne typy logów są zachowane dla tej organizacji lub wyłącz je",
"requestLogsDescription": "Zobacz szczegółowe dzienniki żądań zasobów w tej organizacji",
"logRetentionRequestLabel": "Zachowanie dziennika żądań",
"logRetentionRequestDescription": "Jak długo zachować dzienniki żądań",
"logRetentionAccessLabel": "Zachowanie dziennika dostępu",
"logRetentionAccessDescription": "Jak długo zachować dzienniki dostępu",
"logRetentionActionLabel": "Zachowanie dziennika akcji",
"logRetentionActionDescription": "Jak długo zachować dzienniki akcji",
"logRetentionDisabled": "Wyłączone",
"logRetention3Days": "3 dni",
"logRetention7Days": "7 dni",
"logRetention14Days": "14 dni",
"logRetention30Days": "30 dni",
"logRetention90Days": "90 dni",
"logRetentionForever": "Na zawsze",
"actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji",
"accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji",
"licenseRequiredToUse": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.",
"certResolver": "Rozwiązywanie certyfikatów",
"certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.",
"selectCertResolver": "Wybierz Resolver certyfikatów",
"enterCustomResolver": "Wprowadź niestandardowy Resolver",
"preferWildcardCert": "Preferuj Certyfikat Wildcard",
"unverified": "Niezweryfikowane",
"domainSetting": "Ustawienia domeny",
"domainSettingDescription": "Skonfiguruj ustawienia domeny",
"preferWildcardCertDescription": "Próba wygenerowania certyfikatu wieloznacznego (wymaga poprawnie skonfigurowanego resolwera certyfikatów).",
"recordName": "Nazwa rekordu",
"auto": "Auto",
"TTL": "TTL",
"howToAddRecords": "Jak dodać rekordy",
"dnsRecord": "Wpisy DNS",
"required": "Wymagane",
"domainSettingsUpdated": "Ustawienia domeny zaktualizowane pomyślnie",
"orgOrDomainIdMissing": "Brakuje identyfikatora organizacji lub domeny",
"loadingDNSRecords": "Ładowanie rekordów DNS...",
"olmUpdateAvailableInfo": "Dostępna jest zaktualizowana wersja Olm. Zaktualizuj do najnowszej wersji, aby uzyskać najlepsze doświadczenia.",
"client": "Klient",
"proxyProtocol": "Ustawienia protokołu proxy",
"proxyProtocolDescription": "Skonfiguruj protokół Proxy aby zachować adresy IP klienta dla usług TCP/UDP.",
"enableProxyProtocol": "Włącz protokół proxy",
"proxyProtocolInfo": "Zachowaj adresy IP klienta dla backendów TCP/UDP",
"proxyProtocolVersion": "Wersja protokołu proxy",
"version1": " Wersja 1 (zalecane)",
"version2": "Wersja 2",
"versionDescription": "Wersja 1 jest oparta na tekście i szeroko wspierana. Wersja 2 jest binarna i bardziej efektywna, ale mniej kompatybilna.",
"warning": "Ostrzeżenie",
"proxyProtocolWarning": "Twoja aplikacja backend musi być skonfigurowana tak, aby przyjmować połączenia z protokołem proxy. Jeśli Twój backend nie obsługuje protokołu proxy, włączenie to spowoduje przerwanie wszystkich połączeń. Upewnij się, że konfiguracja twojego backendu do zaufanych nagłówków protokołu proxy z Traefik.",
"restarting": "Restartowanie...",
"manual": "Ręcznie",
"messageSupport": "Obsługa wiadomości",
"supportNotAvailableTitle": "Wsparcie niedostępne",
"supportNotAvailableDescription": "Wsparcie nie jest teraz dostępne. Możesz wysłać e-mail na adres support@pangolin.net.",
"supportRequestSentTitle": "Prośba o wsparcie wysłana",
"supportRequestSentDescription": "Wiadomość została wysłana pomyślnie.",
"supportRequestFailedTitle": "Nie udało się wysłać żądania",
"supportRequestFailedDescription": "Wystąpił błąd podczas wysyłania prośby o wsparcie.",
"supportSubjectRequired": "Temat jest wymagany",
"supportSubjectMaxLength": "Temat musi mieć 255 znaków lub mniej",
"supportMessageRequired": "Wiadomość jest wymagana",
"supportReplyTo": "Odpowiedź do",
"supportSubject": "Temat",
"supportSubjectPlaceholder": "Wprowadź temat",
"supportMessage": "Wiadomość",
"supportMessagePlaceholder": "Wprowadź swoją wiadomość",
"supportSending": "Wysyłanie...",
"supportSend": "Wyślij",
"supportMessageSent": "Wiadomość wysłana!",
"supportWillContact": "Wkrótce będziemy w kontakcie!",
"selectLogRetention": "Wybierz zatrzymanie dziennika",
"showColumns": "Pokaż kolumny",
"hideColumns": "Ukryj kolumny",
"columnVisibility": "Widoczność kolumn",
"toggleColumn": "Przełącz kolumnę {columnName}",
"allColumns": "Wszystkie kolumny",
"defaultColumns": "Kolumny domyślne",
"customizeView": "Dostosuj widok",
"viewOptions": "Opcje widoku",
"selectAll": "Zaznacz wszystko",
"selectNone": "Nie wybierz żadnego",
"selectedResources": "Wybrane Zasoby",
"enableSelected": "Włącz zaznaczone",
"disableSelected": "Wyłącz zaznaczone",
"checkSelectedStatus": "Sprawdź status zaznaczonych"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "Domínio Base",
"subdomnainDescription": "O subdomínio onde seu recurso estará acessível.",
"resourceRawSettings": "Configurações TCP/UDP",
"resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP",
"resourceRawSettingsDescription": "Configure como seu recurso será acessado sobre TCP/UDP. Você mapeia o recurso para uma porta no servidor Pangolin do hospedeiro, para que você possa acessar o recurso do server-public-ip:mapped-port.",
"protocol": "Protocolo",
"protocolSelect": "Selecione um protocolo",
"resourcePortNumber": "Número da Porta",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Verifique o seu email para obter o código de redefinição.",
"passwordNew": "Nova Palavra-passe",
"passwordNewConfirm": "Confirmar Nova Palavra-passe",
"changePassword": "Mudar a senha",
"changePasswordDescription": "Atualize a senha da sua conta",
"oldPassword": "Palavra-passe Atual",
"newPassword": "Nova Palavra-Passe",
"confirmNewPassword": "Confirme a Nova Senha",
"changePasswordError": "Falha ao alterar a senha",
"changePasswordErrorDescription": "Ocorreu um erro ao alterar sua senha",
"changePasswordSuccess": "Senha alterada com sucesso",
"changePasswordSuccessDescription": "Sua senha foi atualizada com sucesso",
"passwordExpiryRequired": "Expiração de senha necessária",
"passwordExpiryDescription": "Esta organização exige que você altere sua senha a cada {maxDays} dias.",
"changePasswordNow": "Alterar a senha agora",
"pincodeAuth": "Código do Autenticador",
"pincodeSubmit2": "Submeter Código",
"passwordResetSubmit": "Solicitar Redefinição",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Tipo:",
"sidebarClients": "Clientes",
"sidebarDomains": "Domínios",
"sidebarBluePrints": "Diagramas",
"blueprints": "Diagramas",
"blueprintsDescription": "Aplicar configurações declarativas e ver execuções anteriores",
"blueprintAdd": "Adicionar Diagrama",
"blueprintGoBack": "Ver todos os Diagramas",
"blueprintCreate": "Criar Diagrama",
"blueprintCreateDescription2": "Siga as etapas abaixo para criar e aplicar um novo diagrama",
"blueprintDetails": "Detalhes do Diagrama",
"blueprintDetailsDescription": "Veja o resultado do diagrama aplicado e todos os erros que ocorreram",
"blueprintInfo": "Informação do Diagrama",
"message": "mensagem",
"blueprintContentsDescription": "Defina o conteúdo YAML descrevendo a sua infraestrutura",
"blueprintErrorCreateDescription": "Ocorreu um erro ao aplicar o diagrama",
"blueprintErrorCreate": "Erro ao criar diagrama",
"searchBlueprintProgress": "Pesquisar diagramas...",
"appliedAt": "Aplicado em",
"source": "fonte",
"contents": "Conteúdo",
"parsedContents": "Conteúdo analisado (Somente Leitura)",
"enableDockerSocket": "Habilitar o Diagrama Docker",
"enableDockerSocketDescription": "Ativar a scraping de rótulo Docker para rótulos de diagramas. Caminho de Socket deve ser fornecido para Newt.",
"enableDockerSocketLink": "Saiba mais",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Houve um problema ao usar sua chave de segurança. Tente novamente.",
"twoFactorRequired": "A autenticação de dois fatores é necessária para registrar uma chave de segurança.",
"twoFactor": "Autenticação de Dois Fatores",
"twoFactorAuthentication": "Autenticação dupla",
"twoFactorDescription": "Esta organização requer autenticação de dois fatores.",
"enableTwoFactor": "Ativar autenticação dupla",
"organizationSecurityPolicy": "Política de Segurança da Organização",
"organizationSecurityPolicyDescription": "Esta organização tem requisitos de segurança que precisam ser cumpridos antes que você possa acessá-la",
"securityRequirements": "Requisitos De Segurança",
"allRequirementsMet": "Todos os requisitos foram cumpridos",
"completeRequirementsToContinue": "Preencha os requisitos abaixo para continuar acessando esta organização",
"youCanNowAccessOrganization": "Agora você pode acessar esta organização",
"reauthenticationRequired": "Comprimento da sessão",
"reauthenticationDescription": "Esta organização requer que você faça login a cada {maxDays} dias.",
"reauthenticationDescriptionHours": "Esta organização exige que você faça login a cada {maxHours} horas.",
"reauthenticateNow": "Iniciar sessão novamente",
"adminEnabled2FaOnYourAccount": "Seu administrador ativou a autenticação de dois fatores para {email}. Complete o processo de configuração para continuar.",
"securityKeyAdd": "Adicionar Chave de Segurança",
"securityKeyRegisterTitle": "Registrar Nova Chave de Segurança",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Editar arquivo: docker-compose.yml",
"emailVerificationRequired": "Verificação de e-mail é necessária. Por favor, faça login novamente via {dashboardUrl}/auth/login conclui esta etapa. Em seguida, volte aqui.",
"twoFactorSetupRequired": "Configuração de autenticação de dois fatores é necessária. Por favor, entre novamente via {dashboardUrl}/auth/login conclua este passo. Em seguida, volte aqui.",
"additionalSecurityRequired": "Segurança adicional necessária",
"organizationRequiresAdditionalSteps": "Esta organização requer etapas de segurança adicionais antes que você possa acessar os recursos.",
"completeTheseSteps": "Conclua estas etapas",
"enableTwoFactorAuthentication": "Ativar autenticação de dois fatores",
"completeSecuritySteps": "Passos de segurança completos",
"securitySettings": "Configurações de Segurança",
"securitySettingsDescription": "Configurar políticas de segurança para a sua organização",
"requireTwoFactorForAllUsers": "Exigir autenticação dupla para todos os usuários",
"requireTwoFactorDescription": "Quando ativado, todos os usuários internos nesta organização devem ter a autenticação de dois fatores ativada para acessar a organização.",
"requireTwoFactorDisabledDescription": "Este recurso requer uma licença válida (Enterprise) ou assinatura ativa (SaaS)",
"requireTwoFactorCannotEnableDescription": "Você deve ativar a autenticação de dois fatores para sua conta antes de aplicá-la para todos os usuários",
"maxSessionLength": "Comprimento Máximo da Sessão",
"maxSessionLengthDescription": "Definir a duração máxima para as sessões dos usuários. Após esse tempo, os usuários precisarão autenticar novamente.",
"maxSessionLengthDisabledDescription": "Este recurso requer uma licença válida (Enterprise) ou assinatura ativa (SaaS)",
"selectSessionLength": "Selecionar duração da sessão",
"unenforced": "Inforçado",
"1Hour": "number@@0 horas",
"3Hours": "3 horas",
"6Hours": "6 horas",
"12Hours": "12 horas",
"1DaySession": "1 dia",
"3Days": "3 dias",
"7Days": "7 dias",
"14Days": "14 dias",
"30DaysSession": "30 dias",
"90DaysSession": "90 dias",
"180DaysSession": "180 dias",
"passwordExpiryDays": "Expiração da Senha",
"editPasswordExpiryDescription": "Defina o número de dias antes que os usuários sejam obrigados a mudar sua senha.",
"selectPasswordExpiry": "Selecione a senha expirada",
"30Days": "30 dias",
"1Day": "1 dia",
"60Days": "60 dias",
"90Days": "90 dias",
"180Days": "180 dias",
"1Year": "1 ano",
"subscriptionBadge": "Assinatura requerida",
"securityPolicyChangeWarning": "Aviso de Mudança da Política de Segurança",
"securityPolicyChangeDescription": "Você está prestes a alterar as configurações da política de segurança. Depois de salvar, talvez você precise se autenticar novamente para cumprir estas atualizações de política. Todos os usuários que não estiverem em conformidade também precisarão reautenticar.",
"securityPolicyChangeConfirmMessage": "Eu confirmo",
"securityPolicyChangeWarningText": "Isso afetará todos os usuários da organização",
"authPageErrorUpdateMessage": "Ocorreu um erro ao atualizar as configurações da página de autenticação",
"authPageErrorUpdate": "Não é possível atualizar a página de autenticação",
"authPageUpdated": "Página de autenticação atualizada com sucesso",
"healthCheckNotAvailable": "Localização",
"rewritePath": "Reescrever Caminho",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Isso não pode ser desfeito.",
"toConfirm": "para confirmar",
"deleteClientQuestion": "Você tem certeza que deseja remover o cliente do site e da organização?",
"clientMessageRemove": "Depois de removido, o cliente não poderá mais se conectar ao site."
}
"clientMessageRemove": "Depois de removido, o cliente não poderá mais se conectar ao site.",
"sidebarLogs": "Registros",
"request": "Pedir",
"logs": "Registros",
"logsSettingsDescription": "Monitorar logs coletados desta orginização",
"searchLogs": "Pesquisar registros...",
"action": "Acão",
"actor": "Ator",
"timestamp": "Timestamp",
"accessLogs": "Logs de Acesso",
"exportCsv": "Exportar como CSV",
"actorId": "ID do ator",
"allowedByRule": "Permitido por regra",
"allowedNoAuth": "Não Permitido Nenhuma Autenticação",
"validAccessToken": "Token de acesso válido",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Senha válida",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Recurso bloqueado",
"droppedByRule": "Derrubado pela regra",
"noSessions": "Sem Sessões",
"temporaryRequestToken": "Token de solicitação temporária",
"noMoreAuthMethods": "No Valid Auth",
"ip": "PI",
"reason": "Motivo",
"requestLogs": "Registro de pedidos",
"host": "Servidor",
"location": "Local:",
"actionLogs": "Logs de Ações",
"sidebarLogsRequest": "Registro de pedidos",
"sidebarLogsAccess": "Logs de Acesso",
"sidebarLogsAction": "Logs de Ações",
"logRetention": "Retenção de Log",
"logRetentionDescription": "Gerenciar quanto tempo os diferentes tipos de logs são mantidos para esta organização ou desativá-los",
"requestLogsDescription": "Ver registros de pedidos detalhados de recursos nesta organização",
"logRetentionRequestLabel": "Solicitar retenção de registro",
"logRetentionRequestDescription": "Por quanto tempo manter os registros de pedidos",
"logRetentionAccessLabel": "Retenção de Log de Acesso",
"logRetentionAccessDescription": "Por quanto tempo manter os registros de acesso",
"logRetentionActionLabel": "Ação de Retenção no Log",
"logRetentionActionDescription": "Por quanto tempo manter os registros de ação",
"logRetentionDisabled": "Desabilitado",
"logRetention3Days": "3 dias",
"logRetention7Days": "7 dias",
"logRetention14Days": "14 dias",
"logRetention30Days": "30 dias",
"logRetention90Days": "90 dias",
"logRetentionForever": "Permanentemente",
"actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização",
"accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização",
"licenseRequiredToUse": "É necessária uma licença empresarial para usar esse recurso.",
"certResolver": "Resolvedor de Certificado",
"certResolverDescription": "Selecione o resolvedor de certificados para este recurso.",
"selectCertResolver": "Selecionar solucionador de certificado",
"enterCustomResolver": "Inserir Resolvedor Personalizado",
"preferWildcardCert": "Prefere Certificado Wildcard",
"unverified": "Não verificado",
"domainSetting": "Configurações do domínio",
"domainSettingDescription": "Configure as configurações para o seu domínio",
"preferWildcardCertDescription": "Tentativa de gerar um certificado coringa (requer um resolvedor de certificado devidamente configurado).",
"recordName": "Nome da gravação",
"auto": "Automático",
"TTL": "TTL",
"howToAddRecords": "Como adicionar registros",
"dnsRecord": "Registros DNS",
"required": "Obrigatório",
"domainSettingsUpdated": "Configurações de domínio atualizadas com sucesso",
"orgOrDomainIdMissing": "ID da organização ou domínio está faltando",
"loadingDNSRecords": "Carregando registros DNS...",
"olmUpdateAvailableInfo": "Uma versão atualizada do Olm está disponível. Atualize para a versão mais recente para ter a melhor experiência.",
"client": "Cliente",
"proxyProtocol": "Configurações de Protocolo Proxy",
"proxyProtocolDescription": "Configurar o protocolo Proxy para preservar endereços IP do cliente para serviços TCP/UDP.",
"enableProxyProtocol": "Habilitar protocolo proxy",
"proxyProtocolInfo": "Preservar endereços IP do cliente para backends TCP/UDP",
"proxyProtocolVersion": "Versão do Protocolo Proxy",
"version1": " Versão 1 (recomendado)",
"version2": "Versão 2",
"versionDescription": "A versão 1 é baseada em texto e amplamente suportada. A versão 2 é binária e mais eficiente, mas menos compatível.",
"warning": "ATENÇÃO",
"proxyProtocolWarning": "Seu aplicativo de backend deve ser configurado para aceitar conexões de protocolo de proxy. Se o seu backend não suportar o protocolo de protocolo, habilitando isso quebrará todas as conexões. Certifique-se de configurar seu backend para confiar nos cabeçalhos do protocolo proxy no Traefik.",
"restarting": "Reiniciando...",
"manual": "Manualmente",
"messageSupport": "Suporte a Mensagens",
"supportNotAvailableTitle": "Suporte Não Disponível",
"supportNotAvailableDescription": "Não está disponível no momento. Você pode enviar um e-mail para support@pangolin.net.",
"supportRequestSentTitle": "Pedido de suporte enviado",
"supportRequestSentDescription": "Sua mensagem foi enviada com sucesso.",
"supportRequestFailedTitle": "Falha ao enviar solicitação",
"supportRequestFailedDescription": "Ocorreu um erro ao enviar sua solicitação de suporte.",
"supportSubjectRequired": "Assunto é necessária",
"supportSubjectMaxLength": "O assunto deve ter 255 caracteres ou menos",
"supportMessageRequired": "A mensagem é obrigatória",
"supportReplyTo": "Responder a",
"supportSubject": "Cargo",
"supportSubjectPlaceholder": "Digite o assunto",
"supportMessage": "mensagem",
"supportMessagePlaceholder": "Digite sua mensagem",
"supportSending": "Enviando...",
"supportSend": "Mandar",
"supportMessageSent": "Mensagem enviada!",
"supportWillContact": "Entraremos em contato em breve!",
"selectLogRetention": "Selecionar retenção de log",
"showColumns": "Exibir Colunas",
"hideColumns": "Ocultar colunas",
"columnVisibility": "Visibilidade da Coluna",
"toggleColumn": "Alternar coluna {columnName}",
"allColumns": "Todas as colunas",
"defaultColumns": "Colunas padrão",
"customizeView": "Personalizar visualização",
"viewOptions": "Opções de visualização",
"selectAll": "Selecionar Todos",
"selectNone": "Não selecionar nada",
"selectedResources": "Recursos Selecionados",
"enableSelected": "Habilitar Selecionados",
"disableSelected": "Desativar Selecionados",
"checkSelectedStatus": "Status de Verificação dos Selecionados"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "Срок действия",
"neverExpire": "Бессрочный доступ",
"shareExpireDescription": "Срок действия - это период, в течение которого ссылка будет работать и предоставлять доступ к ресурсу. После этого времени ссылка перестанет работать, и пользователи, использовавшие эту ссылку, потеряют доступ к ресурсу.",
"shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте её.",
"shareSeeOnce": "Вы сможете увидеть эту ссылку только один раз. Обязательно скопируйте ее.",
"shareAccessHint": "Любой, у кого есть эта ссылка, может получить доступ к ресурсу. Делитесь ею с осторожностью.",
"shareTokenUsage": "Посмотреть использование токена доступа",
"createLink": "Создать ссылку",
@@ -179,7 +179,7 @@
"baseDomain": "Базовый домен",
"subdomnainDescription": "Поддомен, на котором будет доступен ресурс.",
"resourceRawSettings": "Настройки TCP/UDP",
"resourceRawSettingsDescription": "Настройте, как будет осуществляться доступ к вашему ресурсу через TCP/UDP",
"resourceRawSettingsDescription": "Настройте доступ к вашему ресурсу по TCP/UDP. Вы соотносите ресурс с портом на сервере хоста Pangolin, так что вы можете получить доступ к ресурсу с сервера server-public-ip:mapped-порта.",
"protocol": "Протокол",
"protocolSelect": "Выберите протокол",
"resourcePortNumber": "Номер порта",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "Проверьте вашу почту для получения кода сброса пароля.",
"passwordNew": "Новый пароль",
"passwordNewConfirm": "Подтвердите новый пароль",
"changePassword": "Изменить пароль",
"changePasswordDescription": "Обновить пароль учетной записи",
"oldPassword": "Текущий пароль",
"newPassword": "Новый пароль",
"confirmNewPassword": "Подтвердите новый пароль",
"changePasswordError": "Не удалось сменить пароль",
"changePasswordErrorDescription": "Произошла ошибка при смене пароля",
"changePasswordSuccess": "Пароль успешно изменен",
"changePasswordSuccessDescription": "Ваш пароль был успешно обновлен",
"passwordExpiryRequired": "Требуется срок действия пароля",
"passwordExpiryDescription": "Эта организация требует смены пароля каждые {maxDays} дней.",
"changePasswordNow": "Изменить пароль сейчас",
"pincodeAuth": "Код аутентификатора",
"pincodeSubmit2": "Отправить код",
"passwordResetSubmit": "Запросить сброс",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Лицензия",
"sidebarClients": "Клиенты",
"sidebarDomains": "Домены",
"sidebarBluePrints": "Чертежи",
"blueprints": "Чертежи",
"blueprintsDescription": "Применить декларирующие конфигурации и просмотреть предыдущие запуски",
"blueprintAdd": "Добавить чертёж",
"blueprintGoBack": "Посмотреть все чертежи",
"blueprintCreate": "Создать чертёж",
"blueprintCreateDescription2": "Для создания и применения нового чертежа выполните следующие шаги",
"blueprintDetails": "Детали чертежа",
"blueprintDetailsDescription": "Посмотреть результат примененного чертежа и все возникшие ошибки",
"blueprintInfo": "Информация о чертеже",
"message": "Сообщение",
"blueprintContentsDescription": "Определите содержимое YAML, описывающее вашу инфраструктуру",
"blueprintErrorCreateDescription": "Произошла ошибка при применении чертежа",
"blueprintErrorCreate": "Ошибка при создании чертежа",
"searchBlueprintProgress": "Поиск чертежей...",
"appliedAt": "Заявка на",
"source": "Источник",
"contents": "Содержание",
"parsedContents": "Переработанное содержимое (только для чтения)",
"enableDockerSocket": "Включить чертёж Docker",
"enableDockerSocketDescription": "Включить scraping ярлыка Docker Socket для ярлыков чертежей. Путь к сокету должен быть предоставлен в Newt.",
"enableDockerSocketLink": "Узнать больше",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Произошла проблема при использовании вашего ключа безопасности. Пожалуйста, попробуйте еще раз.",
"twoFactorRequired": "Для регистрации ключа безопасности требуется двухфакторная аутентификация.",
"twoFactor": "Двухфакторная аутентификация",
"twoFactorAuthentication": "Двухфакторная аутентификация",
"twoFactorDescription": "Эта организация требует двухфакторной аутентификации.",
"enableTwoFactor": "Включить двухфакторную аутентификацию",
"organizationSecurityPolicy": "Политика безопасности Организации",
"organizationSecurityPolicyDescription": "У этой организации есть требования безопасности, которые должны быть выполнены, прежде чем вы сможете получить доступ к ней",
"securityRequirements": "Требования безопасности",
"allRequirementsMet": "Все требования выполнены",
"completeRequirementsToContinue": "Выполните следующие требования, чтобы продолжить доступ к этой организации",
"youCanNowAccessOrganization": "Теперь вы можете получить доступ к этой организации",
"reauthenticationRequired": "Длина сессии",
"reauthenticationDescription": "Эта организация требует входа каждый {maxDays} дней.",
"reauthenticationDescriptionHours": "Эта организация требует входа в систему каждый {maxHours} часов.",
"reauthenticateNow": "Войти снова",
"adminEnabled2FaOnYourAccount": "Ваш администратор включил двухфакторную аутентификацию для {email}. Пожалуйста, завершите процесс настройки, чтобы продолжить.",
"securityKeyAdd": "Добавить ключ безопасности",
"securityKeyRegisterTitle": "Регистрация нового ключа безопасности",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Редактировать файл: docker-compose.yml",
"emailVerificationRequired": "Требуется подтверждение адреса электронной почты. Пожалуйста, войдите снова через {dashboardUrl}/auth/login завершить этот шаг. Затем вернитесь сюда.",
"twoFactorSetupRequired": "Требуется настройка двухфакторной аутентификации. Пожалуйста, войдите снова через {dashboardUrl}/auth/login завершить этот шаг. Затем вернитесь сюда.",
"additionalSecurityRequired": "Требуется дополнительная безопасность",
"organizationRequiresAdditionalSteps": "Эта организация требует дополнительных шагов безопасности, прежде чем вы сможете получить доступ к ресурсам.",
"completeTheseSteps": "Выполните эти шаги",
"enableTwoFactorAuthentication": "Включить двухфакторную аутентификацию",
"completeSecuritySteps": "Пройти шаги безопасности",
"securitySettings": "Настройки безопасности",
"securitySettingsDescription": "Настройка политик безопасности для вашей организации",
"requireTwoFactorForAllUsers": "Требовать двухфакторную аутентификацию для всех пользователей",
"requireTwoFactorDescription": "Когда включено, все внутренние пользователи в этой организации должны иметь двухфакторную аутентификацию для доступа к организации.",
"requireTwoFactorDisabledDescription": "Эта функция требует действительной лицензии (Enterprise) или активной подписки (SaaS)",
"requireTwoFactorCannotEnableDescription": "Вы должны включить двухфакторную аутентификацию для вашей учетной записи, прежде чем принудительно ее применять для всех пользователей",
"maxSessionLength": "Максимальная длина сессии",
"maxSessionLengthDescription": "Установите максимальную длительность сессий пользователя. После этого времени, пользователям нужно будет пройти повторную аутентификацию.",
"maxSessionLengthDisabledDescription": "Эта функция требует действительной лицензии (Enterprise) или активной подписки (SaaS)",
"selectSessionLength": "Выберите длину сеанса",
"unenforced": "Не применено",
"1Hour": "1 час",
"3Hours": "3 часа",
"6Hours": "6 часов",
"12Hours": "12 часов",
"1DaySession": "1 день",
"3Days": "3 дня",
"7Days": "7 дней",
"14Days": "14 дней",
"30DaysSession": "30 дней",
"90DaysSession": "90 дней",
"180DaysSession": "180 дней",
"passwordExpiryDays": "Срок действия пароля",
"editPasswordExpiryDescription": "Установите количество дней, прежде чем пользователи должны изменить свой пароль.",
"selectPasswordExpiry": "Выберите срок действия пароля",
"30Days": "30 дней",
"1Day": "1 день",
"60Days": "60 дней",
"90Days": "90 дней",
"180Days": "180 дней",
"1Year": "1 год",
"subscriptionBadge": "Требуется подписка",
"securityPolicyChangeWarning": "Предупреждение об изменении политики безопасности",
"securityPolicyChangeDescription": "Вы собираетесь изменить настройки политики безопасности. После сохранения вам может потребоваться повторная аутентификация, чтобы соответствовать этим обновлениям. Все пользователи, которые не соответствуют установленным правилам, также должны пройти процедуру повторной аутентификации.",
"securityPolicyChangeConfirmMessage": "Подтверждаю",
"securityPolicyChangeWarningText": "Это повлияет на всех пользователей организации",
"authPageErrorUpdateMessage": "Произошла ошибка при обновлении настроек страницы авторизации",
"authPageErrorUpdate": "Не удалось обновить страницу авторизации",
"authPageUpdated": "Страница авторизации успешно обновлена",
"healthCheckNotAvailable": "Локальный",
"rewritePath": "Переписать путь",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Это действие не может быть отменено.",
"toConfirm": "для подтверждения",
"deleteClientQuestion": "Вы уверены, что хотите удалить клиента из сайта и организации?",
"clientMessageRemove": "После удаления клиент больше не сможет подключиться к сайту."
}
"clientMessageRemove": "После удаления клиент больше не сможет подключиться к сайту.",
"sidebarLogs": "Логи",
"request": "Запросить",
"logs": "Логи",
"logsSettingsDescription": "Отслеживать журналы, собранные в этой организации",
"searchLogs": "Поиск журналов...",
"action": "Действие",
"actor": "Актер",
"timestamp": "Отметка времени",
"accessLogs": "Журналы доступа",
"exportCsv": "Экспорт CSV",
"actorId": "ID актера",
"allowedByRule": "Разрешено правилом",
"allowedNoAuth": "Разрешено без авторизации",
"validAccessToken": "Действительный маркер доступа",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "Допустимый пароль",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "Ресурс заблокирован",
"droppedByRule": "Отброшено по правилам",
"noSessions": "Нет сессий",
"temporaryRequestToken": "Временный токен запроса",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Причина",
"requestLogs": "Запросить журналы",
"host": "Хост",
"location": "Местоположение",
"actionLogs": "Журнал действий",
"sidebarLogsRequest": "Запросить журналы",
"sidebarLogsAccess": "Журналы доступа",
"sidebarLogsAction": "Журнал действий",
"logRetention": "Сохранение журнала",
"logRetentionDescription": "Управление сохранением различных типов журналов для этой организации или отключение их",
"requestLogsDescription": "Просмотреть подробные журналы запроса ресурсов в этой организации",
"logRetentionRequestLabel": "Запросить сохранение журнала",
"logRetentionRequestDescription": "Как долго сохранять журналы запросов",
"logRetentionAccessLabel": "Хранение журнала доступа",
"logRetentionAccessDescription": "Как долго сохранять журналы доступа",
"logRetentionActionLabel": "Сохранение журнала действий",
"logRetentionActionDescription": "Как долго хранить журналы действий",
"logRetentionDisabled": "Отключено",
"logRetention3Days": "3 дня",
"logRetention7Days": "7 дней",
"logRetention14Days": "14 дней",
"logRetention30Days": "30 дней",
"logRetention90Days": "90 дней",
"logRetentionForever": "Всегда",
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
"licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.",
"certResolver": "Резольвер сертификата",
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
"selectCertResolver": "Выберите резолвер сертификата",
"enterCustomResolver": "Введите пользовательский резолвер",
"preferWildcardCert": "Предпочитать сертификат Wildcard",
"unverified": "Не подтверждено",
"domainSetting": "Настройки домена",
"domainSettingDescription": "Настройка параметров для вашего домена",
"preferWildcardCertDescription": "Попытка создания шаблона сертификата (требуется должным образом сконфигурированный резолвер сертификата).",
"recordName": "Имя записи",
"auto": "Авто",
"TTL": "TTL",
"howToAddRecords": "Как добавить записи",
"dnsRecord": "DNS записи",
"required": "Требуется",
"domainSettingsUpdated": "Настройки домена успешно обновлены",
"orgOrDomainIdMissing": "Отсутствует организация или ID домена",
"loadingDNSRecords": "Загрузка записей DNS...",
"olmUpdateAvailableInfo": "Доступна обновленная версия Олма. Пожалуйста, обновитесь до последней версии.",
"client": "Клиент",
"proxyProtocol": "Настройки протокола прокси",
"proxyProtocolDescription": "Настроить Прокси-протокол для сохранения IP-адресов клиента для служб TCP/UDP.",
"enableProxyProtocol": "Включить Прокси Протокол",
"proxyProtocolInfo": "Сохранять IP-адреса клиента для кэша TCP/UDP",
"proxyProtocolVersion": "Версия протокола прокси",
"version1": " Версия 1 (рекомендуется)",
"version2": "Версия 2",
"versionDescription": "Версия 1 основана на тексте и широко поддерживается. Версия 2 является бинарной и более эффективной, но менее совместимой.",
"warning": "Предупреждение",
"proxyProtocolWarning": "Бэкэнд приложение должно быть сконфигурировано для принятия прокси-соединений. Если ваш бэкэнд не поддерживает Прокси-протокол, это нарушит все соединения. Обязательно настройте вашего бэкэнда на доверие заголовкам Proxy Protocol от Traefik.",
"restarting": "Перезапуск...",
"manual": "Ручной",
"messageSupport": "Поддержка сообщений",
"supportNotAvailableTitle": "Поддержка недоступна",
"supportNotAvailableDescription": "Поддержка сейчас недоступна. Вы можете отправить письмо по адресу support@pangolin.net.",
"supportRequestSentTitle": "Запрос на поддержку отправлен",
"supportRequestSentDescription": "Ваше сообщение успешно отправлено.",
"supportRequestFailedTitle": "Не удалось отправить запрос",
"supportRequestFailedDescription": "Произошла ошибка при отправке запроса поддержки.",
"supportSubjectRequired": "Необходимо ввести тему",
"supportSubjectMaxLength": "Тема должна быть 255 символов или меньше",
"supportMessageRequired": "Требуется сообщение",
"supportReplyTo": "Ответить",
"supportSubject": "Тема",
"supportSubjectPlaceholder": "Введите тему",
"supportMessage": "Сообщение",
"supportMessagePlaceholder": "Введите ваше сообщение",
"supportSending": "Отправка...",
"supportSend": "Отправить",
"supportMessageSent": "Сообщение отправлено!",
"supportWillContact": "Мы скоро свяжемся с Вами!",
"selectLogRetention": "Выберите удержание журнала",
"showColumns": "Показать колонки",
"hideColumns": "Скрыть столбцы",
"columnVisibility": "Видимость столбцов",
"toggleColumn": "Столбец {columnName}",
"allColumns": "Все колонки",
"defaultColumns": "Столбцы по умолчанию",
"customizeView": "Настроить вид",
"viewOptions": "Параметры просмотра",
"selectAll": "Выделить все",
"selectNone": "Не выбирать",
"selectedResources": "Выбранные ресурсы",
"enableSelected": "Включить выбранные",
"disableSelected": "Отключить выбранные",
"checkSelectedStatus": "Проверить статус выбранных"
}

View File

@@ -179,7 +179,7 @@
"baseDomain": "Temel Alan Adı",
"subdomnainDescription": "Kaynağınızın erişilebileceği alt alan adı.",
"resourceRawSettings": "TCP/UDP Ayarları",
"resourceRawSettingsDescription": "Kaynağınıza TCP/UDP üzerinden erişimin nasıl sağlanacağını yapılandırın",
"resourceRawSettingsDescription": "Kaynağınızın TCP/UDP üzerinden nasıl erişileceğini yapılandırın. Kaynağı, sunucudan erişebilmeniz için bir ana bilgisayar Pangolin sunucusundaki bir bağlantı noktasına eşlersiniz: sunucu genel-IP: eşlenen-bağlantı-noktası.",
"protocol": "Protokol",
"protocolSelect": "Bir protokol seçin",
"resourcePortNumber": "Port Numarası",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "E-posta gelen kutunuzda sıfırlama kodunu kontrol edin.",
"passwordNew": "Yeni Şifre",
"passwordNewConfirm": "Yeni Şifreyi Onayla",
"changePassword": "Parola Değiştir",
"changePasswordDescription": "Hesap şifrenizi güncelleyin",
"oldPassword": "Mevcut Şifre",
"newPassword": "Yeni Şifre",
"confirmNewPassword": "Yeni Şifreyi Onayla",
"changePasswordError": "Parola değiştirme başarısız oldu",
"changePasswordErrorDescription": "Parolanız değiştiriliyor.",
"changePasswordSuccess": "Şifre Başarıyla Değiştirildi",
"changePasswordSuccessDescription": "Parolanız başarıyla güncellendi",
"passwordExpiryRequired": "Şifre Süresi Gereklidir",
"passwordExpiryDescription": "Bu kuruluş, parolanızı {maxDays} günde bir değiştirmenizi gerektirir.",
"changePasswordNow": "Şifrenizi Şimdi Değiştirin",
"pincodeAuth": "Kimlik Doğrulama Kodu",
"pincodeSubmit2": "Kodu Gönder",
"passwordResetSubmit": "Sıfırlama İsteği",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "Lisans",
"sidebarClients": "İstemciler",
"sidebarDomains": "Alan Adları",
"sidebarBluePrints": "Planlar",
"blueprints": "Planlar",
"blueprintsDescription": "Deklaratif yapılandırmaları uygulayın ve önceki çalışmaları görüntüleyin",
"blueprintAdd": "Plan Ekle",
"blueprintGoBack": "Tüm Planları Gör",
"blueprintCreate": "Plan Oluştur",
"blueprintCreateDescription2": "Yeni bir plan oluşturup uygulamak için aşağıdaki adımları izleyin",
"blueprintDetails": "Mavi Yazılım Detayları",
"blueprintDetailsDescription": "Uygulanan mavi yazılımın sonucunu ve oluşan hataları görün",
"blueprintInfo": "Plan Bilgileri",
"message": "Mesaj",
"blueprintContentsDescription": "Altyapınızı tanımlayan YAML içeriğini tanımlayın",
"blueprintErrorCreateDescription": "Plan uygulanırken bir hata oluştu",
"blueprintErrorCreate": "Plan oluşturulurken hata oluştu",
"searchBlueprintProgress": "Planlarda ara...",
"appliedAt": "Uygulama Zamanı",
"source": "Kaynak",
"contents": "İçerik",
"parsedContents": "Verilerin Ayrıştırılmış İçeriği (Salt Okunur)",
"enableDockerSocket": "Docker Soketini Etkinleştir",
"enableDockerSocketDescription": "Plan etiketleri için Docker Socket etiket toplamasını etkinleştirin. Newt'e soket yolu sağlanmalıdır.",
"enableDockerSocketLink": "Daha fazla bilgi",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "Güvenlik anahtarınızı kullanırken bir sorun oluştu. Lütfen tekrar deneyin.",
"twoFactorRequired": "Güvenlik anahtarını kaydetmek için iki faktörlü kimlik doğrulama gereklidir.",
"twoFactor": "İki Faktörlü Kimlik Doğrulama",
"twoFactorAuthentication": "İki Faktörlü Kimlik Doğrulama",
"twoFactorDescription": "Bu kuruluş iki faktörlü kimlik doğrulama gerektirir.",
"enableTwoFactor": "İki Faktörlü Kimlik Doğrulamayı Etkinleştir",
"organizationSecurityPolicy": "Kuruluş Güvenlik Politikası",
"organizationSecurityPolicyDescription": "Bu kuruluşun güvenlik gereksinimlerine erişmeden önce karşılanması gereken güvenlik gereksinimleri vardır.",
"securityRequirements": "Güvenlik Gereksinimleri",
"allRequirementsMet": "Tüm gereksinimler karşılandı",
"completeRequirementsToContinue": "Bu kuruluşa erişmeye devam etmek için aşağıdaki gereksinimleri tamamlayın",
"youCanNowAccessOrganization": "Artık bu kuruluşa erişebilirsiniz",
"reauthenticationRequired": "Oturum Süresi",
"reauthenticationDescription": "Bu kuruluş, {maxDays} günde bir oturum açmanızı gerektirir.",
"reauthenticationDescriptionHours": "Bu kuruluş, {maxHours} saatte bir oturum açmanızı gerektirir.",
"reauthenticateNow": "Tekrar Giriş Yap",
"adminEnabled2FaOnYourAccount": "Yöneticiniz {email} için iki faktörlü kimlik doğrulamayı etkinleştirdi. Devam etmek için kurulum işlemini tamamlayın.",
"securityKeyAdd": "Güvenlik Anahtarı Ekle",
"securityKeyRegisterTitle": "Yeni Güvenlik Anahtarı Kaydet",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "Dosyayı düzenle: docker-compose.yml",
"emailVerificationRequired": "E-posta doğrulaması gereklidir. Bu adımı tamamlamak için lütfen tekrar {dashboardUrl}/auth/login üzerinden oturum açın. Sonra buraya geri dönün.",
"twoFactorSetupRequired": "İki faktörlü kimlik doğrulama ayarı gereklidir. Bu adımı tamamlamak için lütfen tekrar {dashboardUrl}/auth/login üzerinden oturum açın. Sonra buraya geri dönün.",
"additionalSecurityRequired": "Ek Güvenlik Gereklidir",
"organizationRequiresAdditionalSteps": "Bu kuruluş, kaynaklara erişmeden önce ek güvenlik adımları gerektirir.",
"completeTheseSteps": "Bu adımları tamamlayın",
"enableTwoFactorAuthentication": "İki faktörlü kimlik doğrulamayı etkinleştir",
"completeSecuritySteps": "Güvenlik Adımlarını Tamamla",
"securitySettings": "Güvenlik Ayarları",
"securitySettingsDescription": "Kuruluşunuz için güvenlik politikalarını yapılandırın",
"requireTwoFactorForAllUsers": "Tüm Kullanıcılar için İki Faktörlü Kimlik Doğrulama Gerektir",
"requireTwoFactorDescription": "Etkinleştirildiğinde, bu kuruluştaki tüm dahili kullanıcıların, kuruluşa erişmek için iki faktörlü kimlik doğrulama etkinleştirilmiş olmalıdır.",
"requireTwoFactorDisabledDescription": "Bu özellik, geçerli bir lisans (Kurumsal) veya aktif bir abonelik (SaaS) gerektirir",
"requireTwoFactorCannotEnableDescription": "Tüm kullanıcılar için etkinleştirilmeden önce hesabınızda iki faktörlü kimlik doğrulamayı etkinleştirmeniz gerekir",
"maxSessionLength": "Maksimum Oturum Süresi",
"maxSessionLengthDescription": "Kullanıcı oturumları için maksimum süreyi ayarlayın. Bu süre sonra kullanıcıların tekrar kimlik doğrulaması gerekecektir.",
"maxSessionLengthDisabledDescription": "Bu özellik, geçerli bir lisans (Kurumsal) veya aktif bir abonelik (SaaS) gerektirir",
"selectSessionLength": "Oturum süresini seçin",
"unenforced": "Zorunlu Değil",
"1Hour": "1 saat",
"3Hours": "3 saat",
"6Hours": "6 saat",
"12Hours": "12 saat",
"1DaySession": "1 gün",
"3Days": "3 gün",
"7Days": "7 gün",
"14Days": "14 gün",
"30DaysSession": "30 gün",
"90DaysSession": "90 gün",
"180DaysSession": "180 gün",
"passwordExpiryDays": "Şifre Sona Ermesi",
"editPasswordExpiryDescription": "Kullanıcıların parolalarını değiştirmeleri gereken gün sayısını ayarlayın.",
"selectPasswordExpiry": "Şifre sona ermesini seçin",
"30Days": "30 gün",
"1Day": "1 gün",
"60Days": "60 gün",
"90Days": "90 gün",
"180Days": "180 gün",
"1Year": "1 yıl",
"subscriptionBadge": "Abonelik Gerekiyor",
"securityPolicyChangeWarning": "Güvenlik Politikası Değişiklik Uyarısı",
"securityPolicyChangeDescription": "Güvenlik politikası ayarlarını değiştirmek üzeresiniz. Değişiklikleri kaydettikten sonra, bu politika güncellemelerine uyum sağlamak amacıyla tekrar kimlik doğrulamanız gerekebilir. Uyum sağlamayan tüm kullanıcıların da tekrar kimlik doğrulaması gerekecektir.",
"securityPolicyChangeConfirmMessage": "Onaylıyorum",
"securityPolicyChangeWarningText": "Bu, organizasyondaki tüm kullanıcıları etkileyecektir",
"authPageErrorUpdateMessage": "Kimlik doğrulama sayfası ayarları güncellenirken bir hata oluştu.",
"authPageErrorUpdate": "Kimlik doğrulama sayfası güncellenemedi",
"authPageUpdated": "Kimlik doğrulama sayfası başarıyla güncellendi",
"healthCheckNotAvailable": "Yerel",
"rewritePath": "Yolu Yeniden Yaz",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "Bu geri alınamaz.",
"toConfirm": "doğrulamak için",
"deleteClientQuestion": "Müşteriyi siteden ve organizasyondan kaldırmak istediğinizden emin misiniz?",
"clientMessageRemove": "Kaldırıldıktan sonra müşteri siteye bağlanamayacaktır."
}
"clientMessageRemove": "Kaldırıldıktan sonra müşteri siteye bağlanamayacaktır.",
"sidebarLogs": "Kayıtlar",
"request": "İstek",
"logs": "Günlükler",
"logsSettingsDescription": "Bu organizasyondan toplanan günlükleri izleyin",
"searchLogs": "Günlüklerde ara...",
"action": "Eylem",
"actor": "Aktör",
"timestamp": "Zaman damgası",
"accessLogs": "Erişim Günlükleri",
"exportCsv": "CSV Dışa Aktar",
"actorId": "Aktör Kimliği",
"allowedByRule": "Kurallara Göre İzin Verildi",
"allowedNoAuth": "Kimlik Doğrulama Yok İzin Verildi",
"validAccessToken": "Geçerli Erişim Jetonu",
"validHeaderAuth": "Geçerli Başlık Doğrulama",
"validPincode": "Geçerli Pincode",
"validPassword": "Geçerli Şifre",
"validEmail": "Geçerli E-posta",
"validSSO": "Geçerli SSO",
"resourceBlocked": "Kaynak Engellendi",
"droppedByRule": "Kurallara Göre Çıkartıldı",
"noSessions": "Oturum Yok",
"temporaryRequestToken": "Geçici İstek Jetonu",
"noMoreAuthMethods": "Daha Fazla Kimlik Doğrulama Yöntemi Yok",
"ip": "IP",
"reason": "Sebep",
"requestLogs": "İstek Günlükleri",
"host": "Sunucu",
"location": "Konum",
"actionLogs": "Eylem Günlükleri",
"sidebarLogsRequest": "İstek Günlükleri",
"sidebarLogsAccess": "Erişim Günlükleri",
"sidebarLogsAction": "Eylem Günlükleri",
"logRetention": "Kayıt Saklama",
"logRetentionDescription": "Bu organizasyon için farklı türdeki günlüklerin ne kadar süre saklanacağını yönetin veya devre dışı bırakın",
"requestLogsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek günlüklerini görüntüleyin",
"logRetentionRequestLabel": "İstek Günlüğü Saklama",
"logRetentionRequestDescription": "İstek günlüklerini ne kadar süre tutacağını belirle",
"logRetentionAccessLabel": "Erişim Günlüğü Saklama",
"logRetentionAccessDescription": "Erişim günlüklerini ne kadar süre tutacağını belirle",
"logRetentionActionLabel": "Eylem Günlüğü Saklama",
"logRetentionActionDescription": "Eylem günlüklerini ne kadar süre tutacağını belirle",
"logRetentionDisabled": "Devre Dışı",
"logRetention3Days": "3 gün",
"logRetention7Days": "7 gün",
"logRetention14Days": "14 gün",
"logRetention30Days": "30 gün",
"logRetention90Days": "90 gün",
"logRetentionForever": "Sonsuza kadar",
"actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin",
"accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin",
"licenseRequiredToUse": "Bu özelliği kullanmak için bir kurumsal lisans gereklidir.",
"certResolver": "Sertifika Çözücü",
"certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.",
"selectCertResolver": "Sertifika Çözücü Seçin",
"enterCustomResolver": "Özel Çözücü Girin",
"preferWildcardCert": "Joker Sertifikayı Tercih Et",
"unverified": "Doğrulanmadı",
"domainSetting": "Alan Adı Ayarları",
"domainSettingDescription": "Alan adınız için ayarları yapılandırın",
"preferWildcardCertDescription": "Joker sertifika üretmeye çalışın (doğru yapılandırılmış bir sertifika çözücü gereklidir).",
"recordName": "Kayıt Adı",
"auto": "Otomatik",
"TTL": "TTL",
"howToAddRecords": "Kayıtları Nasıl Ekleyebilirsiniz",
"dnsRecord": "DNS Kayıtları",
"required": "Gerekli",
"domainSettingsUpdated": "Alan adına yönelik ayarlar başarıyla güncellendi",
"orgOrDomainIdMissing": "Organizasyon veya Alan Adı Kimliği eksik",
"loadingDNSRecords": "DNS kayıtları yükleniyor...",
"olmUpdateAvailableInfo": "Olm'nin güncellenmiş bir sürümü mevcut. En iyi deneyim için lütfen en son sürüme güncelleyin.",
"client": "İstemci",
"proxyProtocol": "Proxy Protokol Ayarları",
"proxyProtocolDescription": "TCP/UDP hizmetleri için istemci IP adreslerini korumak için Proxy Protokolünü yapılandırın.",
"enableProxyProtocol": "Proxy Protokolünü Etkinleştir",
"proxyProtocolInfo": "TCP/UDP arka uçları için istemci IP adreslerini koruyun",
"proxyProtocolVersion": "Proxy Protokol Versiyonu",
"version1": " Versiyon 1 (Önerilen)",
"version2": "Versiyon 2",
"versionDescription": "Versiyon 1 metin tabanlı ve yaygın olarak desteklenir. Versiyon 2 ise ikili ve daha verimlidir ama daha az uyumludur.",
"warning": "Uyarı",
"proxyProtocolWarning": "Arka uç uygulamanız, Proxy Protokol bağlantılarını kabul etmek üzere yapılandırılmalıdır. Arka ucunuz Proxy Protokolünü desteklemiyorsa, bunu etkinleştirmek tüm bağlantıları koparır. Traefik'ten gelen Proxy Protokol başlıklarına güvenecek şekilde arka ucunuzu yapılandırdığınızdan emin olun.",
"restarting": "Yeniden Başlatılıyor...",
"manual": "Manuel",
"messageSupport": "Destek Mesajı Gönder",
"supportNotAvailableTitle": "Destek Yok",
"supportNotAvailableDescription": "Destek şu anda mevcut değil. Destek'e bir e-posta gönderebilirsiniz: support@pangolin.net.",
"supportRequestSentTitle": "Destek İsteği Gönderildi",
"supportRequestSentDescription": "Mesajınız başarıyla gönderildi.",
"supportRequestFailedTitle": "İsteği Gönderme Başarısız",
"supportRequestFailedDescription": "Destek isteği gönderilirken bir hata oluştu.",
"supportSubjectRequired": "Konu gerekli",
"supportSubjectMaxLength": "Konu en fazla 255 karakter olabilir",
"supportMessageRequired": "Mesaj gerekli",
"supportReplyTo": "Yanıtla",
"supportSubject": "Konu",
"supportSubjectPlaceholder": "Konu girin",
"supportMessage": "Mesaj",
"supportMessagePlaceholder": "Mesajınızı girin",
"supportSending": "Gönderiliyor...",
"supportSend": "Gönder",
"supportMessageSent": "Mesaj Gönderildi!",
"supportWillContact": "En kısa sürede size geri döneceğiz!",
"selectLogRetention": "Kayıt saklama seç",
"showColumns": "Sütunları Göster",
"hideColumns": "Sütunları Gizle",
"columnVisibility": "Sütun Görünürlüğü",
"toggleColumn": "{columnName} sütununu aç/kapat",
"allColumns": "Tüm Sütunlar",
"defaultColumns": "Varsayılan Sütunlar",
"customizeView": "Görünümü Özelleştir",
"viewOptions": "Görünüm Seçenekleri",
"selectAll": "Tümünü Seç",
"selectNone": "Hiçbirini Seçme",
"selectedResources": "Seçilen Kaynaklar",
"enableSelected": "Seçilenleri Etkinleştir",
"disableSelected": "Seçilenleri Devre Dışı Bırak",
"checkSelectedStatus": "Seçilenlerin Durumunu Kontrol Et"
}

View File

@@ -131,7 +131,7 @@
"expireIn": "过期时间",
"neverExpire": "永不过期",
"shareExpireDescription": "过期时间是链接可以使用并提供对资源的访问时间。 此时间后,链接将不再工作,使用此链接的用户将失去对资源的访问。",
"shareSeeOnce": "您只能看到此链接。请确保复制它。",
"shareSeeOnce": "您只能看到一次此链接。请确保复制它。",
"shareAccessHint": "任何具有此链接的人都可以访问该资源。小心地分享它。",
"shareTokenUsage": "查看访问令牌使用情况",
"createLink": "创建链接",
@@ -179,7 +179,7 @@
"baseDomain": "根域名",
"subdomnainDescription": "您的资源可以访问的子域名。",
"resourceRawSettings": "TCP/UDP 设置",
"resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源",
"resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问您的资源。 您映射资源到主机Pangolin服务器上的端口这样您就可以访问服务器-公共-ip:mapped端口的资源。",
"protocol": "协议",
"protocolSelect": "选择协议",
"resourcePortNumber": "端口号",
@@ -911,6 +911,18 @@
"passwordResetCodeDescription": "请检查您的电子邮件以获取验证码。",
"passwordNew": "新密码",
"passwordNewConfirm": "确认新密码",
"changePassword": "更改密码",
"changePasswordDescription": "更新您的帐户密码",
"oldPassword": "当前密码",
"newPassword": "新密码",
"confirmNewPassword": "确认新密码",
"changePasswordError": "更改密码失败",
"changePasswordErrorDescription": "更改您的密码时出错",
"changePasswordSuccess": "密码修改成功",
"changePasswordSuccessDescription": "您的密码已成功更新",
"passwordExpiryRequired": "需要密码过期",
"passwordExpiryDescription": "该机构要求您每 {maxDays} 天更改一次密码。",
"changePasswordNow": "现在更改密码",
"pincodeAuth": "验证器代码",
"pincodeSubmit2": "提交代码",
"passwordResetSubmit": "请求重置",
@@ -1151,6 +1163,25 @@
"sidebarLicense": "证书",
"sidebarClients": "客户端",
"sidebarDomains": "域",
"sidebarBluePrints": "蓝图",
"blueprints": "蓝图",
"blueprintsDescription": "应用声明配置并查看先前运行的",
"blueprintAdd": "添加蓝图",
"blueprintGoBack": "查看所有蓝图",
"blueprintCreate": "创建蓝图",
"blueprintCreateDescription2": "按照下面的步骤创建和应用新的蓝图",
"blueprintDetails": "蓝图详细信息",
"blueprintDetailsDescription": "查看应用蓝图的结果和发生的任何错误",
"blueprintInfo": "蓝图信息",
"message": "留言",
"blueprintContentsDescription": "定义描述您基础设施的 YAML 内容",
"blueprintErrorCreateDescription": "应用蓝图时出错",
"blueprintErrorCreate": "创建蓝图时出错",
"searchBlueprintProgress": "搜索蓝图...",
"appliedAt": "应用于",
"source": "来源",
"contents": "目录",
"parsedContents": "解析内容 (只读)",
"enableDockerSocket": "启用 Docker 蓝图",
"enableDockerSocketDescription": "启用 Docker Socket 标签擦除蓝图标签。套接字路径必须提供给新的。",
"enableDockerSocketLink": "了解更多",
@@ -1340,6 +1371,19 @@
"securityKeyUnknownError": "使用安全密钥时出现问题。请再试一次。",
"twoFactorRequired": "注册安全密钥需要两步验证。",
"twoFactor": "两步验证",
"twoFactorAuthentication": "两步验证",
"twoFactorDescription": "这个组织需要双重身份验证。",
"enableTwoFactor": "启用两步验证",
"organizationSecurityPolicy": "组织安全政策",
"organizationSecurityPolicyDescription": "此机构拥有安全要求,您必须先满足才能访问",
"securityRequirements": "安全要求",
"allRequirementsMet": "已满足所有要求",
"completeRequirementsToContinue": "完成下面的要求以继续访问此组织",
"youCanNowAccessOrganization": "您现在可以访问此组织",
"reauthenticationRequired": "会话长度",
"reauthenticationDescription": "该机构要求您每 {maxDays} 天登录一次。",
"reauthenticationDescriptionHours": "该机构要求您每 {maxHours} 小时登录一次。",
"reauthenticateNow": "再次登录",
"adminEnabled2FaOnYourAccount": "管理员已为{email}启用两步验证。请完成设置以继续。",
"securityKeyAdd": "添加安全密钥",
"securityKeyRegisterTitle": "注册新安全密钥",
@@ -1726,7 +1770,49 @@
"resourceExposePortsEditFile": "编辑文件docker-compose.yml",
"emailVerificationRequired": "需要电子邮件验证。 请通过 {dashboardUrl}/auth/login 再次登录以完成此步骤。 然后,回到这里。",
"twoFactorSetupRequired": "需要设置双因素身份验证。 请通过 {dashboardUrl}/auth/login 再次登录以完成此步骤。 然后,回到这里。",
"additionalSecurityRequired": "需要额外的安全",
"organizationRequiresAdditionalSteps": "这个组织需要额外的安全步骤才能访问资源。",
"completeTheseSteps": "完成这些步骤",
"enableTwoFactorAuthentication": "启用两步验证",
"completeSecuritySteps": "完成安全步骤",
"securitySettings": "安全设置",
"securitySettingsDescription": "配置您组织的安全策略",
"requireTwoFactorForAllUsers": "所有用户需要两步验证",
"requireTwoFactorDescription": "如果启用,此组织的所有内部用户必须启用双重身份验证才能访问组织。",
"requireTwoFactorDisabledDescription": "此功能需要有效的许可证企业或活动订阅SaS",
"requireTwoFactorCannotEnableDescription": "您必须为您的帐户启用双重身份验证才能对所有用户",
"maxSessionLength": "最大会话长度",
"maxSessionLengthDescription": "设置用户会话的最长时间。此后用户需要重新验证。",
"maxSessionLengthDisabledDescription": "此功能需要有效的许可证企业或活动订阅SaS",
"selectSessionLength": "选择会话长度",
"unenforced": "未执行",
"1Hour": "1 小时",
"3Hours": "3 小时",
"6Hours": "6 小时",
"12Hours": "12 小时",
"1DaySession": "1天",
"3Days": "3 天",
"7Days": "7 天",
"14Days": "14 天",
"30DaysSession": "30 天",
"90DaysSession": "90 天",
"180DaysSession": "180天",
"passwordExpiryDays": "密码过期",
"editPasswordExpiryDescription": "设置用户需要更改密码之前的天数。",
"selectPasswordExpiry": "选择密码过期",
"30Days": "30 天",
"1Day": "1天",
"60Days": "60天",
"90Days": "90 天",
"180Days": "180天",
"1Year": "1 年",
"subscriptionBadge": "需要订阅",
"securityPolicyChangeWarning": "安全政策更改警告",
"securityPolicyChangeDescription": "您即将更改安全政策设置。保存后,您可能需要重新认证以遵守这些政策更新。 所有不符合要求的用户也需要重新认证。",
"securityPolicyChangeConfirmMessage": "我确认",
"securityPolicyChangeWarningText": "这将影响组织中的所有用户",
"authPageErrorUpdateMessage": "更新身份验证页面设置时出错",
"authPageErrorUpdate": "无法更新认证页面",
"authPageUpdated": "身份验证页面更新成功",
"healthCheckNotAvailable": "本地的",
"rewritePath": "重写路径",
@@ -1891,5 +1977,123 @@
"cannotbeUndone": "无法撤消。",
"toConfirm": "确认",
"deleteClientQuestion": "您确定要从站点和组织中删除客户吗?",
"clientMessageRemove": "一旦删除,客户端将无法连接到站点。"
}
"clientMessageRemove": "一旦删除,客户端将无法连接到站点。",
"sidebarLogs": "日志",
"request": "请求",
"logs": "日志",
"logsSettingsDescription": "监视从此orginization中收集的日志",
"searchLogs": "搜索日志...",
"action": "行 动",
"actor": "执行者",
"timestamp": "时间戳",
"accessLogs": "访问日志",
"exportCsv": "导出CSV",
"actorId": "执行者ID",
"allowedByRule": "根据规则允许",
"allowedNoAuth": "无认证",
"validAccessToken": "有效访问令牌",
"validHeaderAuth": "Valid header auth",
"validPincode": "Valid Pincode",
"validPassword": "有效密码",
"validEmail": "Valid email",
"validSSO": "Valid SSO",
"resourceBlocked": "资源被阻止",
"droppedByRule": "被规则删除",
"noSessions": "无会话",
"temporaryRequestToken": "临时请求令牌",
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "原因",
"requestLogs": "请求日志",
"host": "主机",
"location": "地点",
"actionLogs": "操作日志",
"sidebarLogsRequest": "请求日志",
"sidebarLogsAccess": "访问日志",
"sidebarLogsAction": "操作日志",
"logRetention": "日志保留",
"logRetentionDescription": "管理不同类型的日志为这个机构保留多长时间或禁用这些日志",
"requestLogsDescription": "查看此机构资源的详细请求日志",
"logRetentionRequestLabel": "请求日志保留",
"logRetentionRequestDescription": "保留请求日志的时间",
"logRetentionAccessLabel": "访问日志保留",
"logRetentionAccessDescription": "保留访问日志的时间",
"logRetentionActionLabel": "动作日志保留",
"logRetentionActionDescription": "保留操作日志的时间",
"logRetentionDisabled": "已禁用",
"logRetention3Days": "3 天",
"logRetention7Days": "7 天",
"logRetention14Days": "14 天",
"logRetention30Days": "30 天",
"logRetention90Days": "90 天",
"logRetentionForever": "永远的",
"actionLogsDescription": "查看此机构执行的操作历史",
"accessLogsDescription": "查看此机构资源的访问认证请求",
"licenseRequiredToUse": "需要企业许可证才能使用此功能。",
"certResolver": "证书解决器",
"certResolverDescription": "选择用于此资源的证书解析器。",
"selectCertResolver": "选择证书解析",
"enterCustomResolver": "输入自定义解析器",
"preferWildcardCert": "喜欢通配符证书",
"unverified": "未验证",
"domainSetting": "域设置",
"domainSettingDescription": "配置您的域的设置",
"preferWildcardCertDescription": "尝试生成通配符证书(需要正确配置的证书解析器)。",
"recordName": "记录名称",
"auto": "自动操作",
"TTL": "TTL",
"howToAddRecords": "如何添加记录",
"dnsRecord": "DNS记录",
"required": "必填",
"domainSettingsUpdated": "域设置更新成功",
"orgOrDomainIdMissing": "缺少机构或域 ID",
"loadingDNSRecords": "正在载入DNS记录...",
"olmUpdateAvailableInfo": "有最新版本的 Olm 可用。请更新到最新版本以获取最佳体验。",
"client": "客户端:",
"proxyProtocol": "代理协议设置",
"proxyProtocolDescription": "配置代理协议以保留TCP/UDP 服务的客户端IP地址。",
"enableProxyProtocol": "启用代理协议",
"proxyProtocolInfo": "为TCP/UDP 后端保留客户端IP地址",
"proxyProtocolVersion": "代理协议版本",
"version1": " 版本 1 (推荐)",
"version2": "版本 2",
"versionDescription": "版本 1 是基于文本和广泛支持的版本。版本 2 是二进制和更有效率但不那么兼容。",
"warning": "警告",
"proxyProtocolWarning": "您的后端应用程序必须配置为接受代理协议连接。如果您的后端不支持代理协议,启用这将会中断所有连接。 请务必从Traefik配置您的后端到信任代理协议标题。",
"restarting": "正在重启...",
"manual": "手动模式",
"messageSupport": "消息支持",
"supportNotAvailableTitle": "支持不可用",
"supportNotAvailableDescription": "支持现在不可用。您可以发送电子邮件到 support@pangolin.net。",
"supportRequestSentTitle": "支持请求已发送",
"supportRequestSentDescription": "您的消息已成功发送。",
"supportRequestFailedTitle": "发送请求失败",
"supportRequestFailedDescription": "发送您的支持请求时出错。",
"supportSubjectRequired": "主题是必填项",
"supportSubjectMaxLength": "主题必须是255个或更少的字符",
"supportMessageRequired": "消息是必填项",
"supportReplyTo": "回复给",
"supportSubject": "议 题",
"supportSubjectPlaceholder": "输入主题",
"supportMessage": "留言",
"supportMessagePlaceholder": "输入您的消息",
"supportSending": "正在发送...",
"supportSend": "发送",
"supportMessageSent": "消息已发送!",
"supportWillContact": "我们很快就会联系起来!",
"selectLogRetention": "选择保留日志",
"showColumns": "显示列",
"hideColumns": "隐藏列",
"columnVisibility": "列可见性",
"toggleColumn": "切换 {columnName} 列",
"allColumns": "全部列",
"defaultColumns": "默认列",
"customizeView": "自定义视图",
"viewOptions": "查看选项",
"selectAll": "选择所有",
"selectNone": "没有选择",
"selectedResources": "选定的资源",
"enableSelected": "启用选中的",
"disableSelected": "禁用选中的",
"checkSelectedStatus": "检查选中的状态"
}

View File

@@ -7,7 +7,8 @@ const nextConfig = {
eslint: {
ignoreDuringBuilds: true
},
output: "standalone"
output: "standalone",
};
export default withNextIntl(nextConfig);

2379
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,8 +33,9 @@
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.4",
"@aws-sdk/client-s3": "3.908.0",
"@aws-sdk/client-s3": "3.922.0",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "^4.7.0",
"@node-rs/argon2": "^2.0.2",
"@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0",
@@ -64,9 +65,9 @@
"@tailwindcss/forms": "^0.5.10",
"@tanstack/react-table": "8.21.3",
"arctic": "^3.7.0",
"axios": "^1.12.2",
"axios": "^1.13.1",
"better-sqlite3": "11.7.0",
"canvas-confetti": "1.9.3",
"canvas-confetti": "1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
@@ -75,42 +76,46 @@
"cookies": "^0.9.1",
"cors": "2.8.5",
"crypto-js": "^4.2.0",
"drizzle-orm": "0.44.6",
"eslint": "9.37.0",
"eslint-config-next": "15.5.6",
"date-fns": "4.1.0",
"drizzle-orm": "0.44.7",
"eslint": "9.39.0",
"eslint-config-next": "16.0.1",
"express": "5.1.0",
"express-rate-limit": "8.1.0",
"express-rate-limit": "8.2.1",
"glob": "11.0.3",
"helmet": "8.1.0",
"http-errors": "2.0.0",
"i": "^0.3.7",
"input-otp": "1.4.2",
"ioredis": "5.8.1",
"ioredis": "5.8.2",
"jmespath": "^0.16.0",
"js-yaml": "4.1.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.545.0",
"lucide-react": "^0.552.0",
"maxmind": "5.0.0",
"moment": "2.30.1",
"next": "15.5.6",
"next-intl": "^4.3.12",
"next-intl": "^4.4.0",
"next-themes": "0.4.6",
"nextjs-toploader": "^3.9.17",
"node-cache": "5.1.2",
"node-fetch": "3.3.2",
"nodemailer": "7.0.9",
"nodemailer": "7.0.10",
"npm": "^11.6.2",
"nprogress": "^0.2.0",
"oslo": "1.2.1",
"pg": "^8.16.2",
"posthog-node": "^5.9.5",
"posthog-node": "^5.11.0",
"qrcode.react": "4.2.0",
"react": "19.2.0",
"react-day-picker": "9.11.1",
"react-dom": "19.2.0",
"react-easy-sort": "^1.8.0",
"react-hook-form": "7.65.0",
"react-hook-form": "7.66.0",
"react-icons": "^5.5.0",
"rebuild": "0.1.2",
"reodotdev": "^1.0.0",
"resend": "^6.1.2",
"resend": "^6.4.0",
"semver": "^7.7.3",
"stripe": "18.2.1",
"swagger-ui-express": "^5.0.1",
@@ -121,43 +126,46 @@
"winston": "3.18.3",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.18.3",
"yaml": "^2.8.1",
"yargs": "18.0.0",
"zod": "3.25.76",
"zod-validation-error": "3.5.2"
"zod-validation-error": "3.5.2",
"@faker-js/faker": "^10.1.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.51.0",
"@dotenvx/dotenvx": "1.51.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/preview-server": "4.3.1",
"@tailwindcss/postcss": "^4.1.14",
"@react-email/preview-server": "4.3.2",
"@tailwindcss/postcss": "^4.1.17",
"@types/better-sqlite3": "7.6.12",
"@types/cookie-parser": "1.4.9",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/crypto-js": "^4.2.2",
"@types/express": "5.0.3",
"@types/express": "5.0.5",
"@types/express-session": "^1.18.2",
"@types/jmespath": "^0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "24.8.1",
"@types/nodemailer": "7.0.2",
"@types/pg": "8.15.5",
"@types/nprogress": "^0.2.3",
"@types/node": "24.9.2",
"@types/nodemailer": "7.0.3",
"@types/pg": "8.15.6",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/semver": "^7.7.1",
"@types/swagger-ui-express": "^4.1.8",
"@types/ws": "8.18.1",
"@types/yargs": "17.0.33",
"drizzle-kit": "0.31.5",
"esbuild": "0.25.11",
"@types/yargs": "17.0.34",
"drizzle-kit": "0.31.6",
"esbuild": "0.25.12",
"esbuild-node-externals": "1.18.0",
"postcss": "^8",
"react-email": "4.3.1",
"react-email": "4.3.2",
"tailwindcss": "^4.1.4",
"tsc-alias": "1.8.16",
"tsx": "4.20.6",
"typescript": "^5",
"typescript-eslint": "^8.46.0"
"typescript-eslint": "^8.46.3"
},
"overrides": {
"emblor": {

View File

@@ -79,6 +79,12 @@ export function createApiServer() {
// Add request timeout middleware
apiServer.use(requestTimeoutMiddleware(60000)); // 60 second timeout
apiServer.use(logIncomingMiddleware);
if (build !== "oss") {
apiServer.use(`${prefix}/hybrid`, hybridRouter); // put before rate limiting because we will rate limit there separately because some of the routes are heavily used
}
if (!dev) {
apiServer.use(
rateLimit({
@@ -101,11 +107,7 @@ export function createApiServer() {
}
// API routes
apiServer.use(logIncomingMiddleware);
apiServer.use(prefix, unauthenticated);
if (build !== "oss") {
apiServer.use(`${prefix}/hybrid`, hybridRouter);
}
apiServer.use(prefix, authenticated);
// WebSocket routes

View File

@@ -81,6 +81,9 @@ export enum ActionsEnum {
listClients = "listClients",
getClient = "getClient",
listOrgDomains = "listOrgDomains",
getDomain = "getDomain",
updateOrgDomain = "updateOrgDomain",
getDNSRecords = "getDNSRecords",
createNewt = "createNewt",
createIdp = "createIdp",
updateIdp = "updateIdp",
@@ -116,7 +119,11 @@ export enum ActionsEnum {
updateLoginPage = "updateLoginPage",
getLoginPage = "getLoginPage",
deleteLoginPage = "deleteLoginPage",
applyBlueprint = "applyBlueprint"
listBlueprints = "listBlueprints",
getBlueprint = "getBlueprint",
applyBlueprint = "applyBlueprint",
viewLogs = "viewLogs",
exportLogs = "exportLogs"
}
export async function checkUserActionPermission(
@@ -193,7 +200,6 @@ export async function checkUserActionPermission(
.limit(1);
return roleActionPermission.length > 0;
} catch (error) {
console.error("Error checking user action permission:", error);
throw createHttpError(

View File

@@ -39,7 +39,8 @@ export async function createSession(
const session: Session = {
sessionId: sessionId,
userId,
expiresAt: new Date(Date.now() + SESSION_COOKIE_EXPIRES).getTime()
expiresAt: new Date(Date.now() + SESSION_COOKIE_EXPIRES).getTime(),
issuedAt: new Date().getTime()
};
await db.insert(sessions).values(session);
return session;

View File

@@ -50,7 +50,8 @@ export async function createResourceSession(opts: {
doNotExtend: opts.doNotExtend || false,
accessTokenId: opts.accessTokenId || null,
isRequestToken: opts.isRequestToken || false,
userSessionId: opts.userSessionId || null
userSessionId: opts.userSessionId || null,
issuedAt: new Date().getTime()
};
await db.insert(resourceSessions).values(session);

View File

@@ -6,7 +6,8 @@ import {
integer,
bigint,
real,
text
text,
index
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
import { domains, orgs, targets, users, exitNodes, sessions } from "./schema";
@@ -166,6 +167,7 @@ export const remoteExitNodes = pgTable("remoteExitNode", {
secretHash: varchar("secretHash").notNull(),
dateCreated: varchar("dateCreated").notNull(),
version: varchar("version"),
secondaryVersion: varchar("secondaryVersion"), // This is to detect the new nodes after the transition to pangolin-node
exitNodeId: integer("exitNodeId").references(() => exitNodes.exitNodeId, {
onDelete: "cascade"
})
@@ -213,6 +215,43 @@ export const sessionTransferToken = pgTable("sessionTransferToken", {
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
});
export const actionAuditLog = pgTable("actionAuditLog", {
id: serial("id").primaryKey(),
timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds
orgId: varchar("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
actorType: varchar("actorType", { length: 50 }).notNull(),
actor: varchar("actor", { length: 255 }).notNull(),
actorId: varchar("actorId", { length: 255 }).notNull(),
action: varchar("action", { length: 100 }).notNull(),
metadata: text("metadata")
}, (table) => ([
index("idx_actionAuditLog_timestamp").on(table.timestamp),
index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp)
]));
export const accessAuditLog = pgTable("accessAuditLog", {
id: serial("id").primaryKey(),
timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds
orgId: varchar("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
actorType: varchar("actorType", { length: 50 }),
actor: varchar("actor", { length: 255 }),
actorId: varchar("actorId", { length: 255 }),
resourceId: integer("resourceId"),
ip: varchar("ip", { length: 45 }),
type: varchar("type", { length: 100 }).notNull(),
action: boolean("action").notNull(),
location: text("location"),
userAgent: text("userAgent"),
metadata: text("metadata")
}, (table) => ([
index("idx_identityAuditLog_timestamp").on(table.timestamp),
index("idx_identityAuditLog_org_timestamp").on(table.orgId, table.timestamp)
]));
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
export type Certificate = InferSelectModel<typeof certificates>;
@@ -230,3 +269,5 @@ export type RemoteExitNodeSession = InferSelectModel<
>;
export type ExitNodeOrg = InferSelectModel<typeof exitNodeOrgs>;
export type LoginPage = InferSelectModel<typeof loginPage>;
export type ActionAuditLog = InferSelectModel<typeof actionAuditLog>;
export type AccessAuditLog = InferSelectModel<typeof accessAuditLog>;

View File

@@ -6,7 +6,8 @@ import {
integer,
bigint,
real,
text
text,
index
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
import { randomUUID } from "crypto";
@@ -18,7 +19,21 @@ export const domains = pgTable("domains", {
type: varchar("type"), // "ns", "cname", "wildcard"
verified: boolean("verified").notNull().default(false),
failed: boolean("failed").notNull().default(false),
tries: integer("tries").notNull().default(0)
tries: integer("tries").notNull().default(0),
certResolver: varchar("certResolver"),
customCertResolver: varchar("customCertResolver"),
preferWildcardCert: boolean("preferWildcardCert")
});
export const dnsRecords = pgTable("dnsRecords", {
id: serial("id").primaryKey(),
domainId: varchar("domainId")
.notNull()
.references(() => domains.domainId, { onDelete: "cascade" }),
recordType: varchar("recordType").notNull(), // "NS" | "CNAME" | "A" | "TXT"
baseDomain: varchar("baseDomain"),
value: varchar("value").notNull(),
verified: boolean("verified").notNull().default(false)
});
export const orgs = pgTable("orgs", {
@@ -26,7 +41,18 @@ export const orgs = pgTable("orgs", {
name: varchar("name").notNull(),
subnet: varchar("subnet"),
createdAt: text("createdAt"),
settings: text("settings") // JSON blob of org-specific settings
requireTwoFactor: boolean("requireTwoFactor"),
maxSessionLengthHours: integer("maxSessionLengthHours"),
passwordExpiryDays: integer("passwordExpiryDays"),
settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever
.notNull()
.default(7),
settingsLogRetentionDaysAccess: integer("settingsLogRetentionDaysAccess")
.notNull()
.default(0),
settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction")
.notNull()
.default(0)
});
export const orgDomains = pgTable("orgDomains", {
@@ -100,9 +126,11 @@ export const resources = pgTable("resources", {
setHostHeader: varchar("setHostHeader"),
enableProxy: boolean("enableProxy").default(true),
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "cascade"
onDelete: "set null"
}),
headers: text("headers") // comma-separated list of headers to add to the request
headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1)
});
export const targets = pgTable("targets", {
@@ -200,7 +228,8 @@ export const users = pgTable("user", {
dateCreated: varchar("dateCreated").notNull(),
termsAcceptedTimestamp: varchar("termsAcceptedTimestamp"),
termsVersion: varchar("termsVersion"),
serverAdmin: boolean("serverAdmin").notNull().default(false)
serverAdmin: boolean("serverAdmin").notNull().default(false),
lastPasswordChange: bigint("lastPasswordChange", { mode: "number" })
});
export const newts = pgTable("newt", {
@@ -226,7 +255,8 @@ export const sessions = pgTable("session", {
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
issuedAt: bigint("issuedAt", { mode: "number" })
});
export const newtSessions = pgTable("newtSession", {
@@ -443,7 +473,8 @@ export const resourceSessions = pgTable("resourceSessions", {
{
onDelete: "cascade"
}
)
),
issuedAt: bigint("issuedAt", { mode: "number" })
});
export const resourceWhitelist = pgTable("resourceWhitelist", {
@@ -671,6 +702,57 @@ export const setupTokens = pgTable("setupTokens", {
dateUsed: varchar("dateUsed")
});
// Blueprint runs
export const blueprints = pgTable("blueprints", {
blueprintId: serial("blueprintId").primaryKey(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
name: varchar("name").notNull(),
source: varchar("source").notNull(),
createdAt: integer("createdAt").notNull(),
succeeded: boolean("succeeded").notNull(),
contents: text("contents").notNull(),
message: text("message")
});
export const requestAuditLog = pgTable(
"requestAuditLog",
{
id: serial("id").primaryKey(),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
action: boolean("action").notNull(),
reason: integer("reason").notNull(),
actorType: text("actorType"),
actor: text("actor"),
actorId: text("actorId"),
resourceId: integer("resourceId"),
ip: text("ip"),
location: text("location"),
userAgent: text("userAgent"),
metadata: text("metadata"),
headers: text("headers"), // JSON blob
query: text("query"), // JSON blob
originalRequestURL: text("originalRequestURL"),
scheme: text("scheme"),
host: text("host"),
path: text("path"),
method: text("method"),
tls: boolean("tls")
},
(table) => [
index("idx_requestAuditLog_timestamp").on(table.timestamp),
index("idx_requestAuditLog_org_timestamp").on(
table.orgId,
table.timestamp
)
]
);
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
export type Site = InferSelectModel<typeof sites>;
@@ -722,3 +804,8 @@ export type SetupToken = InferSelectModel<typeof setupTokens>;
export type HostMeta = InferSelectModel<typeof hostMeta>;
export type TargetHealthCheck = InferSelectModel<typeof targetHealthCheck>;
export type IdpOidcConfig = InferSelectModel<typeof idpOidcConfig>;
export type Blueprint = InferSelectModel<typeof blueprints>;
export type LicenseKey = InferSelectModel<typeof licenseKey>;
export type SecurityKey = InferSelectModel<typeof securityKeys>;
export type WebauthnChallenge = InferSelectModel<typeof webauthnChallenge>;
export type RequestAuditLog = InferSelectModel<typeof requestAuditLog>;

View File

@@ -1,4 +1,4 @@
import { db, loginPage, LoginPage, loginPageOrg } from "@server/db";
import { db, loginPage, LoginPage, loginPageOrg, Org, orgs } from "@server/db";
import {
Resource,
ResourcePassword,
@@ -23,6 +23,7 @@ export type ResourceWithAuth = {
pincode: ResourcePincode | null;
password: ResourcePassword | null;
headerAuth: ResourceHeaderAuth | null;
org: Org;
};
export type UserSessionWithUser = {
@@ -51,6 +52,10 @@ export async function getResourceByDomain(
resourceHeaderAuth,
eq(resourceHeaderAuth.resourceId, resources.resourceId)
)
.innerJoin(
orgs,
eq(orgs.orgId, resources.orgId)
)
.where(eq(resources.fullDomain, domain))
.limit(1);
@@ -62,7 +67,8 @@ export async function getResourceByDomain(
resource: result.resources,
pincode: result.resourcePincode,
password: result.resourcePassword,
headerAuth: result.resourceHeaderAuth
headerAuth: result.resourceHeaderAuth,
org: result.orgs
};
}

View File

@@ -13,12 +13,16 @@ bootstrapVolume();
function createDb() {
const sqlite = new Database(location);
return DrizzleSqlite(sqlite, { schema });
return DrizzleSqlite(sqlite, {
schema
});
}
export const db = createDb();
export default db;
export type Transaction = Parameters<Parameters<typeof db["transaction"]>[0]>[0];
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];
function checkFileExists(filePath: string): boolean {
try {

View File

@@ -2,10 +2,12 @@ import {
sqliteTable,
integer,
text,
real
real,
index
} from "drizzle-orm/sqlite-core";
import { InferSelectModel } from "drizzle-orm";
import { domains, orgs, targets, users, exitNodes, sessions } from "./schema";
import { metadata } from "@app/app/[orgId]/settings/layout";
export const certificates = sqliteTable("certificates", {
certId: integer("certId").primaryKey({ autoIncrement: true }),
@@ -160,6 +162,7 @@ export const remoteExitNodes = sqliteTable("remoteExitNode", {
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
secondaryVersion: text("secondaryVersion"), // This is to detect the new nodes after the transition to pangolin-node
exitNodeId: integer("exitNodeId").references(() => exitNodes.exitNodeId, {
onDelete: "cascade"
})
@@ -207,6 +210,43 @@ export const sessionTransferToken = sqliteTable("sessionTransferToken", {
expiresAt: integer("expiresAt").notNull()
});
export const actionAuditLog = sqliteTable("actionAuditLog", {
id: integer("id").primaryKey({ autoIncrement: true }),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
actorType: text("actorType").notNull(),
actor: text("actor").notNull(),
actorId: text("actorId").notNull(),
action: text("action").notNull(),
metadata: text("metadata")
}, (table) => ([
index("idx_actionAuditLog_timestamp").on(table.timestamp),
index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp)
]));
export const accessAuditLog = sqliteTable("accessAuditLog", {
id: integer("id").primaryKey({ autoIncrement: true }),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
actorType: text("actorType"),
actor: text("actor"),
actorId: text("actorId"),
resourceId: integer("resourceId"),
ip: text("ip"),
location: text("location"),
type: text("type").notNull(),
action: integer("action", { mode: "boolean" }).notNull(),
userAgent: text("userAgent"),
metadata: text("metadata")
}, (table) => ([
index("idx_identityAuditLog_timestamp").on(table.timestamp),
index("idx_identityAuditLog_org_timestamp").on(table.orgId, table.timestamp)
]));
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
export type Certificate = InferSelectModel<typeof certificates>;
@@ -224,3 +264,5 @@ export type RemoteExitNodeSession = InferSelectModel<
>;
export type ExitNodeOrg = InferSelectModel<typeof exitNodeOrgs>;
export type LoginPage = InferSelectModel<typeof loginPage>;
export type ActionAuditLog = InferSelectModel<typeof actionAuditLog>;
export type AccessAuditLog = InferSelectModel<typeof accessAuditLog>;

View File

@@ -1,6 +1,7 @@
import { randomUUID } from "crypto";
import { InferSelectModel } from "drizzle-orm";
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
import { boolean } from "yargs";
export const domains = sqliteTable("domains", {
domainId: text("domainId").primaryKey(),
@@ -11,15 +12,41 @@ export const domains = sqliteTable("domains", {
type: text("type"), // "ns", "cname", "wildcard"
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
failed: integer("failed", { mode: "boolean" }).notNull().default(false),
tries: integer("tries").notNull().default(0)
tries: integer("tries").notNull().default(0),
certResolver: text("certResolver"),
preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" })
});
export const dnsRecords = sqliteTable("dnsRecords", {
id: integer("id").primaryKey({ autoIncrement: true }),
domainId: text("domainId")
.notNull()
.references(() => domains.domainId, { onDelete: "cascade" }),
recordType: text("recordType").notNull(), // "NS" | "CNAME" | "A" | "TXT"
baseDomain: text("baseDomain"),
value: text("value").notNull(),
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
});
export const orgs = sqliteTable("orgs", {
orgId: text("orgId").primaryKey(),
name: text("name").notNull(),
subnet: text("subnet"),
createdAt: text("createdAt"),
settings: text("settings") // JSON blob of org-specific settings
requireTwoFactor: integer("requireTwoFactor", { mode: "boolean" }),
maxSessionLengthHours: integer("maxSessionLengthHours"), // hours
passwordExpiryDays: integer("passwordExpiryDays"), // days
settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever
.notNull()
.default(7),
settingsLogRetentionDaysAccess: integer("settingsLogRetentionDaysAccess")
.notNull()
.default(0),
settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction")
.notNull()
.default(0)
});
export const userDomains = sqliteTable("userDomains", {
@@ -112,9 +139,12 @@ export const resources = sqliteTable("resources", {
setHostHeader: text("setHostHeader"),
enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "cascade"
onDelete: "set null"
}),
headers: text("headers") // comma-separated list of headers to add to the request
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)
});
export const targets = sqliteTable("targets", {
@@ -142,11 +172,15 @@ export const targets = sqliteTable("targets", {
});
export const targetHealthCheck = sqliteTable("targetHealthCheck", {
targetHealthCheckId: integer("targetHealthCheckId").primaryKey({ autoIncrement: true }),
targetHealthCheckId: integer("targetHealthCheckId").primaryKey({
autoIncrement: true
}),
targetId: integer("targetId")
.notNull()
.references(() => targets.targetId, { onDelete: "cascade" }),
hcEnabled: integer("hcEnabled", { mode: "boolean" }).notNull().default(false),
hcEnabled: integer("hcEnabled", { mode: "boolean" })
.notNull()
.default(false),
hcPath: text("hcPath"),
hcScheme: text("hcScheme"),
hcMode: text("hcMode").default("http"),
@@ -156,7 +190,9 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
hcUnhealthyInterval: integer("hcUnhealthyInterval").default(30), // in seconds
hcTimeout: integer("hcTimeout").default(5), // in seconds
hcHeaders: text("hcHeaders"),
hcFollowRedirects: integer("hcFollowRedirects", { mode: "boolean" }).default(true),
hcFollowRedirects: integer("hcFollowRedirects", {
mode: "boolean"
}).default(true),
hcMethod: text("hcMethod").default("GET"),
hcStatus: integer("hcStatus"), // http code
hcHealth: text("hcHealth").default("unknown") // "unknown", "healthy", "unhealthy"
@@ -222,7 +258,8 @@ export const users = sqliteTable("user", {
termsVersion: text("termsVersion"),
serverAdmin: integer("serverAdmin", { mode: "boolean" })
.notNull()
.default(false)
.default(false),
lastPasswordChange: integer("lastPasswordChange")
});
export const securityKeys = sqliteTable("webauthnCredentials", {
@@ -327,7 +364,8 @@ export const sessions = sqliteTable("session", {
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull()
expiresAt: integer("expiresAt").notNull(),
issuedAt: integer("issuedAt")
});
export const newtSessions = sqliteTable("newtSession", {
@@ -577,7 +615,8 @@ export const resourceSessions = sqliteTable("resourceSessions", {
{
onDelete: "cascade"
}
)
),
issuedAt: integer("issuedAt")
});
export const resourceWhitelist = sqliteTable("resourceWhitelist", {
@@ -710,6 +749,59 @@ export const idpOrg = sqliteTable("idpOrg", {
orgMapping: text("orgMapping")
});
// Blueprint runs
export const blueprints = sqliteTable("blueprints", {
blueprintId: integer("blueprintId").primaryKey({
autoIncrement: true
}),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
name: text("name").notNull(),
source: text("source").notNull(),
createdAt: integer("createdAt").notNull(),
succeeded: integer("succeeded", { mode: "boolean" }).notNull(),
contents: text("contents").notNull(),
message: text("message")
});
export const requestAuditLog = sqliteTable(
"requestAuditLog",
{
id: integer("id").primaryKey({ autoIncrement: true }),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
action: integer("action", { mode: "boolean" }).notNull(),
reason: integer("reason").notNull(),
actorType: text("actorType"),
actor: text("actor"),
actorId: text("actorId"),
resourceId: integer("resourceId"),
ip: text("ip"),
location: text("location"),
userAgent: text("userAgent"),
metadata: text("metadata"),
headers: text("headers"), // JSON blob
query: text("query"), // JSON blob
originalRequestURL: text("originalRequestURL"),
scheme: text("scheme"),
host: text("host"),
path: text("path"),
method: text("method"),
tls: integer("tls", { mode: "boolean" })
},
(table) => [
index("idx_requestAuditLog_timestamp").on(table.timestamp),
index("idx_requestAuditLog_org_timestamp").on(
table.orgId,
table.timestamp
)
]
);
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
export type Site = InferSelectModel<typeof sites>;
@@ -746,6 +838,7 @@ export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;
export type VersionMigration = InferSelectModel<typeof versionMigrations>;
export type ResourceRule = InferSelectModel<typeof resourceRules>;
export type Domain = InferSelectModel<typeof domains>;
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
export type Client = InferSelectModel<typeof clients>;
export type ClientSite = InferSelectModel<typeof clientSites>;
export type RoleClient = InferSelectModel<typeof roleClients>;
@@ -761,3 +854,8 @@ export type SetupToken = InferSelectModel<typeof setupTokens>;
export type HostMeta = InferSelectModel<typeof hostMeta>;
export type TargetHealthCheck = InferSelectModel<typeof targetHealthCheck>;
export type IdpOidcConfig = InferSelectModel<typeof idpOidcConfig>;
export type Blueprint = InferSelectModel<typeof blueprints>;
export type LicenseKey = InferSelectModel<typeof licenseKey>;
export type SecurityKey = InferSelectModel<typeof securityKeys>;
export type WebauthnChallenge = InferSelectModel<typeof webauthnChallenge>;
export type RequestAuditLog = InferSelectModel<typeof requestAuditLog>;

View File

@@ -0,0 +1,56 @@
import React from "react";
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailGreeting,
EmailLetterHead,
EmailText
} from "./components/Email";
interface SupportEmailProps {
email: string;
username: string;
subject: string;
body: string;
}
export const SupportEmail = ({
username,
email,
body,
subject
}: SupportEmailProps) => {
const previewText = subject;
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind config={themeColors}>
<Body className="font-sans bg-gray-50">
<EmailContainer>
<EmailLetterHead />
<EmailGreeting>Hi support,</EmailGreeting>
<EmailText>
You have received a new support request from{" "}
<strong>{username}</strong> ({email}).
</EmailText>
<EmailText>
<strong>Subject:</strong> {subject}
</EmailText>
<EmailText>
<strong>Message:</strong> {body}
</EmailText>
</EmailContainer>
</Body>
</Tailwind>
</Html>
);
};
export default SupportEmail;

View File

@@ -5,6 +5,7 @@ import { runSetupFunctions } from "./setup";
import { createApiServer } from "./apiServer";
import { createNextServer } from "./nextServer";
import { createInternalServer } from "./internalServer";
import { createIntegrationApiServer } from "./integrationApiServer";
import {
ApiKey,
ApiKeyOrg,
@@ -13,13 +14,14 @@ import {
User,
UserOrg
} from "@server/db";
import { createIntegrationApiServer } from "./integrationApiServer";
import config from "@server/lib/config";
import { setHostMeta } from "@server/lib/hostMeta";
import { initTelemetryClient } from "./lib/telemetry.js";
import { TraefikConfigManager } from "./lib/traefik/TraefikConfigManager.js";
import { initTelemetryClient } from "@server/lib/telemetry";
import { TraefikConfigManager } from "@server/lib/traefik/TraefikConfigManager";
import { initCleanup } from "#dynamic/cleanup";
import license from "#dynamic/license/license";
import { initLogCleanupInterval } from "@server/lib/cleanupLogs";
import { fetchServerIp } from "@server/lib/serverIpService";
async function startServers() {
await setHostMeta();
@@ -31,14 +33,17 @@ async function startServers() {
await runSetupFunctions();
await fetchServerIp();
initTelemetryClient();
initLogCleanupInterval();
// Start all servers
const apiServer = createApiServer();
const internalServer = createInternalServer();
let nextServer;
nextServer = await createNextServer();
const nextServer = await createNextServer();
if (config.getRawConfig().traefik.file_mode) {
const monitor = new TraefikConfigManager();
await monitor.start();

View File

@@ -1,8 +1,8 @@
export async function getOrgTierData(
orgId: string
): Promise<{ tier: string | null; active: boolean }> {
let tier = null;
let active = false;
const tier = null;
const active = false;
return { tier, active };
}

View File

@@ -1,5 +1,4 @@
import { eq, sql, and } from "drizzle-orm";
import NodeCache from "node-cache";
import { v4 as uuidv4 } from "uuid";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import * as fs from "fs/promises";
@@ -20,6 +19,7 @@ import logger from "@server/logger";
import { sendToClient } from "#dynamic/routers/ws";
import { build } from "@server/build";
import { s3Client } from "@server/lib/s3";
import cache from "@server/lib/cache";
interface StripeEvent {
identifier?: string;
@@ -43,7 +43,6 @@ export function noop() {
}
export class UsageService {
private cache: NodeCache;
private bucketName: string | undefined;
private currentEventFile: string | null = null;
private currentFileStartTime: number = 0;
@@ -51,7 +50,6 @@ export class UsageService {
private uploadingFiles: Set<string> = new Set();
constructor() {
this.cache = new NodeCache({ stdTTL: 300 }); // 5 minute TTL
if (noop()) {
return;
}
@@ -399,7 +397,7 @@ export class UsageService {
featureId: FeatureId
): Promise<string | null> {
const cacheKey = `customer_${orgId}_${featureId}`;
const cached = this.cache.get<string>(cacheKey);
const cached = cache.get<string>(cacheKey);
if (cached) {
return cached;
@@ -422,7 +420,7 @@ export class UsageService {
const customerId = customer.customerId;
// Cache the result
this.cache.set(cacheKey, customerId);
cache.set(cacheKey, customerId, 300); // 5 minute TTL
return customerId;
} catch (error) {
@@ -700,10 +698,6 @@ export class UsageService {
await this.uploadFileToS3();
}
public clearCache(): void {
this.cache.flushAll();
}
/**
* Scan the events directory for files older than 1 minute and upload them if not empty.
*/

View File

@@ -1,22 +1,35 @@
import { db, newts, Target } from "@server/db";
import { db, newts, blueprints, Blueprint } from "@server/db";
import { Config, ConfigSchema } from "./types";
import { ProxyResourcesResults, updateProxyResources } from "./proxyResources";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { resources, targets, sites } from "@server/db";
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
import { sites } from "@server/db";
import { eq, and, isNotNull } from "drizzle-orm";
import { addTargets as addProxyTargets } from "@server/routers/newt/targets";
import { addTargets as addClientTargets } from "@server/routers/client/targets";
import {
ClientResourcesResults,
updateClientResources
} from "./clientResources";
import { BlueprintSource } from "@server/routers/blueprints/types";
import { stringify as stringifyYaml } from "yaml";
import { faker } from "@faker-js/faker";
export async function applyBlueprint(
orgId: string,
configData: unknown,
siteId?: number
): Promise<void> {
type ApplyBlueprintArgs = {
orgId: string;
configData: unknown;
name?: string;
siteId?: number;
source?: BlueprintSource;
};
export async function applyBlueprint({
orgId,
configData,
siteId,
name,
source = "API"
}: ApplyBlueprintArgs): Promise<Blueprint> {
// Validate the input data
const validationResult = ConfigSchema.safeParse(configData);
if (!validationResult.success) {
@@ -24,6 +37,9 @@ export async function applyBlueprint(
}
const config: Config = validationResult.data;
let blueprintSucceeded: boolean = false;
let blueprintMessage: string;
let error: any | null = null;
try {
let proxyResourcesResults: ProxyResourcesResults = [];
@@ -120,10 +136,42 @@ export async function applyBlueprint(
);
}
}
} catch (error) {
logger.error(`Failed to update database from config: ${error}`);
throw error;
blueprintSucceeded = true;
blueprintMessage = "Blueprint applied successfully";
} catch (err) {
blueprintSucceeded = false;
blueprintMessage = `Blueprint applied with errors: ${err}`;
logger.error(blueprintMessage);
error = err;
}
let blueprint: Blueprint | null = null;
await db.transaction(async (trx) => {
const newBlueprint = await trx
.insert(blueprints)
.values({
orgId,
name:
name ??
`${faker.word.adjective()} ${faker.word.adjective()} ${faker.word.noun()}`,
contents: stringifyYaml(configData),
createdAt: Math.floor(Date.now() / 1000),
succeeded: blueprintSucceeded,
message: blueprintMessage,
source
})
.returning();
blueprint = newBlueprint[0];
});
if (!blueprint || (source !== "UI" && !blueprintSucceeded)) {
// ^^^^^^^^^^^^^^^ The UI considers a failed blueprint as a valid response
throw error ?? "Unknown Server Error";
}
return blueprint;
}
// await updateDatabaseFromConfig("org_i21aifypnlyxur2", {

View File

@@ -29,15 +29,29 @@ export async function applyNewtDockerBlueprint(
logger.debug(`Received Docker blueprint: ${JSON.stringify(blueprint)}`);
// make sure this is not an empty object
if (isEmptyObject(blueprint)) {
return;
}
if (isEmptyObject(blueprint["proxy-resources"]) && isEmptyObject(blueprint["client-resources"])) {
return;
}
// Update the blueprint in the database
await applyBlueprint(site.orgId, blueprint, site.siteId);
await applyBlueprint({
orgId: site.orgId,
configData: blueprint,
siteId: site.siteId,
source: "NEWT"
});
} catch (error) {
logger.error(`Failed to update database from config: ${error}`);
await sendToClient(newtId, {
type: "newt/blueprint/results",
data: {
success: false,
message: `Failed to update database from config: ${error}`
message: `Failed to apply blueprint from config: ${error}`
}
});
return;
@@ -51,3 +65,10 @@ export async function applyNewtDockerBlueprint(
}
});
}
function isEmptyObject(obj: any) {
if (obj === null || obj === undefined) {
return true;
}
return Object.keys(obj).length === 0 && obj.constructor === Object;
}

View File

@@ -30,6 +30,7 @@ import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { get } from "http";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -114,7 +115,12 @@ export async function updateProxyResources(
internalPort: internalPortToCreate,
path: targetData.path,
pathMatchType: targetData["path-match"],
rewritePath: targetData.rewritePath,
rewritePath:
targetData.rewritePath ||
targetData["rewrite-path"] ||
(targetData["rewrite-match"] === "stripPrefix"
? "/"
: undefined),
rewritePathType: targetData["rewrite-match"],
priority: targetData.priority
})
@@ -139,10 +145,14 @@ export async function updateProxyResources(
hcHostname: healthcheckData?.hostname,
hcPort: healthcheckData?.port,
hcInterval: healthcheckData?.interval,
hcUnhealthyInterval: healthcheckData?.unhealthyInterval,
hcUnhealthyInterval:
healthcheckData?.unhealthyInterval ||
healthcheckData?.["unhealthy-interval"],
hcTimeout: healthcheckData?.timeout,
hcHeaders: hcHeaders,
hcFollowRedirects: healthcheckData?.followRedirects,
hcFollowRedirects:
healthcheckData?.followRedirects ||
healthcheckData?.["follow-redirects"],
hcMethod: healthcheckData?.method,
hcStatus: healthcheckData?.status,
hcHealth: "unknown"
@@ -392,7 +402,12 @@ export async function updateProxyResources(
enabled: targetData.enabled,
path: targetData.path,
pathMatchType: targetData["path-match"],
rewritePath: targetData.rewritePath,
rewritePath:
targetData.rewritePath ||
targetData["rewrite-path"] ||
(targetData["rewrite-match"] === "stripPrefix"
? "/"
: undefined),
rewritePathType: targetData["rewrite-match"],
priority: targetData.priority
})
@@ -452,10 +467,13 @@ export async function updateProxyResources(
hcPort: healthcheckData?.port,
hcInterval: healthcheckData?.interval,
hcUnhealthyInterval:
healthcheckData?.unhealthyInterval,
healthcheckData?.unhealthyInterval ||
healthcheckData?.["unhealthy-interval"],
hcTimeout: healthcheckData?.timeout,
hcHeaders: hcHeaders,
hcFollowRedirects: healthcheckData?.followRedirects,
hcFollowRedirects:
healthcheckData?.followRedirects ||
healthcheckData?.["follow-redirects"],
hcMethod: healthcheckData?.method,
hcStatus: healthcheckData?.status
})
@@ -527,7 +545,7 @@ export async function updateProxyResources(
if (
existingRule.action !== getRuleAction(rule.action) ||
existingRule.match !== rule.match.toUpperCase() ||
existingRule.value !== rule.value
existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value)
) {
validateRule(rule);
await trx
@@ -535,7 +553,7 @@ export async function updateProxyResources(
.set({
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value
value: getRuleValue(rule.match.toUpperCase(), rule.value),
})
.where(
eq(resourceRules.ruleId, existingRule.ruleId)
@@ -547,7 +565,7 @@ export async function updateProxyResources(
resourceId: existingResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value,
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -705,7 +723,7 @@ export async function updateProxyResources(
resourceId: newResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value,
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -735,6 +753,14 @@ function getRuleAction(input: string) {
return action;
}
function getRuleValue(match: string, value: string) {
// if the match is a country, uppercase the value
if (match == "COUNTRY") {
return value.toUpperCase();
}
return value;
}
function validateRule(rule: any) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {

View File

@@ -13,10 +13,12 @@ export const TargetHealthCheckSchema = z.object({
scheme: z.string().optional(),
mode: z.string().default("http"),
interval: z.number().int().default(30),
unhealthyInterval: z.number().int().default(30),
"unhealthy-interval": z.number().int().default(30),
unhealthyInterval: z.number().int().optional(), // deprecated alias
timeout: z.number().int().default(5),
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional().default(null),
followRedirects: z.boolean().default(true),
"follow-redirects": z.boolean().default(true),
followRedirects: z.boolean().optional(), // deprecated alias
method: z.string().default("GET"),
status: z.number().int().optional()
});
@@ -32,7 +34,8 @@ export const TargetSchema = z.object({
path: z.string().optional(),
"path-match": z.enum(["exact", "prefix", "regex"]).optional().nullable(),
healthcheck: TargetHealthCheckSchema.optional(),
rewritePath: z.string().optional(),
rewritePath: z.string().optional(), // deprecated alias
"rewrite-path": z.string().optional(),
"rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(),
priority: z.number().int().min(1).max(1000).optional().default(100)
});
@@ -275,24 +278,26 @@ export const ConfigSchema = z
}
)
.refine(
// Enforce proxy-port uniqueness within proxy-resources
// Enforce proxy-port uniqueness within proxy-resources per protocol
(config) => {
const proxyPortMap = new Map<number, string[]>();
const protocolPortMap = new Map<string, string[]>();
Object.entries(config["proxy-resources"]).forEach(
([resourceKey, resource]) => {
const proxyPort = resource["proxy-port"];
if (proxyPort !== undefined) {
if (!proxyPortMap.has(proxyPort)) {
proxyPortMap.set(proxyPort, []);
const protocol = resource.protocol;
if (proxyPort !== undefined && protocol !== undefined) {
const key = `${protocol}:${proxyPort}`;
if (!protocolPortMap.has(key)) {
protocolPortMap.set(key, []);
}
proxyPortMap.get(proxyPort)!.push(resourceKey);
protocolPortMap.get(key)!.push(resourceKey);
}
}
);
// Find duplicates
const duplicates = Array.from(proxyPortMap.entries()).filter(
const duplicates = Array.from(protocolPortMap.entries()).filter(
([_, resourceKeys]) => resourceKeys.length > 1
);
@@ -300,25 +305,29 @@ export const ConfigSchema = z
},
(config) => {
// Extract duplicates for error message
const proxyPortMap = new Map<number, string[]>();
const protocolPortMap = new Map<string, string[]>();
Object.entries(config["proxy-resources"]).forEach(
([resourceKey, resource]) => {
const proxyPort = resource["proxy-port"];
if (proxyPort !== undefined) {
if (!proxyPortMap.has(proxyPort)) {
proxyPortMap.set(proxyPort, []);
const protocol = resource.protocol;
if (proxyPort !== undefined && protocol !== undefined) {
const key = `${protocol}:${proxyPort}`;
if (!protocolPortMap.has(key)) {
protocolPortMap.set(key, []);
}
proxyPortMap.get(proxyPort)!.push(resourceKey);
protocolPortMap.get(key)!.push(resourceKey);
}
}
);
const duplicates = Array.from(proxyPortMap.entries())
const duplicates = Array.from(protocolPortMap.entries())
.filter(([_, resourceKeys]) => resourceKeys.length > 1)
.map(
([proxyPort, resourceKeys]) =>
`port ${proxyPort} used by proxy-resources: ${resourceKeys.join(", ")}`
([protocolPort, resourceKeys]) => {
const [protocol, port] = protocolPort.split(':');
return `${protocol.toUpperCase()} port ${port} used by proxy-resources: ${resourceKeys.join(", ")}`;
}
)
.join("; ");

5
server/lib/cache.ts Normal file
View File

@@ -0,0 +1,5 @@
import NodeCache from "node-cache";
export const cache = new NodeCache({ stdTTL: 3600, checkperiod: 120 });
export default cache;

View File

@@ -0,0 +1,41 @@
import { Org, ResourceSession, Session, User } from "@server/db";
export type CheckOrgAccessPolicyProps = {
orgId?: string;
org?: Org;
userId?: string;
user?: User;
sessionId?: string;
session?: Session;
};
export type CheckOrgAccessPolicyResult = {
allowed: boolean;
error?: string;
policies?: {
requiredTwoFactor?: boolean;
maxSessionLength?: {
compliant: boolean;
maxSessionLengthHours: number;
sessionAgeHours: number;
};
passwordAge?: {
compliant: boolean;
maxPasswordAgeDays: number;
passwordAgeDays: number;
};
};
};
export async function enforceResourceSessionLength(
resourceSession: ResourceSession,
org: Org
): Promise<{ valid: boolean; error?: string }> {
return { valid: true };
}
export async function checkOrgAccessPolicy(
props: CheckOrgAccessPolicyProps
): Promise<CheckOrgAccessPolicyResult> {
return { allowed: true };
}

62
server/lib/cleanupLogs.ts Normal file
View File

@@ -0,0 +1,62 @@
import { db, orgs } from "@server/db";
import { cleanUpOldLogs as cleanUpOldAccessLogs } from "#dynamic/lib/logAccessAudit";
import { cleanUpOldLogs as cleanUpOldActionLogs } from "#dynamic/middlewares/logActionAudit";
import { cleanUpOldLogs as cleanUpOldRequestLogs } from "@server/routers/badger/logRequestAudit";
import { gt, or } from "drizzle-orm";
export function initLogCleanupInterval() {
return setInterval(
async () => {
const orgsToClean = await db
.select({
orgId: orgs.orgId,
settingsLogRetentionDaysAction:
orgs.settingsLogRetentionDaysAction,
settingsLogRetentionDaysAccess:
orgs.settingsLogRetentionDaysAccess,
settingsLogRetentionDaysRequest:
orgs.settingsLogRetentionDaysRequest
})
.from(orgs)
.where(
or(
gt(orgs.settingsLogRetentionDaysAction, 0),
gt(orgs.settingsLogRetentionDaysAccess, 0),
gt(orgs.settingsLogRetentionDaysRequest, 0)
)
);
for (const org of orgsToClean) {
const {
orgId,
settingsLogRetentionDaysAction,
settingsLogRetentionDaysAccess,
settingsLogRetentionDaysRequest
} = org;
if (settingsLogRetentionDaysAction > 0) {
await cleanUpOldActionLogs(
orgId,
settingsLogRetentionDaysRequest
);
}
if (settingsLogRetentionDaysAccess > 0) {
await cleanUpOldAccessLogs(
orgId,
settingsLogRetentionDaysRequest
);
}
if (settingsLogRetentionDaysRequest > 0) {
await cleanUpOldRequestLogs(
orgId,
settingsLogRetentionDaysRequest
);
}
}
},
// 3 * 60 * 60 * 1000
60 * 1000 // for testing
); // every 3 hours
}

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.11.0";
export const APP_VERSION = "1.12.1";
export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME);

View File

@@ -6,7 +6,7 @@ export async function getCountryCodeForIp(
): Promise<string | undefined> {
try {
if (!maxmindLookup) {
logger.warn(
logger.debug(
"MaxMind DB path not configured, cannot perform GeoIP lookup"
);
return;

View File

@@ -0,0 +1,17 @@
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
return;
}
export async function logAccessAudit(data: {
action: boolean;
type: string;
orgId: string;
resourceId?: number;
user?: { username: string; userId: string };
apiKey?: { name: string | null; apiKeyId: string };
metadata?: any;
userAgent?: string;
requestIp?: string;
}) {
return;
}

View File

@@ -50,7 +50,7 @@ export const configSchema = z
.string()
.nonempty("base_domain must not be empty")
.transform((url) => url.toLowerCase()),
cert_resolver: z.string().optional().default("letsencrypt"),
cert_resolver: z.string().optional(), // null falls back to traefik.cert_resolver
prefer_wildcard_cert: z.boolean().optional().default(false)
})
)
@@ -204,7 +204,8 @@ export const configSchema = z
.optional()
.default(["newt", "wireguard", "local"]),
allow_raw_resources: z.boolean().optional().default(true),
file_mode: z.boolean().optional().default(false)
file_mode: z.boolean().optional().default(false),
pp_transport_prefix: z.string().optional().default("pp-transport-v")
})
.optional()
.default({}),

View File

@@ -1,7 +1,8 @@
export enum AudienceIds {
General = "",
Subscribed = "",
Churned = ""
SignUps = "",
Subscribed = "",
Churned = "",
Newsletter = ""
}
let resend;
@@ -12,4 +13,4 @@ export async function moveEmailToAudience(
audienceId: AudienceIds
) {
return;
}
}

View File

@@ -0,0 +1,29 @@
import logger from "@server/logger";
import axios from "axios";
let serverIp: string | null = null;
const services = [
"https://checkip.amazonaws.com",
"https://ifconfig.io/ip",
"https://api.ipify.org",
];
export async function fetchServerIp() {
for (const url of services) {
try {
const response = await axios.get(url, { timeout: 5000 });
serverIp = response.data.trim();
logger.debug("Detected public IP: " + serverIp);
return;
} catch (err: any) {
console.warn(`Failed to fetch server IP from ${url}: ${err.message || err.code}`);
}
}
console.error("All attempts to fetch server IP failed.");
}
export function getServerIp() {
return serverIp;
}

View File

@@ -200,10 +200,7 @@ class TelemetryClient {
event: "supporter_status",
properties: {
valid: stats.supporterStatus.valid,
tier: stats.supporterStatus.tier,
github_username: stats.supporterStatus.githubUsername
? this.anon(stats.supporterStatus.githubUsername)
: "None"
tier: stats.supporterStatus.tier
}
});
}
@@ -217,21 +214,6 @@ class TelemetryClient {
install_timestamp: hostMeta.createdAt
}
});
for (const email of stats.adminUsers) {
// There should only be on admin user, but just in case
if (email) {
this.client.capture({
distinctId: this.anon(email),
event: "admin_user",
properties: {
host_id: hostMeta.hostMetaId,
app_version: stats.appVersion,
hashed_email: this.anon(email)
}
});
}
}
}
private async collectAndSendAnalytics() {
@@ -262,19 +244,38 @@ class TelemetryClient {
num_clients: stats.numClients,
num_identity_providers: stats.numIdentityProviders,
num_sites_online: stats.numSitesOnline,
resources: stats.resources.map((r) => ({
name: this.anon(r.name),
sso_enabled: r.sso,
protocol: r.protocol,
http_enabled: r.http
})),
sites: stats.sites.map((s) => ({
site_name: this.anon(s.siteName),
megabytes_in: s.megabytesIn,
megabytes_out: s.megabytesOut,
type: s.type,
online: s.online
})),
num_resources_sso_enabled: stats.resources.filter(
(r) => r.sso
).length,
num_resources_non_http: stats.resources.filter(
(r) => !r.http
).length,
num_newt_sites: stats.sites.filter((s) => s.type === "newt")
.length,
num_local_sites: stats.sites.filter(
(s) => s.type === "local"
).length,
num_wg_sites: stats.sites.filter(
(s) => s.type === "wireguard"
).length,
avg_megabytes_in:
stats.sites.length > 0
? Math.round(
stats.sites.reduce(
(sum, s) => sum + (s.megabytesIn ?? 0),
0
) / stats.sites.length
)
: 0,
avg_megabytes_out:
stats.sites.length > 0
? Math.round(
stats.sites.reduce(
(sum, s) => sum + (s.megabytesOut ?? 0),
0
) / stats.sites.length
)
: 0,
num_api_keys: stats.numApiKeys,
num_custom_roles: stats.numCustomRoles
}

View File

@@ -309,10 +309,7 @@ export class TraefikConfigManager {
this.lastActiveDomains = new Set(domains);
}
if (
process.env.USE_PANGOLIN_DNS === "true" &&
build != "oss"
) {
if (process.env.USE_PANGOLIN_DNS === "true" && build != "oss") {
// Scan current local certificate state
this.lastLocalCertificateState =
await this.scanLocalCertificateState();
@@ -450,7 +447,8 @@ export class TraefikConfigManager {
currentExitNode,
config.getRawConfig().traefik.site_types,
build == "oss", // filter out the namespace domains in open source
build != "oss" // generate the login pages on the cloud and hybrid
build != "oss", // generate the login pages on the cloud and hybrid,
build == "saas" ? false : config.getRawConfig().traefik.allow_raw_resources // dont allow raw resources on saas otherwise use config
);
const domains = new Set<string>();
@@ -502,6 +500,25 @@ export class TraefikConfigManager {
};
}
// tcp:
// serversTransports:
// pp-transport-v1:
// proxyProtocol:
// version: 1
// pp-transport-v2:
// proxyProtocol:
// version: 2
if (build != "saas") {
// add the serversTransports section if not present
if (traefikConfig.tcp && !traefikConfig.tcp.serversTransports) {
traefikConfig.tcp.serversTransports = {
"pp-transport-v1": { proxyProtocol: { version: 1 } },
"pp-transport-v2": { proxyProtocol: { version: 2 } }
};
}
}
return { domains, traefikConfig };
} catch (error) {
// pull data out of the axios error to log

View File

@@ -1,4 +1,4 @@
import { db, targetHealthCheck } from "@server/db";
import { db, targetHealthCheck, domains } from "@server/db";
import {
and,
eq,
@@ -23,7 +23,8 @@ export async function getTraefikConfig(
exitNodeId: number,
siteTypes: string[],
filterOutNamespaceDomains = false,
generateLoginPageRouters = false
generateLoginPageRouters = false,
allowRawResources = true
): Promise<any> {
// Define extended target type with site information
type TargetWithSite = Target & {
@@ -56,6 +57,8 @@ export async function getTraefikConfig(
setHostHeader: resources.setHostHeader,
enableProxy: resources.enableProxy,
headers: resources.headers,
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
// Target fields
targetId: targets.targetId,
targetEnabled: targets.enabled,
@@ -75,11 +78,15 @@ export async function getTraefikConfig(
siteType: sites.type,
siteOnline: sites.online,
subnet: sites.subnet,
exitNodeId: sites.exitNodeId
exitNodeId: sites.exitNodeId,
// Domain cert resolver fields
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
.innerJoin(resources, eq(resources.resourceId, targets.resourceId))
.leftJoin(domains, eq(domains.domainId, resources.domainId))
.leftJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
@@ -101,7 +108,7 @@ export async function getTraefikConfig(
isNull(targetHealthCheck.hcHealth) // Include targets with no health check record
),
inArray(sites.type, siteTypes),
config.getRawConfig().traefik.allow_raw_resources
allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
: eq(resources.http, true)
)
@@ -164,11 +171,16 @@ export async function getTraefikConfig(
enableProxy: row.enableProxy,
targets: [],
headers: row.headers,
proxyProtocol: row.proxyProtocol,
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
path: row.path, // the targets will all have the same path
pathMatchType: row.pathMatchType, // the targets will all have the same pathMatchType
rewritePath: row.rewritePath,
rewritePathType: row.rewritePathType,
priority: priority // may be null, we fallback later
priority: priority,
// Store domain cert resolver fields
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}
@@ -247,21 +259,35 @@ export async function getTraefikConfig(
wildCard = resource.fullDomain;
}
const configDomain = config.getDomain(resource.domainId);
const globalDefaultResolver =
config.getRawConfig().traefik.cert_resolver;
const globalDefaultPreferWildcard =
config.getRawConfig().traefik.prefer_wildcard_cert;
let certResolver: string, preferWildcardCert: boolean;
if (!configDomain) {
certResolver = config.getRawConfig().traefik.cert_resolver;
preferWildcardCert =
config.getRawConfig().traefik.prefer_wildcard_cert;
const domainCertResolver = resource.domainCertResolver;
const preferWildcardCert = resource.preferWildcardCert;
let resolverName: string | undefined;
let preferWildcard: boolean | undefined;
// Handle both letsencrypt & custom cases
if (domainCertResolver) {
resolverName = domainCertResolver.trim();
} else {
certResolver = configDomain.cert_resolver;
preferWildcardCert = configDomain.prefer_wildcard_cert;
resolverName = globalDefaultResolver;
}
if (
preferWildcardCert !== undefined &&
preferWildcardCert !== null
) {
preferWildcard = preferWildcardCert;
} else {
preferWildcard = globalDefaultPreferWildcard;
}
const tls = {
certResolver: certResolver,
...(preferWildcardCert
certResolver: resolverName,
...(preferWildcard
? {
domains: [
{
@@ -562,6 +588,8 @@ export async function getTraefikConfig(
...(protocol === "tcp" ? { rule: "HostSNI(`*`)" } : {})
};
const ppPrefix = config.getRawConfig().traefik.pp_transport_prefix;
config_output[protocol].services[serviceName] = {
loadBalancer: {
servers: (() => {
@@ -615,6 +643,11 @@ export async function getTraefikConfig(
}
});
})(),
...(resource.proxyProtocol && protocol == "tcp"
? {
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
}
: {}),
...(resource.stickySession
? {
sticky: {

View File

@@ -40,6 +40,10 @@ export class License {
public setServerSecret(secret: string) {
this.serverSecret = secret;
}
public async isUnlocked() {
return false;
}
}
await setHostMeta();

View File

@@ -27,3 +27,4 @@ export * from "./verifyDomainAccess";
export * from "./verifyClientsEnabled";
export * from "./verifyUserIsOrgOwner";
export * from "./verifySiteResourceAccess";
export * from "./logActionAudit";

View File

@@ -0,0 +1,16 @@
import { ActionsEnum } from "@server/auth/actions";
import { Request, Response, NextFunction } from "express";
export function logActionAudit(action: ActionsEnum) {
return async function (
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
next();
};
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
return;
}

View File

@@ -1,9 +1,11 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { db, orgs } from "@server/db";
import { userOrgs } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import logger from "@server/logger";
export async function verifyOrgAccess(
req: Request,
@@ -43,12 +45,30 @@ export async function verifyOrgAccess(
"User does not have access to this organization"
)
);
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRoleId = req.userOrg.roleId;
req.userOrgId = orgId;
return next();
}
const policyCheck = await checkOrgAccessPolicy({
orgId,
userId,
session: req.session
});
logger.debug("Org check policy result", { policyCheck });
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
// User has access, attach the user's role to the request for potential future use
req.userOrgRoleId = req.userOrg.roleId;
req.userOrgId = orgId;
return next();
} catch (e) {
return next(
createHttpError(

View File

@@ -9,7 +9,7 @@ const nextPort = config.getRawConfig().server.next_port;
export async function createNextServer() {
// const app = next({ dev });
const app = next({ dev: process.env.ENVIRONMENT !== "prod" });
const app = next({ dev: process.env.ENVIRONMENT !== "prod", turbopack: true });
const handle = app.getRequestHandler();
await app.prepare();

View File

@@ -15,5 +15,6 @@ export enum OpenAPITags {
Idp = "Identity Provider",
Client = "Client",
ApiKey = "API Key",
Domain = "Domain"
Domain = "Domain",
Blueprint = "Blueprint"
}

View File

@@ -16,8 +16,8 @@ import { certificates, db } from "@server/db";
import { and, eq, isNotNull, or, inArray, sql } from "drizzle-orm";
import { decryptData } from "@server/lib/encryption";
import * as fs from "fs";
import NodeCache from "node-cache";
import logger from "@server/logger";
import cache from "@server/lib/cache";
let encryptionKeyPath = "";
let encryptionKeyHex = "";
@@ -51,9 +51,6 @@ export type CertificateResult = {
updatedAt?: number | null;
};
// --- In-Memory Cache Implementation ---
const certificateCache = new NodeCache({ stdTTL: 180 }); // Cache for 3 minutes (180 seconds)
export async function getValidCertificatesForDomains(
domains: Set<string>,
useCache: boolean = true
@@ -67,7 +64,8 @@ export async function getValidCertificatesForDomains(
// 1. Check cache first if enabled
if (useCache) {
for (const domain of domains) {
const cachedCert = certificateCache.get<CertificateResult>(domain);
const cacheKey = `cert:${domain}`;
const cachedCert = cache.get<CertificateResult>(cacheKey);
if (cachedCert) {
finalResults.push(cachedCert); // Valid cache hit
} else {
@@ -180,7 +178,8 @@ export async function getValidCertificatesForDomains(
// Add to cache for future requests, using the *requested domain* as the key
if (useCache) {
certificateCache.set(domain, resultCert);
const cacheKey = `cert:${domain}`;
cache.set(cacheKey, resultCert, 180);
}
}
}

View File

@@ -0,0 +1,201 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 {
db,
Org,
orgs,
ResourceSession,
sessions,
users
} from "@server/db";
import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/billing/tiers";
import license from "#private/license/license";
import { eq } from "drizzle-orm";
import {
CheckOrgAccessPolicyProps,
CheckOrgAccessPolicyResult
} from "@server/lib/checkOrgAccessPolicy";
import { UserType } from "@server/types/UserTypes";
export async function enforceResourceSessionLength(
resourceSession: ResourceSession,
org: Org
): Promise<{ valid: boolean; error?: string }> {
if (org.maxSessionLengthHours) {
const sessionIssuedAt = resourceSession.issuedAt; // may be null
const maxSessionLengthHours = org.maxSessionLengthHours;
if (sessionIssuedAt) {
const maxSessionLengthMs = maxSessionLengthHours * 60 * 60 * 1000;
const sessionAgeMs = Date.now() - sessionIssuedAt;
if (sessionAgeMs > maxSessionLengthMs) {
return {
valid: false,
error: `Resource session has expired due to organization policy (max session length: ${maxSessionLengthHours} hours)`
};
}
} else {
return {
valid: false,
error: `Resource session is invalid due to organization policy (max session length: ${maxSessionLengthHours} hours)`
};
}
}
return { valid: true };
}
export async function checkOrgAccessPolicy(
props: CheckOrgAccessPolicyProps
): Promise<CheckOrgAccessPolicyResult> {
const userId = props.userId || props.user?.userId;
const orgId = props.orgId || props.org?.orgId;
const sessionId = props.sessionId || props.session?.sessionId;
if (!orgId) {
return {
allowed: false,
error: "Organization ID is required"
};
}
if (!userId) {
return { allowed: false, error: "User ID is required" };
}
if (!sessionId) {
return { allowed: false, error: "Session ID is required" };
}
if (build === "enterprise") {
const isUnlocked = await license.isUnlocked();
// if not licensed, don't check the policies
if (!isUnlocked) {
return { allowed: true };
}
}
// get the needed data
if (!props.org) {
const [orgQuery] = await db
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
props.org = orgQuery;
if (!props.org) {
return { allowed: false, error: "Organization not found" };
}
}
if (!props.user) {
const [userQuery] = await db
.select()
.from(users)
.where(eq(users.userId, userId));
props.user = userQuery;
if (!props.user) {
return { allowed: false, error: "User not found" };
}
}
if (!props.session) {
const [sessionQuery] = await db
.select()
.from(sessions)
.where(eq(sessions.sessionId, sessionId));
props.session = sessionQuery;
if (!props.session) {
return { allowed: false, error: "Session not found" };
}
}
if (props.session.userId !== props.user.userId) {
return {
allowed: false,
error: "Session does not belong to the user"
};
}
// now check the policies
const policies: CheckOrgAccessPolicyResult["policies"] = {};
// only applies to internal users; oidc users 2fa is managed by the IDP
if (props.user.type === UserType.Internal && props.org.requireTwoFactor) {
policies.requiredTwoFactor = props.user.twoFactorEnabled || false;
}
// applies to all users
if (props.org.maxSessionLengthHours) {
const sessionIssuedAt = props.session.issuedAt; // may be null
const maxSessionLengthHours = props.org.maxSessionLengthHours;
if (sessionIssuedAt) {
const maxSessionLengthMs = maxSessionLengthHours * 60 * 60 * 1000;
const sessionAgeMs = Date.now() - sessionIssuedAt;
policies.maxSessionLength = {
compliant: sessionAgeMs <= maxSessionLengthMs,
maxSessionLengthHours,
sessionAgeHours: sessionAgeMs / (60 * 60 * 1000)
};
} else {
policies.maxSessionLength = {
compliant: false,
maxSessionLengthHours,
sessionAgeHours: maxSessionLengthHours
};
}
}
// only applies to internal users; oidc users don't have passwords
if (props.user.type === UserType.Internal && props.org.passwordExpiryDays) {
if (props.user.lastPasswordChange) {
const passwordExpiryDays = props.org.passwordExpiryDays;
const passwordAgeMs = Date.now() - props.user.lastPasswordChange;
const passwordAgeDays = passwordAgeMs / (24 * 60 * 60 * 1000);
policies.passwordAge = {
compliant: passwordAgeDays <= passwordExpiryDays,
maxPasswordAgeDays: passwordExpiryDays,
passwordAgeDays: passwordAgeDays
};
} else {
policies.passwordAge = {
compliant: false,
maxPasswordAgeDays: props.org.passwordExpiryDays,
passwordAgeDays: props.org.passwordExpiryDays // Treat as expired
};
}
}
let allowed = true;
if (policies.requiredTwoFactor === false) {
allowed = false;
}
if (
policies.maxSessionLength &&
policies.maxSessionLength.compliant === false
) {
allowed = false;
}
if (policies.passwordAge && policies.passwordAge.compliant === false) {
allowed = false;
}
return {
allowed,
policies
};
}

View File

@@ -0,0 +1,170 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { accessAuditLog, db, orgs } from "@server/db";
import { getCountryCodeForIp } from "@server/lib/geoip";
import logger from "@server/logger";
import { and, eq, lt } from "drizzle-orm";
import cache from "@server/lib/cache";
async function getAccessDays(orgId: string): Promise<number> {
// check cache first
const cached = cache.get<number>(`org_${orgId}_accessDays`);
if (cached !== undefined) {
return cached;
}
const [org] = await db
.select({
settingsLogRetentionDaysAction: orgs.settingsLogRetentionDaysAction
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
return 0;
}
// store the result in cache
cache.set(
`org_${orgId}_accessDays`,
org.settingsLogRetentionDaysAction,
300
);
return org.settingsLogRetentionDaysAction;
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
const now = Math.floor(Date.now() / 1000);
const cutoffTimestamp = now - retentionDays * 24 * 60 * 60;
try {
await db
.delete(accessAuditLog)
.where(
and(
lt(accessAuditLog.timestamp, cutoffTimestamp),
eq(accessAuditLog.orgId, orgId)
)
);
logger.debug(
`Cleaned up access audit logs older than ${retentionDays} days`
);
} catch (error) {
logger.error("Error cleaning up old action audit logs:", error);
}
}
export async function logAccessAudit(data: {
action: boolean;
type: string;
orgId: string;
resourceId?: number;
user?: { username: string; userId: string };
apiKey?: { name: string | null; apiKeyId: string };
metadata?: any;
userAgent?: string;
requestIp?: string;
}) {
try {
const retentionDays = await getAccessDays(data.orgId);
if (retentionDays === 0) {
// do not log
return;
}
let actorType: string | undefined;
let actor: string | undefined;
let actorId: string | undefined;
const user = data.user;
if (user) {
actorType = "user";
actor = user.username;
actorId = user.userId;
}
const apiKey = data.apiKey;
if (apiKey) {
actorType = "apiKey";
actor = apiKey.name || apiKey.apiKeyId;
actorId = apiKey.apiKeyId;
}
// if (!actorType || !actor || !actorId) {
// logger.warn("logRequestAudit: Incomplete actor information");
// return;
// }
const timestamp = Math.floor(Date.now() / 1000);
let metadata = null;
if (metadata) {
metadata = JSON.stringify(metadata);
}
const clientIp = data.requestIp
? (() => {
if (
data.requestIp.startsWith("[") &&
data.requestIp.includes("]")
) {
// if brackets are found, extract the IPv6 address from between the brackets
const ipv6Match = data.requestIp.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
return data.requestIp;
})()
: undefined;
const countryCode = data.requestIp
? await getCountryCodeFromIp(data.requestIp)
: undefined;
await db.insert(accessAuditLog).values({
timestamp: timestamp,
orgId: data.orgId,
actorType,
actor,
actorId,
action: data.action,
type: data.type,
metadata,
resourceId: data.resourceId,
userAgent: data.userAgent,
ip: clientIp,
location: countryCode
});
} catch (error) {
logger.error(error);
}
}
async function getCountryCodeFromIp(ip: string): Promise<string | undefined> {
const geoIpCacheKey = `geoip_access:${ip}`;
let cachedCountryCode: string | undefined = cache.get(geoIpCacheKey);
if (!cachedCountryCode) {
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
// Cache for longer since IP geolocation doesn't change frequently
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
}
return cachedCountryCode;
}

View File

@@ -72,6 +72,43 @@ export class RateLimitService {
return `ratelimit:${clientId}:${messageType}`;
}
// Helper function to clean up old timestamp fields from a Redis hash
private async cleanupOldTimestamps(key: string, windowStart: number): Promise<void> {
if (!redisManager.isRedisEnabled()) return;
try {
const client = redisManager.getClient();
if (!client) return;
// Get all fields in the hash
const allData = await redisManager.hgetall(key);
if (!allData || Object.keys(allData).length === 0) return;
// Find fields that are older than the window
const fieldsToDelete: string[] = [];
for (const timestamp of Object.keys(allData)) {
const time = parseInt(timestamp);
if (time < windowStart) {
fieldsToDelete.push(timestamp);
}
}
// Delete old fields in batches to avoid call stack size exceeded errors
// The spread operator can cause issues with very large arrays
if (fieldsToDelete.length > 0) {
const batchSize = 1000; // Process 1000 fields at a time
for (let i = 0; i < fieldsToDelete.length; i += batchSize) {
const batch = fieldsToDelete.slice(i, i + batchSize);
await client.hdel(key, ...batch);
}
logger.debug(`Cleaned up ${fieldsToDelete.length} old timestamp fields from ${key}`);
}
} catch (error) {
logger.error(`Failed to cleanup old timestamps for key ${key}:`, error);
// Don't throw - cleanup failures shouldn't block rate limiting
}
}
// Helper function to sync local rate limit data to Redis
private async syncRateLimitToRedis(
clientId: string,
@@ -81,8 +118,12 @@ export class RateLimitService {
try {
const currentTime = Math.floor(Date.now() / 1000);
const windowStart = currentTime - RATE_LIMIT_WINDOW;
const globalKey = this.getRateLimitKey(clientId);
// Clean up old timestamp fields before writing
await this.cleanupOldTimestamps(globalKey, windowStart);
// Get current value and add pending count
const currentValue = await redisManager.hget(
globalKey,
@@ -93,7 +134,7 @@ export class RateLimitService {
).toString();
await redisManager.hset(globalKey, currentTime.toString(), newValue);
// Set TTL using the client directly
// Set TTL using the client directly - this prevents the key from persisting forever
if (redisManager.getClient()) {
await redisManager
.getClient()
@@ -119,8 +160,12 @@ export class RateLimitService {
try {
const currentTime = Math.floor(Date.now() / 1000);
const windowStart = currentTime - RATE_LIMIT_WINDOW;
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
// Clean up old timestamp fields before writing
await this.cleanupOldTimestamps(messageTypeKey, windowStart);
// Get current value and add pending count
const currentValue = await redisManager.hget(
messageTypeKey,
@@ -135,7 +180,7 @@ export class RateLimitService {
newValue
);
// Set TTL using the client directly
// Set TTL using the client directly - this prevents the key from persisting forever
if (redisManager.getClient()) {
await redisManager
.getClient()
@@ -170,6 +215,10 @@ export class RateLimitService {
try {
const globalKey = this.getRateLimitKey(clientId);
// Clean up old timestamp fields before reading
await this.cleanupOldTimestamps(globalKey, windowStart);
const globalRateLimitData = await redisManager.hgetall(globalKey);
let count = 0;
@@ -215,6 +264,10 @@ export class RateLimitService {
try {
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
// Clean up old timestamp fields before reading
await this.cleanupOldTimestamps(messageTypeKey, windowStart);
const messageTypeRateLimitData = await redisManager.hgetall(messageTypeKey);
let count = 0;

View File

@@ -16,9 +16,10 @@ import privateConfig from "#private/lib/config";
import logger from "@server/logger";
export enum AudienceIds {
General = "5cfbf99b-c592-40a9-9b8a-577a4681c158",
Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20",
Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549"
SignUps = "5cfbf99b-c592-40a9-9b8a-577a4681c158",
Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20",
Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549",
Newsletter = "5500c431-191c-42f0-a5d4-8b6d445b4ea0"
}
const resend = new Resend(

View File

@@ -15,6 +15,7 @@ import {
certificates,
db,
domainNamespaces,
domains,
exitNodes,
loginPage,
targetHealthCheck
@@ -50,7 +51,8 @@ export async function getTraefikConfig(
exitNodeId: number,
siteTypes: string[],
filterOutNamespaceDomains = false,
generateLoginPageRouters = false
generateLoginPageRouters = false,
allowRawResources = true
): Promise<any> {
// Define extended target type with site information
type TargetWithSite = Target & {
@@ -83,6 +85,8 @@ export async function getTraefikConfig(
setHostHeader: resources.setHostHeader,
enableProxy: resources.enableProxy,
headers: resources.headers,
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
// Target fields
targetId: targets.targetId,
targetEnabled: targets.enabled,
@@ -104,11 +108,17 @@ export async function getTraefikConfig(
subnet: sites.subnet,
exitNodeId: sites.exitNodeId,
// Namespace
domainNamespaceId: domainNamespaces.domainNamespaceId
domainNamespaceId: domainNamespaces.domainNamespaceId,
// Certificate
certificateStatus: certificates.status,
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
.innerJoin(resources, eq(resources.resourceId, targets.resourceId))
.leftJoin(certificates, eq(certificates.domainId, resources.domainId))
.leftJoin(domains, eq(domains.domainId, resources.domainId))
.leftJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
@@ -126,7 +136,8 @@ export async function getTraefikConfig(
and(
isNull(sites.exitNodeId),
sql`(${siteTypes.includes("local") ? 1 : 0} = 1)`, // only allow local sites if "local" is in siteTypes
eq(sites.type, "local")
eq(sites.type, "local"),
sql`(${build != "saas" ? 1 : 0} = 1)` // Dont allow undefined local sites in cloud
)
),
or(
@@ -134,7 +145,7 @@ export async function getTraefikConfig(
isNull(targetHealthCheck.hcHealth) // Include targets with no health check record
),
inArray(sites.type, siteTypes),
config.getRawConfig().traefik.allow_raw_resources
allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
: eq(resources.http, true)
)
@@ -201,11 +212,15 @@ export async function getTraefikConfig(
enableProxy: row.enableProxy,
targets: [],
headers: row.headers,
proxyProtocol: row.proxyProtocol,
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
path: row.path, // the targets will all have the same path
pathMatchType: row.pathMatchType, // the targets will all have the same pathMatchType
rewritePath: row.rewritePath,
rewritePathType: row.rewritePathType,
priority: priority // may be null, we fallback later
priority: priority, // may be null, we fallback later
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}
@@ -293,6 +308,20 @@ export async function getTraefikConfig(
config_output.http.services = {};
}
const domainParts = fullDomain.split(".");
let wildCard;
if (domainParts.length <= 2) {
wildCard = `*.${domainParts.join(".")}`;
} else {
wildCard = `*.${domainParts.slice(1).join(".")}`;
}
if (!resource.subdomain) {
wildCard = resource.fullDomain;
}
const configDomain = config.getDomain(resource.domainId);
let tls = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
const domainParts = fullDomain.split(".");
@@ -307,21 +336,35 @@ export async function getTraefikConfig(
wildCard = resource.fullDomain;
}
const configDomain = config.getDomain(resource.domainId);
const globalDefaultResolver =
config.getRawConfig().traefik.cert_resolver;
const globalDefaultPreferWildcard =
config.getRawConfig().traefik.prefer_wildcard_cert;
let certResolver: string, preferWildcardCert: boolean;
if (!configDomain) {
certResolver = config.getRawConfig().traefik.cert_resolver;
preferWildcardCert =
config.getRawConfig().traefik.prefer_wildcard_cert;
const domainCertResolver = resource.domainCertResolver;
const preferWildcardCert = resource.preferWildcardCert;
let resolverName: string | undefined;
let preferWildcard: boolean | undefined;
// Handle both letsencrypt & custom cases
if (domainCertResolver) {
resolverName = domainCertResolver.trim();
} else {
certResolver = configDomain.cert_resolver;
preferWildcardCert = configDomain.prefer_wildcard_cert;
resolverName = globalDefaultResolver;
}
if (
preferWildcardCert !== undefined &&
preferWildcardCert !== null
) {
preferWildcard = preferWildcardCert;
} else {
preferWildcard = globalDefaultPreferWildcard;
}
tls = {
certResolver: certResolver,
...(preferWildcardCert
certResolver: resolverName,
...(preferWildcard
? {
domains: [
{
@@ -337,7 +380,7 @@ export async function getTraefikConfig(
(cert) => cert.queriedDomain === resource.fullDomain
);
if (!matchingCert) {
logger.warn(
logger.debug(
`No matching certificate found for domain: ${resource.fullDomain}`
);
continue;
@@ -634,6 +677,8 @@ export async function getTraefikConfig(
...(protocol === "tcp" ? { rule: "HostSNI(`*`)" } : {})
};
const ppPrefix = config.getRawConfig().traefik.pp_transport_prefix;
config_output[protocol].services[serviceName] = {
loadBalancer: {
servers: (() => {
@@ -687,6 +732,11 @@ export async function getTraefikConfig(
}
});
})(),
...(resource.proxyProtocol && protocol == "tcp" // proxy protocol only works for tcp
? {
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
}
: {}),
...(resource.stickySession
? {
sticky: {
@@ -762,7 +812,7 @@ export async function getTraefikConfig(
continue;
}
let tls = {};
const tls = {};
if (
!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
) {

View File

@@ -15,4 +15,5 @@ export * from "./verifyCertificateAccess";
export * from "./verifyRemoteExitNodeAccess";
export * from "./verifyIdpAccess";
export * from "./verifyLoginPageAccess";
export * from "../../lib/corsWithLoginPage";
export * from "./logActionAudit";
export * from "./verifySubscription";

View File

@@ -0,0 +1,145 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { ActionsEnum } from "@server/auth/actions";
import { actionAuditLog, db, orgs } from "@server/db";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import { Request, Response, NextFunction } from "express";
import createHttpError from "http-errors";
import { and, eq, lt } from "drizzle-orm";
import cache from "@server/lib/cache";
async function getActionDays(orgId: string): Promise<number> {
// check cache first
const cached = cache.get<number>(`org_${orgId}_actionDays`);
if (cached !== undefined) {
return cached;
}
const [org] = await db
.select({
settingsLogRetentionDaysAction: orgs.settingsLogRetentionDaysAction
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
return 0;
}
// store the result in cache
cache.set(`org_${orgId}_actionDays`, org.settingsLogRetentionDaysAction, 300);
return org.settingsLogRetentionDaysAction;
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
const now = Math.floor(Date.now() / 1000);
const cutoffTimestamp = now - retentionDays * 24 * 60 * 60;
try {
await db
.delete(actionAuditLog)
.where(
and(
lt(actionAuditLog.timestamp, cutoffTimestamp),
eq(actionAuditLog.orgId, orgId)
)
);
logger.debug(
`Cleaned up action audit logs older than ${retentionDays} days`
);
} catch (error) {
logger.error("Error cleaning up old action audit logs:", error);
}
}
export function logActionAudit(action: ActionsEnum) {
return async function (
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
let orgId;
let actorType;
let actor;
let actorId;
const user = req.user;
if (user) {
const userOrg = req.userOrg;
orgId = userOrg?.orgId;
actorType = "user";
actor = user.username;
actorId = user.userId;
}
const apiKey = req.apiKey;
if (apiKey) {
const apiKeyOrg = req.apiKeyOrg;
orgId = apiKeyOrg?.orgId;
actorType = "apiKey";
actor = apiKey.name;
actorId = apiKey.apiKeyId;
}
if (!orgId) {
logger.warn("logActionAudit: No organization context found");
return next();
}
if (!actorType || !actor || !actorId) {
logger.warn("logActionAudit: Incomplete actor information");
return next();
}
const retentionDays = await getActionDays(orgId);
if (retentionDays === 0) {
// do not log
return next();
}
const timestamp = Math.floor(Date.now() / 1000);
let metadata = null;
if (req.params) {
metadata = JSON.stringify(req.params);
}
await db.insert(actionAuditLog).values({
timestamp,
orgId,
actorType,
actor,
actorId,
action,
metadata
});
return next();
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying logging action"
)
);
}
};
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { Request, Response, NextFunction } from "express";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing";
export async function verifyValidSubscription(
req: Request,
res: Response,
next: NextFunction
) {
try {
if (build != "saas") {
return next();
}
const tier = await getOrgTierData(req.params.orgId);
if (!tier.active) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Organization does not have an active subscription"
)
);
}
return next();
} catch (e) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying subscription"
)
);
}
}

View File

@@ -0,0 +1,81 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { queryAccessAuditLogsParams, queryAccessAuditLogsQuery, queryAccess } from "./queryAccessAuditLog";
import { generateCSV } from "@server/routers/auditLogs/generateCSV";
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/access/export",
description: "Export the access audit log for an organization as CSV",
tags: [OpenAPITags.Org],
request: {
query: queryAccessAuditLogsQuery,
params: queryAccessAuditLogsParams
},
responses: {}
});
export async function exportAccessAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryAccessAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryAccess(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const csvData = generateCSV(log);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="access-audit-logs-${data.orgId}-${Date.now()}.csv"`);
return res.send(csvData);
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,81 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { queryActionAuditLogsParams, queryActionAuditLogsQuery, queryAction } from "./queryActionAuditLog";
import { generateCSV } from "@server/routers/auditLogs/generateCSV";
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/action/export",
description: "Export the action audit log for an organization as CSV",
tags: [OpenAPITags.Org],
request: {
query: queryActionAuditLogsQuery,
params: queryActionAuditLogsParams
},
responses: {}
});
export async function exportActionAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryActionAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryActionAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryAction(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const csvData = generateCSV(log);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="action-audit-logs-${data.orgId}-${Date.now()}.csv"`);
return res.send(csvData);
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,17 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 "./queryActionAuditLog";
export * from "./exportActionAuditLog";
export * from "./queryAccessAuditLog";
export * from "./exportAccessAuditLog";

View File

@@ -0,0 +1,258 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { accessAuditLog, db, resources } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { eq, gt, lt, and, count, desc } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi";
import { z } from "zod";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { QueryAccessAuditLogResponse } from "@server/routers/auditLogs/types";
import response from "@server/lib/response";
import logger from "@server/logger";
export const queryAccessAuditLogsQuery = z.object({
// iso string just validate its a parseable date
timeStart: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeStart must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
timeEnd: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeEnd must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.default(new Date().toISOString()),
action: z
.union([z.boolean(), z.string()])
.transform((val) => (typeof val === "string" ? val === "true" : val))
.optional(),
actorType: z.string().optional(),
actorId: z.string().optional(),
resourceId: z
.string()
.optional()
.transform(Number)
.pipe(z.number().int().positive())
.optional(),
actor: z.string().optional(),
type: z.string().optional(),
location: z.string().optional(),
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().positive()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative())
});
export const queryAccessAuditLogsParams = z.object({
orgId: z.string()
});
export const queryAccessAuditLogsCombined = queryAccessAuditLogsQuery.merge(
queryAccessAuditLogsParams
);
type Q = z.infer<typeof queryAccessAuditLogsCombined>;
function getWhere(data: Q) {
return and(
gt(accessAuditLog.timestamp, data.timeStart),
lt(accessAuditLog.timestamp, data.timeEnd),
eq(accessAuditLog.orgId, data.orgId),
data.resourceId
? eq(accessAuditLog.resourceId, data.resourceId)
: undefined,
data.actor ? eq(accessAuditLog.actor, data.actor) : undefined,
data.actorType
? eq(accessAuditLog.actorType, data.actorType)
: undefined,
data.actorId ? eq(accessAuditLog.actorId, data.actorId) : undefined,
data.location ? eq(accessAuditLog.location, data.location) : undefined,
data.type ? eq(accessAuditLog.type, data.type) : undefined,
data.action !== undefined
? eq(accessAuditLog.action, data.action)
: undefined
);
}
export function queryAccess(data: Q) {
return db
.select({
orgId: accessAuditLog.orgId,
action: accessAuditLog.action,
actorType: accessAuditLog.actorType,
actorId: accessAuditLog.actorId,
resourceId: accessAuditLog.resourceId,
resourceName: resources.name,
resourceNiceId: resources.niceId,
ip: accessAuditLog.ip,
location: accessAuditLog.location,
userAgent: accessAuditLog.userAgent,
metadata: accessAuditLog.metadata,
type: accessAuditLog.type,
timestamp: accessAuditLog.timestamp,
actor: accessAuditLog.actor
})
.from(accessAuditLog)
.leftJoin(
resources,
eq(accessAuditLog.resourceId, resources.resourceId)
)
.where(getWhere(data))
.orderBy(desc(accessAuditLog.timestamp), desc(accessAuditLog.id));
}
export function countAccessQuery(data: Q) {
const countQuery = db
.select({ count: count() })
.from(accessAuditLog)
.where(getWhere(data));
return countQuery;
}
async function queryUniqueFilterAttributes(
timeStart: number,
timeEnd: number,
orgId: string
) {
const baseConditions = and(
gt(accessAuditLog.timestamp, timeStart),
lt(accessAuditLog.timestamp, timeEnd),
eq(accessAuditLog.orgId, orgId)
);
// Get unique actors
const uniqueActors = await db
.selectDistinct({
actor: accessAuditLog.actor
})
.from(accessAuditLog)
.where(baseConditions);
// Get unique locations
const uniqueLocations = await db
.selectDistinct({
locations: accessAuditLog.location
})
.from(accessAuditLog)
.where(baseConditions);
// Get unique resources with names
const uniqueResources = await db
.selectDistinct({
id: accessAuditLog.resourceId,
name: resources.name
})
.from(accessAuditLog)
.leftJoin(
resources,
eq(accessAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions);
return {
actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null),
resources: uniqueResources.filter((row): row is { id: number; name: string | null } => row.id !== null),
locations: uniqueLocations.map(row => row.locations).filter((location): location is string => location !== null)
};
}
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/access",
description: "Query the access audit log for an organization",
tags: [OpenAPITags.Org],
request: {
query: queryAccessAuditLogsQuery,
params: queryAccessAuditLogsParams
},
responses: {}
});
export async function queryAccessAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryAccessAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryAccess(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const totalCountResult = await countAccessQuery(data);
const totalCount = totalCountResult[0].count;
const filterAttributes = await queryUniqueFilterAttributes(
data.timeStart,
data.timeEnd,
data.orgId
);
return response<QueryAccessAuditLogResponse>(res, {
data: {
log: log,
pagination: {
total: totalCount,
limit: data.limit,
offset: data.offset
},
filterAttributes
},
success: true,
error: false,
message: "Access audit logs retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,211 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { actionAuditLog, db } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { eq, gt, lt, and, count, desc } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi";
import { z } from "zod";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { QueryActionAuditLogResponse } from "@server/routers/auditLogs/types";
import response from "@server/lib/response";
import logger from "@server/logger";
export const queryActionAuditLogsQuery = z.object({
// iso string just validate its a parseable date
timeStart: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeStart must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
timeEnd: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeEnd must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.default(new Date().toISOString()),
action: z.string().optional(),
actorType: z.string().optional(),
actorId: z.string().optional(),
actor: z.string().optional(),
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().positive()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative())
});
export const queryActionAuditLogsParams = z.object({
orgId: z.string()
});
export const queryActionAuditLogsCombined =
queryActionAuditLogsQuery.merge(queryActionAuditLogsParams);
type Q = z.infer<typeof queryActionAuditLogsCombined>;
function getWhere(data: Q) {
return and(
gt(actionAuditLog.timestamp, data.timeStart),
lt(actionAuditLog.timestamp, data.timeEnd),
eq(actionAuditLog.orgId, data.orgId),
data.actor ? eq(actionAuditLog.actor, data.actor) : undefined,
data.actorType ? eq(actionAuditLog.actorType, data.actorType) : undefined,
data.actorId ? eq(actionAuditLog.actorId, data.actorId) : undefined,
data.action ? eq(actionAuditLog.action, data.action) : undefined
);
}
export function queryAction(data: Q) {
return db
.select({
orgId: actionAuditLog.orgId,
action: actionAuditLog.action,
actorType: actionAuditLog.actorType,
metadata: actionAuditLog.metadata,
actorId: actionAuditLog.actorId,
timestamp: actionAuditLog.timestamp,
actor: actionAuditLog.actor
})
.from(actionAuditLog)
.where(getWhere(data))
.orderBy(desc(actionAuditLog.timestamp), desc(actionAuditLog.id));
}
export function countActionQuery(data: Q) {
const countQuery = db
.select({ count: count() })
.from(actionAuditLog)
.where(getWhere(data));
return countQuery;
}
async function queryUniqueFilterAttributes(
timeStart: number,
timeEnd: number,
orgId: string
) {
const baseConditions = and(
gt(actionAuditLog.timestamp, timeStart),
lt(actionAuditLog.timestamp, timeEnd),
eq(actionAuditLog.orgId, orgId)
);
// Get unique actors
const uniqueActors = await db
.selectDistinct({
actor: actionAuditLog.actor
})
.from(actionAuditLog)
.where(baseConditions);
const uniqueActions = await db
.selectDistinct({
action: actionAuditLog.action
})
.from(actionAuditLog)
.where(baseConditions);
return {
actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null),
actions: uniqueActions.map(row => row.action).filter((action): action is string => action !== null),
};
}
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/action",
description: "Query the action audit log for an organization",
tags: [OpenAPITags.Org],
request: {
query: queryActionAuditLogsQuery,
params: queryActionAuditLogsParams
},
responses: {}
});
export async function queryActionAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryActionAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryActionAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryAction(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const totalCountResult = await countActionQuery(data);
const totalCount = totalCountResult[0].count;
const filterAttributes = await queryUniqueFilterAttributes(
data.timeStart,
data.timeEnd,
data.orgId
);
return response<QueryActionAuditLogResponse>(res, {
data: {
log: log,
pagination: {
total: totalCount,
limit: data.limit,
offset: data.offset
},
filterAttributes
},
success: true,
error: false,
message: "Action audit logs retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -21,20 +21,22 @@ import * as domain from "#private/routers/domain";
import * as auth from "#private/routers/auth";
import * as license from "#private/routers/license";
import * as generateLicense from "./generatedLicense";
import * as logs from "#private/routers/auditLogs";
import * as misc from "#private/routers/misc";
import { Router } from "express";
import {
verifyOrgAccess,
verifyUserHasAction,
verifyUserIsOrgOwner,
verifyUserIsServerAdmin
} from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";
import {
logActionAudit,
verifyCertificateAccess,
verifyIdpAccess,
verifyLoginPageAccess,
verifyRemoteExitNodeAccess
verifyRemoteExitNodeAccess,
verifyValidSubscription
} from "#private/middlewares";
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors";
@@ -72,6 +74,7 @@ authenticated.put(
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createIdp),
logActionAudit(ActionsEnum.createIdp),
orgIdp.createOrgOidcIdp
);
@@ -81,6 +84,7 @@ authenticated.post(
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.updateIdp),
logActionAudit(ActionsEnum.updateIdp),
orgIdp.updateOrgOidcIdp
);
@@ -90,6 +94,7 @@ authenticated.delete(
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.deleteIdp),
logActionAudit(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
);
@@ -127,6 +132,7 @@ authenticated.post(
verifyOrgAccess,
verifyCertificateAccess,
verifyUserHasAction(ActionsEnum.restartCertificate),
logActionAudit(ActionsEnum.restartCertificate),
certificates.restartCertificate
);
@@ -152,6 +158,7 @@ if (build === "saas") {
"/org/:orgId/billing/create-checkout-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createCheckoutSession
);
@@ -159,6 +166,7 @@ if (build === "saas") {
"/org/:orgId/billing/create-portal-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.createPortalSession
);
@@ -187,6 +195,24 @@ if (build === "saas") {
verifyOrgAccess,
generateLicense.generateNewLicense
);
authenticated.post(
"/send-support-request",
rateLimit({
windowMs: 15 * 60 * 1000,
max: 3,
keyGenerator: (req) =>
`sendSupportRequest:${req.user?.userId || ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only send 3 support requests every 15 minutes. Please try again later.`;
return next(
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
);
},
store: createStore()
}),
misc.sendSupportEmail
);
}
authenticated.get(
@@ -206,6 +232,7 @@ authenticated.put(
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRemoteExitNode),
logActionAudit(ActionsEnum.createRemoteExitNode),
remoteExitNode.createRemoteExitNode
);
@@ -240,6 +267,7 @@ authenticated.delete(
verifyOrgAccess,
verifyRemoteExitNodeAccess,
verifyUserHasAction(ActionsEnum.deleteRemoteExitNode),
logActionAudit(ActionsEnum.deleteRemoteExitNode),
remoteExitNode.deleteRemoteExitNode
);
@@ -248,6 +276,7 @@ authenticated.put(
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createLoginPage),
logActionAudit(ActionsEnum.createLoginPage),
loginPage.createLoginPage
);
@@ -257,6 +286,7 @@ authenticated.post(
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.updateLoginPage),
logActionAudit(ActionsEnum.updateLoginPage),
loginPage.updateLoginPage
);
@@ -266,6 +296,7 @@ authenticated.delete(
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.deleteLoginPage),
logActionAudit(ActionsEnum.deleteLoginPage),
loginPage.deleteLoginPage
);
@@ -334,3 +365,41 @@ authenticated.post(
verifyUserIsServerAdmin,
license.recheckStatus
);
authenticated.get(
"/org/:orgId/logs/action",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryActionAuditLogs
);
authenticated.get(
"/org/:orgId/logs/action/export",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportActionAuditLogs
);
authenticated.get(
"/org/:orgId/logs/access",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryAccessAuditLogs
);
authenticated.get(
"/org/:orgId/logs/access/export",
verifyValidLicense,
verifyValidSubscription,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportAccessAuditLogs
);

View File

@@ -16,7 +16,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { response as sendResponse } from "@server/lib/response";
import privateConfig from "@server/private/lib/config";
import privateConfig from "#private/lib/config";
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
async function createNewLicense(orgId: string, licenseData: any): Promise<any> {

View File

@@ -16,7 +16,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { response as sendResponse } from "@server/lib/response";
import privateConfig from "@server/private/lib/config";
import privateConfig from "#private/lib/config";
import { GeneratedLicenseKey, ListGeneratedLicenseKeysResponse } from "@server/routers/generatedLicense/types";
async function fetchLicenseKeys(orgId: string): Promise<any> {

View File

@@ -35,7 +35,9 @@ import {
loginPageOrg,
LoginPage,
resourceHeaderAuth,
ResourceHeaderAuth
ResourceHeaderAuth,
orgs,
requestAuditLog
} from "@server/db";
import {
resources,
@@ -73,6 +75,7 @@ import { validateResourceSessionToken } from "@server/auth/sessions/resource";
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
import { maxmindLookup } from "@server/db/maxmind";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
import semver from "semver";
// Zod schemas for request validation
const getResourceByDomainParamsSchema = z
@@ -224,6 +227,8 @@ export type UserSessionWithUser = {
export const hybridRouter = Router();
hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
// TODO: ADD RATE LIMITING TO THESE ROUTES AS NEEDED BASED ON USAGE PATTERNS
hybridRouter.get(
"/general-config",
async (req: Request, res: Response, next: NextFunction) => {
@@ -270,7 +275,8 @@ hybridRouter.get(
remoteExitNode.exitNodeId,
["newt", "local", "wireguard"], // Allow them to use all the site types
true, // But don't allow domain namespace resources
false // Dont include login pages
false, // Dont include login pages,
true // allow raw resources
);
return response(res, {
@@ -300,7 +306,8 @@ function loadEncryptData() {
return; // already loaded
}
encryptionKeyPath = privateConfig.getRawPrivateConfig().server.encryption_key_path;
encryptionKeyPath =
privateConfig.getRawPrivateConfig().server.encryption_key_path;
if (!fs.existsSync(encryptionKeyPath)) {
throw new Error(
@@ -1066,11 +1073,29 @@ hybridRouter.get(
);
}
const rules = await db
let rules = await db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
// backward compatibility: COUNTRY -> GEOIP
// TODO: remove this after a few versions once all exit nodes are updated
if (
(remoteExitNode.secondaryVersion &&
semver.lt(remoteExitNode.secondaryVersion, "1.1.0")) ||
!remoteExitNode.secondaryVersion
) {
for (const rule of rules) {
if (rule.match == "COUNTRY") {
rule.match = "GEOIP";
}
}
}
logger.debug(
`Retrieved ${rules.length} rules for resource ID ${resourceId}: ${JSON.stringify(rules)}`
);
return response<(typeof resourceRules.$inferSelect)[]>(res, {
data: rules,
success: true,
@@ -1582,3 +1607,188 @@ hybridRouter.post(
}
}
);
hybridRouter.get(
"/org/:orgId/get-retention-days",
async (req: Request, res: Response, next: NextFunction) => {
try {
const parsedParams = getOrgLoginPageParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const remoteExitNode = req.remoteExitNode;
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Remote exit node not found"
)
);
}
if (await checkExitNodeOrg(remoteExitNode.exitNodeId, orgId)) {
// If the exit node is not allowed for the org, return an error
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Exit node not allowed for this organization"
)
);
}
const [org] = await db
.select({
settingsLogRetentionDaysRequest:
orgs.settingsLogRetentionDaysRequest
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
return response(res, {
data: {
settingsLogRetentionDaysRequest:
org.settingsLogRetentionDaysRequest
},
success: true,
error: false,
message: "Log retention days retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
}
}
);
const batchLogsSchema = z.object({
logs: z.array(
z.object({
timestamp: z.number(),
orgId: z.string().optional(),
actorType: z.string().optional(),
actor: z.string().optional(),
actorId: z.string().optional(),
metadata: z.string().nullable(),
action: z.boolean(),
resourceId: z.number().optional(),
reason: z.number(),
location: z.string().optional(),
originalRequestURL: z.string(),
scheme: z.string(),
host: z.string(),
path: z.string(),
method: z.string(),
ip: z.string().optional(),
tls: z.boolean()
})
)
});
hybridRouter.post(
"/logs/batch",
async (req: Request, res: Response, next: NextFunction) => {
try {
const parsedBody = batchLogsSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { logs } = parsedBody.data;
const remoteExitNode = req.remoteExitNode;
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Remote exit node not found"
)
);
}
const exitNodeOrgsRes = await db
.select()
.from(exitNodeOrgs)
.where(
and(eq(exitNodeOrgs.exitNodeId, remoteExitNode.exitNodeId))
)
.limit(1);
// Batch insert all logs in a single query
const logEntries = logs
.filter((logEntry) => {
if (!logEntry.orgId) {
return false;
}
const isOrgAllowed = exitNodeOrgsRes.some(
(eno) => eno.orgId === logEntry.orgId
);
return isOrgAllowed;
})
.map((logEntry) => ({
timestamp: logEntry.timestamp,
orgId: logEntry.orgId,
actorType: logEntry.actorType,
actor: logEntry.actor,
actorId: logEntry.actorId,
metadata: logEntry.metadata,
action: logEntry.action,
resourceId: logEntry.resourceId,
reason: logEntry.reason,
location: logEntry.location,
// userAgent: data.userAgent, // TODO: add this
// headers: data.body.headers,
// query: data.body.query,
originalRequestURL: logEntry.originalRequestURL,
scheme: logEntry.scheme,
host: logEntry.host,
path: logEntry.path,
method: logEntry.method,
ip: logEntry.ip,
tls: logEntry.tls
}));
await db.insert(requestAuditLog).values(logEntries);
return response(res, {
data: null,
success: true,
error: false,
message: "Logs saved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
}
}
);

View File

@@ -23,6 +23,7 @@ import {
import { ActionsEnum } from "@server/auth/actions";
import { unauthenticated as ua, authenticated as a } from "@server/routers/integration";
import { logActionAudit } from "#private/middlewares";
export const unauthenticated = ua;
export const authenticated = a;
@@ -31,12 +32,14 @@ authenticated.post(
`/org/:orgId/send-usage-notification`,
verifyApiKeyIsRoot, // We are the only ones who can use root key so its fine
verifyApiKeyHasAction(ActionsEnum.sendUsageNotification),
org.sendUsageNotification
logActionAudit(ActionsEnum.sendUsageNotification),
org.sendUsageNotification,
);
authenticated.delete(
"/idp/:idpId",
verifyApiKeyIsRoot,
verifyApiKeyHasAction(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
logActionAudit(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp,
);

View File

@@ -25,7 +25,7 @@ import { LoadLoginPageResponse } from "@server/routers/loginPage/types";
const querySchema = z.object({
resourceId: z.coerce.number().int().positive().optional(),
idpId: z.coerce.number().int().positive().optional(),
orgId: z.coerce.number().int().positive().optional(),
orgId: z.string().min(1).optional(),
fullDomain: z.string().min(1)
});
@@ -89,7 +89,7 @@ export async function loadLoginPage(
const { resourceId, idpId, fullDomain } = parsedQuery.data;
let orgId;
let orgId: string | undefined = undefined;
if (resourceId) {
const [resource] = await db
.select()
@@ -118,7 +118,7 @@ export async function loadLoginPage(
orgId = idpOrgLink.orgId;
} else if (parsedQuery.data.orgId) {
orgId = parsedQuery.data.orgId.toString();
orgId = parsedQuery.data.orgId;
}
const loginPage = await query(orgId, fullDomain);

View File

@@ -0,0 +1,14 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 "./sendSupportEmail";

View File

@@ -0,0 +1,94 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 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 { Request, Response, NextFunction } from "express";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { response as sendResponse } from "@server/lib/response";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { sendEmail } from "@server/emails";
import SupportEmail from "@server/emails/templates/SupportEmail";
import config from "@server/lib/config";
const bodySchema = z
.object({
body: z.string().min(1),
subject: z.string().min(1).max(255)
})
.strict();
export async function sendSupportEmail(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedBody = bodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { body, subject } = parsedBody.data;
const user = req.user!;
if (!user?.email) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"User does not have an email associated with their account"
)
);
}
try {
await sendEmail(
SupportEmail({
username: user.username,
email: user.email,
subject,
body
}),
{
name: req.user?.email || "Support User",
to: "support@pangolin.net",
from: config.getNoReplyEmail(),
subject: `Support Request: ${subject}`
}
);
return sendResponse(res, {
data: {},
success: true,
error: false,
message: "Sent support email successfully",
status: HttpCode.OK
});
} catch (e) {
logger.error(e);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `${e}`)
);
}
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -29,7 +29,7 @@ export const handleRemoteExitNodeRegisterMessage: MessageHandler = async (
return;
}
const { remoteExitNodeVersion } = message.data;
const { remoteExitNodeVersion, remoteExitNodeSecondaryVersion } = message.data;
if (!remoteExitNodeVersion) {
logger.warn("Remote exit node version not found");
@@ -39,7 +39,7 @@ export const handleRemoteExitNodeRegisterMessage: MessageHandler = async (
// update the version
await db
.update(remoteExitNodes)
.set({ version: remoteExitNodeVersion })
.set({ version: remoteExitNodeVersion, secondaryVersion: remoteExitNodeSecondaryVersion })
.where(
eq(
remoteExitNodes.remoteExitNodeId,

View File

@@ -63,6 +63,7 @@ function queryAccessTokens(
description: resourceAccessToken.description,
createdAt: resourceAccessToken.createdAt,
resourceName: resources.name,
resourceNiceId: resources.niceId,
siteName: sites.name
};

View File

@@ -0,0 +1,68 @@
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
import { generateCSV } from "./generateCSV";
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/request",
description: "Query the request audit log for an organization",
tags: [OpenAPITags.Org],
request: {
query: queryAccessAuditLogsQuery,
params: queryRequestAuditLogsParams
},
responses: {}
});
export async function exportRequestAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryRequestAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryRequest(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const csvData = generateCSV(log);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="request-audit-logs-${data.orgId}-${Date.now()}.csv"`);
return res.send(csvData);
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,16 @@
export function generateCSV(data: any[]): string {
if (data.length === 0) {
return "orgId,action,actorType,timestamp,actor\n";
}
const headers = Object.keys(data[0]).join(",");
const rows = data.map(row =>
Object.values(row).map(value =>
typeof value === 'string' && value.includes(',')
? `"${value.replace(/"/g, '""')}"`
: value
).join(",")
);
return [headers, ...rows].join("\n");
}

View File

@@ -0,0 +1,2 @@
export * from "./queryRequstAuditLog";
export * from "./exportRequstAuditLog";

View File

@@ -0,0 +1,277 @@
import { db, requestAuditLog, resources } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { eq, gt, lt, and, count, desc } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi";
import { z } from "zod";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
import response from "@server/lib/response";
import logger from "@server/logger";
export const queryAccessAuditLogsQuery = z.object({
// iso string just validate its a parseable date
timeStart: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeStart must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
timeEnd: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
message: "timeEnd must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.default(new Date().toISOString()),
action: z
.union([z.boolean(), z.string()])
.transform((val) => (typeof val === "string" ? val === "true" : val))
.optional(),
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(),
reason: z
.string()
.optional()
.transform(Number)
.pipe(z.number().int().positive())
.optional(),
resourceId: z
.string()
.optional()
.transform(Number)
.pipe(z.number().int().positive())
.optional(),
actor: z.string().optional(),
location: z.string().optional(),
host: z.string().optional(),
path: z.string().optional(),
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().positive()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative())
});
export const queryRequestAuditLogsParams = z.object({
orgId: z.string()
});
export const queryRequestAuditLogsCombined =
queryAccessAuditLogsQuery.merge(queryRequestAuditLogsParams);
type Q = z.infer<typeof queryRequestAuditLogsCombined>;
function getWhere(data: Q) {
return and(
gt(requestAuditLog.timestamp, data.timeStart),
lt(requestAuditLog.timestamp, data.timeEnd),
eq(requestAuditLog.orgId, data.orgId),
data.resourceId
? eq(requestAuditLog.resourceId, data.resourceId)
: undefined,
data.actor ? eq(requestAuditLog.actor, data.actor) : undefined,
data.method ? eq(requestAuditLog.method, data.method) : undefined,
data.reason ? eq(requestAuditLog.reason, data.reason) : undefined,
data.host ? eq(requestAuditLog.host, data.host) : undefined,
data.location ? eq(requestAuditLog.location, data.location) : undefined,
data.path ? eq(requestAuditLog.path, data.path) : undefined,
data.action !== undefined
? eq(requestAuditLog.action, data.action)
: undefined
);
}
export function queryRequest(data: Q) {
return db
.select({
id: requestAuditLog.id,
timestamp: requestAuditLog.timestamp,
orgId: requestAuditLog.orgId,
action: requestAuditLog.action,
reason: requestAuditLog.reason,
actorType: requestAuditLog.actorType,
actor: requestAuditLog.actor,
actorId: requestAuditLog.actorId,
resourceId: requestAuditLog.resourceId,
ip: requestAuditLog.ip,
location: requestAuditLog.location,
userAgent: requestAuditLog.userAgent,
metadata: requestAuditLog.metadata,
headers: requestAuditLog.headers,
query: requestAuditLog.query,
originalRequestURL: requestAuditLog.originalRequestURL,
scheme: requestAuditLog.scheme,
host: requestAuditLog.host,
path: requestAuditLog.path,
method: requestAuditLog.method,
tls: requestAuditLog.tls,
resourceName: resources.name,
resourceNiceId: resources.niceId
})
.from(requestAuditLog)
.leftJoin(
resources,
eq(requestAuditLog.resourceId, resources.resourceId)
) // TODO: Is this efficient?
.where(getWhere(data))
.orderBy(desc(requestAuditLog.timestamp), desc(requestAuditLog.id));
}
export function countRequestQuery(data: Q) {
const countQuery = db
.select({ count: count() })
.from(requestAuditLog)
.where(getWhere(data));
return countQuery;
}
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/request",
description: "Query the request audit log for an organization",
tags: [OpenAPITags.Org],
request: {
query: queryAccessAuditLogsQuery,
params: queryRequestAuditLogsParams
},
responses: {}
});
async function queryUniqueFilterAttributes(
timeStart: number,
timeEnd: number,
orgId: string
) {
const baseConditions = and(
gt(requestAuditLog.timestamp, timeStart),
lt(requestAuditLog.timestamp, timeEnd),
eq(requestAuditLog.orgId, orgId)
);
// Get unique actors
const uniqueActors = await db
.selectDistinct({
actor: requestAuditLog.actor
})
.from(requestAuditLog)
.where(baseConditions);
// Get unique locations
const uniqueLocations = await db
.selectDistinct({
locations: requestAuditLog.location
})
.from(requestAuditLog)
.where(baseConditions);
// Get unique actors
const uniqueHosts = await db
.selectDistinct({
hosts: requestAuditLog.host
})
.from(requestAuditLog)
.where(baseConditions);
// Get unique actors
const uniquePaths = await db
.selectDistinct({
paths: requestAuditLog.path
})
.from(requestAuditLog)
.where(baseConditions);
// Get unique resources with names
const uniqueResources = await db
.selectDistinct({
id: requestAuditLog.resourceId,
name: resources.name
})
.from(requestAuditLog)
.leftJoin(
resources,
eq(requestAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions);
return {
actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null),
resources: uniqueResources.filter((row): row is { id: number; name: string | null } => row.id !== null),
locations: uniqueLocations.map(row => row.locations).filter((location): location is string => location !== null),
hosts: uniqueHosts.map(row => row.hosts).filter((host): host is string => host !== null),
paths: uniquePaths.map(row => row.paths).filter((path): path is string => path !== null)
};
}
export async function queryRequestAuditLogs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryRequestAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const data = { ...parsedQuery.data, ...parsedParams.data };
const baseQuery = queryRequest(data);
const log = await baseQuery.limit(data.limit).offset(data.offset);
const totalCountResult = await countRequestQuery(data);
const totalCount = totalCountResult[0].count;
const filterAttributes = await queryUniqueFilterAttributes(
data.timeStart,
data.timeEnd,
data.orgId
);
return response<QueryRequestAuditLogResponse>(res, {
data: {
log: log,
pagination: {
total: totalCount,
limit: data.limit,
offset: data.offset
},
filterAttributes
},
success: true,
error: false,
message: "Action audit logs retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,93 @@
export type QueryActionAuditLogResponse = {
log: {
orgId: string;
action: string;
actorType: string;
actorId: string;
metadata: string | null;
timestamp: number;
actor: string;
}[];
pagination: {
total: number;
limit: number;
offset: number;
};
filterAttributes: {
actors: string[];
};
};
export type QueryRequestAuditLogResponse = {
log: {
timestamp: number;
action: boolean;
reason: number;
orgId: string | null;
actorType: string | null;
actor: string | null;
actorId: string | null;
resourceId: number | null;
resourceNiceId: string | null;
resourceName: string | null;
ip: string | null;
location: string | null;
userAgent: string | null;
metadata: string | null;
headers: string | null;
query: string | null;
originalRequestURL: string | null;
scheme: string | null;
host: string | null;
path: string | null;
method: string | null;
tls: boolean | null;
}[];
pagination: {
total: number;
limit: number;
offset: number;
};
filterAttributes: {
actors: string[];
resources: {
id: number;
name: string | null;
}[];
locations: string[];
hosts: string[];
paths: string[];
};
};
export type QueryAccessAuditLogResponse = {
log: {
orgId: string;
action: boolean;
actorType: string | null;
actorId: string | null;
resourceId: number | null;
resourceName: string | null;
resourceNiceId: string | null;
ip: string | null;
location: string | null;
userAgent: string | null;
metadata: string | null;
type: string;
timestamp: number;
actor: string | null;
}[];
pagination: {
total: number;
limit: number;
offset: number;
};
filterAttributes: {
actors: string[];
resources: {
id: number;
name: string | null;
}[];
locations: string[];
};
};

View File

@@ -5,7 +5,6 @@ import { fromError } from "zod-validation-error";
import { z } from "zod";
import { db } from "@server/db";
import { User, users } from "@server/db";
import { eq } from "drizzle-orm";
import { response } from "@server/lib/response";
import {
hashPassword,
@@ -15,8 +14,13 @@ import { verifyTotpCode } from "@server/auth/totp";
import logger from "@server/logger";
import { unauthorized } from "@server/auth/unauthorizedResponse";
import { invalidateAllSessions } from "@server/auth/sessions/app";
import { sessions, resourceSessions } from "@server/db";
import { and, eq, ne, inArray } from "drizzle-orm";
import { passwordSchema } from "@server/auth/passwordSchema";
import { UserType } from "@server/types/UserTypes";
import { sendEmail } from "@server/emails";
import ConfirmPasswordReset from "@server/emails/templates/NotifyResetPassword";
import config from "@server/lib/config";
export const changePasswordBody = z
.object({
@@ -32,6 +36,46 @@ export type ChangePasswordResponse = {
codeRequested?: boolean;
};
async function invalidateAllSessionsExceptCurrent(
userId: string,
currentSessionId: string
): Promise<void> {
try {
await db.transaction(async (trx) => {
// Get all user sessions except the current one
const userSessions = await trx
.select()
.from(sessions)
.where(
and(
eq(sessions.userId, userId),
ne(sessions.sessionId, currentSessionId)
)
);
// Delete resource sessions for the sessions we're invalidating
if (userSessions.length > 0) {
await trx.delete(resourceSessions).where(
inArray(
resourceSessions.userSessionId,
userSessions.map((s) => s.sessionId)
)
);
}
// Delete the user sessions (except current)
await trx.delete(sessions).where(
and(
eq(sessions.userId, userId),
ne(sessions.sessionId, currentSessionId)
)
);
});
} catch (e) {
logger.error("Failed to invalidate user sessions except current", e);
}
}
export async function changePassword(
req: Request,
res: Response,
@@ -109,13 +153,24 @@ export async function changePassword(
await db
.update(users)
.set({
passwordHash: hash
passwordHash: hash,
lastPasswordChange: new Date().getTime()
})
.where(eq(users.userId, user.userId));
await invalidateAllSessions(user.userId);
// Invalidate all sessions except the current one
await invalidateAllSessionsExceptCurrent(user.userId, req.session.sessionId);
// TODO: send email to user confirming password change
try {
const email = user.email!;
await sendEmail(ConfirmPasswordReset({ email }), {
from: config.getNoReplyEmail(),
to: email,
subject: "Password Reset Confirmation"
});
} catch (e) {
logger.error("Failed to send password reset confirmation email", e);
}
return response(res, {
data: null,

View File

@@ -3,7 +3,7 @@ import {
generateSessionToken,
serializeSessionCookie
} from "@server/auth/sessions/app";
import { db } from "@server/db";
import { db, resources } from "@server/db";
import { users, securityKeys } from "@server/db";
import HttpCode from "@server/types/HttpCode";
import response from "@server/lib/response";
@@ -18,12 +18,14 @@ import logger from "@server/logger";
import { verifyPassword } from "@server/auth/password";
import { verifySession } from "@server/auth/sessions/verifySession";
import { UserType } from "@server/types/UserTypes";
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
export const loginBodySchema = z
.object({
email: z.string().toLowerCase().email(),
password: z.string(),
code: z.string().optional()
code: z.string().optional(),
resourceGuid: z.string().optional()
})
.strict();
@@ -52,7 +54,7 @@ export async function login(
);
}
const { email, password, code } = parsedBody.data;
const { email, password, code, resourceGuid } = parsedBody.data;
try {
const { session: existingSession } = await verifySession(req);
@@ -66,6 +68,28 @@ export async function login(
});
}
let resourceId: number | null = null;
let orgId: string | null = null;
if (resourceGuid) {
const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
if (!resource) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with GUID ${resourceGuid} not found`
)
);
}
resourceId = resource.resourceId;
orgId = resource.orgId;
}
const existingUserRes = await db
.select()
.from(users)
@@ -78,6 +102,18 @@ export async function login(
`Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
);
}
if (resourceId && orgId) {
logAccessAudit({
orgId: orgId,
resourceId: resourceId,
action: false,
type: "login",
userAgent: req.headers["user-agent"],
requestIp: req.ip
});
}
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
@@ -98,6 +134,18 @@ export async function login(
`Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
);
}
if (resourceId && orgId) {
logAccessAudit({
orgId: orgId,
resourceId: resourceId,
action: false,
type: "login",
userAgent: req.headers["user-agent"],
requestIp: req.ip
});
}
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
@@ -158,6 +206,18 @@ export async function login(
`Two-factor code incorrect. Email: ${email}. IP: ${req.ip}.`
);
}
if (resourceId && orgId) {
logAccessAudit({
orgId: orgId,
resourceId: resourceId,
action: false,
type: "login",
userAgent: req.headers["user-agent"],
requestIp: req.ip
});
}
return next(
createHttpError(
HttpCode.UNAUTHORIZED,

View File

@@ -15,13 +15,11 @@ import config from "@server/lib/config";
import { sendEmail } from "@server/emails";
import ResetPasswordCode from "@server/emails/templates/ResetPasswordCode";
import { hashPassword } from "@server/auth/password";
import { UserType } from "@server/types/UserTypes";
export const requestPasswordResetBody = z
.object({
email: z
.string()
.toLowerCase()
.email(),
email: z.string().toLowerCase().email()
})
.strict();
@@ -56,12 +54,35 @@ export async function requestPasswordReset(
.where(eq(users.email, email));
if (!existingUser || !existingUser.length) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"A user with that email does not exist"
)
await randomDelay(2000);
logger.debug(
`Password reset requested for ${email}, but no such user exists`
);
return response<RequestPasswordResetResponse>(res, {
data: {
sentEmail: true
},
success: true,
error: false,
message: "Password reset requested",
status: HttpCode.OK
});
}
if (existingUser[0].type !== UserType.Internal) {
await randomDelay(2000);
logger.debug(
`Password reset requested for ${email}, but user is of type ${existingUser[0].type}`
);
return response<RequestPasswordResetResponse>(res, {
data: {
sentEmail: true
},
success: true,
error: false,
message: "Password reset requested",
status: HttpCode.OK
});
}
const token = generateRandomString(8, alphabet("0-9", "A-Z", "a-z"));
@@ -120,3 +141,8 @@ export async function requestPasswordReset(
);
}
}
async function randomDelay(maxDelayMs: number) {
const delay = Math.floor(Math.random() * maxDelayMs);
return new Promise((resolve) => setTimeout(resolve, delay));
}

View File

@@ -19,10 +19,7 @@ import { passwordSchema } from "@server/auth/passwordSchema";
export const resetPasswordBody = z
.object({
email: z
.string()
.toLowerCase()
.email(),
email: z.string().toLowerCase().email(),
token: z.string(), // reset secret code
newPassword: passwordSchema,
code: z.string().optional() // 2fa code
@@ -152,7 +149,7 @@ export async function resetPassword(
await db.transaction(async (trx) => {
await trx
.update(users)
.set({ passwordHash })
.set({ passwordHash, lastPasswordChange: new Date().getTime() })
.where(eq(users.userId, resetRequest[0].userId));
await trx

View File

@@ -98,7 +98,8 @@ export async function setServerAdmin(
passwordHash,
dateCreated: moment().toISOString(),
serverAdmin: true,
emailVerified: true
emailVerified: true,
lastPasswordChange: new Date().getTime()
});
});

View File

@@ -23,10 +23,7 @@ import { passwordSchema } from "@server/auth/passwordSchema";
import { UserType } from "@server/types/UserTypes";
import { createUserAccountOrg } from "@server/lib/createUserAccountOrg";
import { build } from "@server/build";
import resend, {
AudienceIds,
moveEmailToAudience
} from "#dynamic/lib/resend";
import resend, { AudienceIds, moveEmailToAudience } from "#dynamic/lib/resend";
export const signupBodySchema = z.object({
email: z.string().toLowerCase().email(),
@@ -183,7 +180,8 @@ export async function signup(
passwordHash,
dateCreated: moment().toISOString(),
termsAcceptedTimestamp: termsAcceptedTimestamp || null,
termsVersion: "1"
termsVersion: "1",
lastPasswordChange: new Date().getTime()
});
// give the user their default permissions:
@@ -224,7 +222,7 @@ export async function signup(
res.appendHeader("Set-Cookie", cookie);
if (build == "saas") {
moveEmailToAudience(email, AudienceIds.General);
moveEmailToAudience(email, AudienceIds.SignUps);
}
if (config.getRawConfig().flags?.require_email_verification) {

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